Merge branch 'v2' into develop

This commit is contained in:
ekzyis 2025-07-26 03:27:41 +02:00
commit 19552799ab
69 changed files with 3090 additions and 5366 deletions

3
.gitignore vendored
View File

@ -1,4 +1 @@
# hugo
public/
resources/
.hugo_build.lock

8
Caddyfile Normal file
View File

@ -0,0 +1,8 @@
:8080 {
root * public
file_server
handle_errors 404 {
rewrite * /404.html
file_server
}
}

View File

@ -1,38 +0,0 @@
# ekzyis
This is the code for my blog at https://ekzy.is/.
## Development
```
// enter dev environment
$ nix-shell
// run server
$ hugo server
// other tab: run tailwindcss in watch mode
$ tailwindcss -i assets/css/input.css -o static/tailwind.css --watch
// other tab: run htmx backend
$ cd server
$ go install github.com/a-h/templ/cmd/templ@latest
$ templ generate
$ go run .
```
## Deployment
```
// deploy static HTML generated by hugo
$ ./deploy
// update server manually
$ ssh ekzy.is
$ tmux at -t <session>
$ <CTRL-C>
$ git pull
$ cd server && templ generate && go build -o server .
$ ./server
$ <CTRL-B+D>
```

View File

@ -2,6 +2,6 @@
<div align="center">
🌐 [ekzy.is](https://ekzy.is) | 💬 [simplex](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FxNnPk9DkTbQJ6NckWom9mi5vheo_VPLm%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnFUiU0M8jS1JY34LxUoPr7mdJlFZwf3pFkjRrhprdQs%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion) [nostr](https://njump.me/npub16x07c4qz05yhqe2gy2q2u9ax359d2lc0tsh6wn3y70dmk8nv2j2s96s89d) [telegram](https://t.me/ekzyis) [signal](https://signal.me/#eu/QQJWrLHuZ-qRrNxo8x1CygWeU9ITJkrCkHg7Sm0vx4WfxB9y5PJM-aPINkauSLxb) | 🔑 [EBAF 75DA 7279 CB48](https://keybase.io/ekzyis)
🌐 [ekzy.is](https://ekzy.is) | 💬 [nostr](https://njump.me/npub16x07c4qz05yhqe2gy2q2u9ax359d2lc0tsh6wn3y70dmk8nv2j2s96s89d) [signal](https://signal.me/#eu/QQJWrLHuZ-qRrNxo8x1CygWeU9ITJkrCkHg7Sm0vx4WfxB9y5PJM-aPINkauSLxb) | 🔑 [EBAF 75DA 7279 CB48](https://keybase.io/ekzyis)
</div>

View File

@ -1,5 +0,0 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++

View File

@ -1,20 +0,0 @@
html.dark {
--background-color: #131418;
--color: #babdc4;
--border-color: #1b1d25;
--link-color: #77a8fd;
--post-meta-color: #767f87;
--post-header-color: #eaeaea;
--quote-color: #9b9ba3;
--quote-border-color: #4a4d56;
a#mode {
line {
fill: none;
}
circle {
fill: none;
}
}
}

View File

@ -1,73 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background-color: #fff;
--color: #434648;
--text-decoration-color: #d2c7c7;
--border-color: #ececec;
--link-color: #003fff;
--post-meta-color: #6b7886;
--post-header-color: #0d122b;
--footer-color: var(--post-meta-color);
--quote-color: #525b66;
--quote-border-color: #c4c8cc;
}
@layer base {
body {
display: flex;
flex-direction: column;
min-height: 100svh;
background-color: var(--background-color);
color: var(--color);
font: 16px/1.85 Roboto, sans-serif;
overflow-y: scroll;
/* FIXME: border-color transition does not work */
/* transition: background-color 1s ease-in-out, border-color 1s ease-in-out; */
}
a {
text-decoration-color: var(--text-decoration-color);
}
a:hover {
color: var(--link-color);
text-decoration-color: var(--link-color);
}
hr {
border-color: var(--border-color);
}
/* roboto-regular - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: url(fonts/Roboto/roboto-v30-latin-regular.eot);
/* IE9 Compat Modes */
src: url(fonts/Roboto/roboto-v30-latin-regular.eot?#iefix) format('embedded-opentype'),
/* IE6-IE8 */
url(fonts/Roboto/roboto-v30-latin-regular.woff2) format('woff2'),
/* Super Modern Browsers */
url(fonts/Roboto/roboto-v30-latin-regular.woff) format('woff'),
/* Modern Browsers */
url(fonts/Roboto/roboto-v30-latin-regular.ttf) format('truetype'),
/* Safari, Android, iOS */
url(fonts/Roboto/roboto-v30-latin-regular.svg#Roboto) format('svg');
/* Legacy iOS */
}
li {
list-style-type: auto;
list-style-position: inside;
}
li>p {
display: inline;
}
}

View File

@ -1,18 +0,0 @@
a#mode {
display: block;
line {
stroke: var(--color);
fill: var(--color);
}
circle {
fill: var(--color);
stroke: var(--color);
}
}
#nav {
border: solid 1px transparent;
border-bottom: solid 1px var(--border-color);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +0,0 @@
(() => {
"use strict";
const LS_THEME_KEY = "theme";
const THEMES = {
LIGHT: "light",
DARK: "dark",
AUTO: "auto",
};
const body = document.body;
const config = body.getAttribute("data-theme");
const getThemeState = () => {
const lsState = localStorage.getItem(LS_THEME_KEY);
if (lsState) return lsState;
let state;
switch (config) {
case THEMES.DARK:
state = THEMES.DARK;
break;
case THEMES.LIGHT:
state = THEMES.LIGHT;
break;
case THEMES.AUTO:
default:
state = window.matchMedia("(prefers-color-scheme: dark)").matches
? THEMES.DARK
: THEMES.LIGHT;
break;
}
return state;
};
const initTheme = (state) => {
if (state === THEMES.DARK) {
document.documentElement.classList.add(THEMES.DARK);
document.documentElement.classList.remove(THEMES.LIGHT);
} else if (state === THEMES.LIGHT) {
document.documentElement.classList.remove(THEMES.DARK);
document.documentElement.classList.add(THEMES.LIGHT);
}
};
// init theme ASAP, then do the rest.
initTheme(getThemeState());
const toggleTheme = () => {
const state = getThemeState();
if (state === THEMES.DARK) {
localStorage.setItem(LS_THEME_KEY, THEMES.LIGHT);
initTheme(THEMES.LIGHT);
} else if (state === THEMES.LIGHT) {
localStorage.setItem(LS_THEME_KEY, THEMES.DARK);
initTheme(THEMES.DARK);
}
};
window.addEventListener("DOMContentLoaded", () => {
// Theme switch
const lamp = document.getElementById("mode");
lamp.addEventListener("click", () => toggleTheme());
});
})();

416
content.go Normal file
View File

@ -0,0 +1,416 @@
package main
import (
"bytes"
"fmt"
"html/template"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
embed "github.com/13rac1/goldmark-embed"
"github.com/alecthomas/chroma/v2/styles"
sn "github.com/ekzyis/snappy"
figure "github.com/mangoumbrella/goldmark-figure"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/html"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"github.com/yuin/goldmark/extension"
"gopkg.in/yaml.v3"
)
type Post struct {
Path string
Title string
Time time.Time
Hidden bool
URL string
Banner string
SnId int
Comments int
Sats int
Markdown string
HTML template.HTML
Images []string
}
type IndexTemplateData struct {
Posts []Post
BaseURL string
Commit string
}
type ErrorTemplateData struct {
BaseURL string
Commit string
}
type PostTemplateData struct {
Post Post
BaseURL string
Commit string
}
type MinifyWriter struct {
w io.Writer
buf bytes.Buffer
m *minify.M
}
func NewHtmlMinifyWriter(w io.Writer) *MinifyWriter {
m := minify.New()
m.AddFunc("text/html", html.Minify)
return &MinifyWriter{
w: w,
m: m,
}
}
func (mw *MinifyWriter) Write(p []byte) (n int, err error) {
return mw.buf.Write(p)
}
func (mw *MinifyWriter) Close() error {
return mw.m.Minify("text/html", mw.w, &mw.buf)
}
var (
contentDir = "content"
mdParser = goldmark.New(
goldmark.WithExtensions(
extension.Footnote,
figure.Figure,
embed.New(),
highlighting.NewHighlighting(
// https://swapoff.org/chroma/playground/
highlighting.WithCustomStyle(styles.Get("catppuccin-mocha")),
),
),
)
baseUrl = "http://localhost:8080/"
commit string
)
func main() {
if os.Getenv("ENV") == "production" {
baseUrl = "https://ekzy.is"
}
output, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
if err != nil {
fmt.Printf("error getting commit: %v\n", err)
os.Exit(1)
}
commit = strings.TrimRight(string(output), "\n")
mdFiles, err := walkMarkdownContent()
if err != nil {
fmt.Printf("error walking markdown content: %v\n", err)
os.Exit(1)
}
posts, err := parsePosts(mdFiles)
if err != nil {
fmt.Printf("error parsing markdown: %v", err)
os.Exit(1)
}
err = executeTemplates(posts)
if err != nil {
fmt.Printf("error executing templates: %v", err)
os.Exit(1)
}
}
func walkMarkdownContent() ([]string, error) {
blogFiles, err := filepath.Glob(filepath.Join(contentDir, "*", "index.md"))
if err != nil {
return nil, err
}
journalFiles, err := filepath.Glob(filepath.Join(contentDir, "journal", "*", "index.md"))
if err != nil {
return nil, err
}
files := append(blogFiles, journalFiles...)
return files, nil
}
func parsePosts(paths []string) ([]Post, error) {
var posts []Post
for _, path := range paths {
post, err := parsePost(path)
if err != nil {
return nil, fmt.Errorf("failed to parse post %s: %v", path, err)
}
posts = append(posts, *post)
}
// sort newest first
sort.Slice(posts, func(i, j int) bool {
return posts[i].Time.After(posts[j].Time)
})
return posts, nil
}
func parsePost(path string) (*Post, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %v", path, err)
}
parts := strings.SplitN(string(content), "---", 3)
if len(parts) < 3 {
return nil, fmt.Errorf("no frontmatter found")
}
var frontmatter map[string]interface{}
err = yaml.Unmarshal([]byte(parts[1]), &frontmatter)
if err != nil {
return nil, err
}
// title
title, _ := frontmatter["title"].(string)
// time
time, ok := frontmatter["date"].(time.Time)
if !ok {
return nil, fmt.Errorf("failed to parse date for %s: %v", path, frontmatter["date"])
}
// hidden
hidden, _ := frontmatter["hidden"].(bool)
// banner
banner, _ := frontmatter["banner"].(string)
// stacker news
snId, _ := frontmatter["sn_id"].(int)
var comments, sats int
if snId > 0 {
item, err := sn.NewClient().Item(snId)
if err != nil {
return nil, fmt.Errorf("failed to get sn item %d: %v", snId, err)
}
comments = item.NComments
sats = item.Sats
}
// url
dir := filepath.Dir(path)
url := strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(dir, contentDir+"/")), "_", "-")
// markdown
markdown := string(parts[2])
// html
var buf bytes.Buffer
if err := mdParser.Convert([]byte(markdown), &buf); err != nil {
return nil, fmt.Errorf("failed to convert markdown for %s: %v", path, err)
}
html := template.HTML(buf.String())
// images
var images []string
err = filepath.Walk(
filepath.Dir(path),
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && regexp.MustCompile(`\.(png|jpg)$`).MatchString(path) {
images = append(images, path)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to find images for %s: %v", path, err)
}
return &Post{
Path: path,
Title: title,
Time: time,
Hidden: hidden,
Banner: banner,
SnId: snId,
Comments: comments,
Sats: sats,
URL: url,
Markdown: markdown,
HTML: html,
Images: images,
}, nil
}
func executeTemplates(posts []Post) error {
tmpl := template.New("").Funcs(
template.FuncMap{
"formatTime": func(t time.Time, format string) string {
return t.Format(format)
},
},
)
tmpl, err := tmpl.ParseFiles(
"html/index.html",
"html/post.html",
"html/404.html",
"html/template/head.html",
"html/template/nav.html",
"html/template/footer.html",
"html/template/post/list.html",
"html/template/post/single.html",
)
if err != nil {
return fmt.Errorf("error parsing templates: %v", err)
}
var blogPosts []Post
var journalPosts []Post
for _, post := range posts {
if !strings.Contains(post.Path, "/journal/") {
blogPosts = append(blogPosts, post)
} else {
journalPosts = append(journalPosts, post)
}
}
err = executeIndexTemplate(tmpl, "public/index.html", blogPosts)
if err != nil {
return fmt.Errorf("error executing index template: %v", err)
}
journalDir := filepath.Join("public", "journal")
err = os.MkdirAll(journalDir, 0755)
if err != nil {
return fmt.Errorf("error creating directory for journal: %v", err)
}
err = executeIndexTemplate(tmpl, "public/journal/index.html", journalPosts)
if err != nil {
return fmt.Errorf("error executing journal index template: %v", err)
}
err = executeErrorTemplate(tmpl)
if err != nil {
return fmt.Errorf("error executing error template: %v", err)
}
err = executePostTemplates(tmpl, posts)
if err != nil {
return fmt.Errorf("error executing post templates: %v", err)
}
return nil
}
func executeIndexTemplate(tmpl *template.Template, outputPath string, posts []Post) error {
outputFile, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer outputFile.Close()
mw := NewHtmlMinifyWriter(outputFile)
defer mw.Close()
err = tmpl.ExecuteTemplate(mw, "index.html", IndexTemplateData{
Posts: posts,
BaseURL: baseUrl,
Commit: commit,
})
if err != nil {
return fmt.Errorf("error executing template: %v", err)
}
fmt.Printf("> %s\n", outputPath)
return nil
}
func executeErrorTemplate(tmpl *template.Template) error {
outputPath := filepath.Join("public", "404.html")
outputFile, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer outputFile.Close()
mw := NewHtmlMinifyWriter(outputFile)
defer mw.Close()
err = tmpl.ExecuteTemplate(mw, "404.html", ErrorTemplateData{
BaseURL: baseUrl,
Commit: commit,
})
if err != nil {
return fmt.Errorf("error executing template: %v", err)
}
fmt.Printf("> %s\n", outputPath)
return nil
}
func executePostTemplates(tmpl *template.Template, posts []Post) error {
for _, post := range posts {
postDir := filepath.Join("public", post.URL)
err := os.MkdirAll(postDir, 0755)
if err != nil {
return fmt.Errorf("error creating directory for post %s: %v", post.Title, err)
}
// Create HTML file
postHTMLPath := filepath.Join(postDir, "index.html")
postFile, err := os.Create(postHTMLPath)
if err != nil {
return fmt.Errorf("error creating HTML file for post %s: %v", post.Title, err)
}
defer postFile.Close()
// Execute the single post template
mw := NewHtmlMinifyWriter(postFile)
defer mw.Close()
err = tmpl.ExecuteTemplate(mw, "post.html", PostTemplateData{
Post: post,
BaseURL: baseUrl,
Commit: commit,
})
if err != nil {
return fmt.Errorf("error executing single post template for %s: %v", post.Title, err)
}
// copy images as webp
for _, image := range post.Images {
dstImage := filepath.Join(postDir, filepath.Base(image))
dstImage = strings.TrimSuffix(strings.TrimSuffix(dstImage, ".png"), ".jpg") + ".webp"
err = toWebp(image, dstImage)
if err != nil {
return fmt.Errorf("error copying image %s: %v", image, err)
}
fmt.Printf("> %s\n", dstImage)
}
fmt.Printf("> %s\n", postHTMLPath)
}
return nil
}
func toWebp(src, dst string) error {
err := exec.Command("ffmpeg", "-i", src, "-c:v", "libwebp", dst).Run()
if err != nil {
return fmt.Errorf("error converting image to webp: %v", err)
}
return nil
}

View File

@ -24,7 +24,7 @@ Anyway, I didn't know if I was going to ever publish my feelings and let everyon
> _Would I have enough time in my life to ever finish writing? To finish my thoughts? Or would I die in the most ridiculous way possible tomorrow? Snuffed out like a candle with all my dreams and aspirations [in an instant](https://www.goodreads.com/book/show/51037979-in-an-instant)? No time to ask for pen and paper in my last moments? At least it would be fun and painless, I guess. But would anyone care if one more light goes out? Would I care?_
{{< youtube Tm8LGxTLtQk >}}
![](https://www.youtube.com/watch?v=Tm8LGxTLtQk)
Whatever I did, there was always something I needed to write down RIGHT NOW. And that never seemed to be code.
@ -52,9 +52,8 @@ Whatever signals my body was sending must have gotten completely overriden until
I hope this set the stage enough for what comes next.
![peace-and-love.jpg](./peace-and-love.jpg)
_a picture taken on a beautiful day in Spring 2020 that I found while searching for a specific, more related picture that I didn't find_
![peace-and-love.webp](./peace-and-love.webp)
a picture taken on a beautiful day in Spring 2020 that I found while searching for a specific, more related picture that I didn't find
---------DISCLAIMER END---------

View File

@ -1,7 +1,7 @@
---
title: 4_F4ll_GuY_0x02.md
date: 2024-03-21T22:46:03.079Z
banner: fall-guy.jpg
banner: fall-guy.webp
sn_id: 474909
---
@ -15,7 +15,7 @@ The other world felt like a world full of sweet dreams. Dreams you are not in co
You don't want to leave a world that could care less about you but it doesn't. A world that tries to convince you that everything will be fine as long as you just let go, give up control and have trust in the process. A world that loves you just as you were. Not as you are, but as you were before you were born. A world that tries to teach you something but how hard that lesson is going to be is up to you. That's part of the lesson.
{{< youtube 38FMDG7tiA4 >}}
![](https://www.youtube.com/watch?v=38FMDG7tiA4)
But you are just too human and need to find a problem in everything so you can try to solve it and give meaning to your insignificant existence. And if there isn't one, you make one: YOU become the problem.

View File

@ -1,8 +1,7 @@
---
title: About
date: 2024-04-13T12:27:51.281Z
banner: psychedelic-digital-sky.jpg
menu: main
banner: psychedelic-digital-sky.webp
sn_id: 505345
---

View File

@ -1,7 +1,7 @@
---
title: Austin's Orange Glow
date: 2024-06-23
banner: austin_orange_glow.jpg
banner: austin_orange_glow.webp
hidden: true
sn_id: 674266
---
@ -70,7 +70,8 @@ Excited to finally be able to move around freely again, I passed a sign that men
# PlebLab
{{< figure src="/sn_cropped.jpg" caption="The Stacker News Door" >}}
![](./sn_cropped.webp)
The Stacker News Door
* supertestnet introducing me to everyone; ek vs ek-zee-kee-yas

View File

@ -1,7 +1,7 @@
---
title: Between Moments of Wonder and Euphoria
date: 2024-07-13
banner: landscape.jpg
banner: landscape.webp
sn_id: 607554
---

View File

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 365 KiB

View File

@ -52,7 +52,7 @@ The chains traverse through their rules in order until they find a matching rule
The order of the chains is defined in this (simplified) flow chart taken from [here](https://wiki.archlinux.org/title/iptables#Basic_concepts):
![iptables_flowchart.png](./iptables_flowchart.png)
![iptables_flowchart.webp](./iptables_flowchart.webp)
Packets that come in on any network interface enter this flow chart at the top
and thus go first through the PREROUTING chain of the nat table.
@ -514,7 +514,7 @@ This is because `wg0` is the _virtual network interface_, not the actual physica
interface that sends the UDP packets. WireGuard works by wrapping all packets (like ICMP here) in UDP packets
before sending them out "over the wire". The following chart should make more clear what this means:
![wireguard_layering.png](./wireguard_layering.png)
![wireguard_layering.webp](./wireguard_layering.webp)
If we use the physical network interface
(which is `enp3s0` for the local machine as can be seen in `ip address`), the ping works again:

View File

@ -2,7 +2,7 @@
title: I miss her
date: 2025-05-10
sn_id: 977206
banner: remember.jpg
banner: remember.webp
---
I've been reading [@plebpoet](https://stacker.news/plebpoet)'s [journal](https://www.plebpoet.com/journal.html). I'll write a few words every now and then and hope they survive.

View File

@ -22,7 +22,8 @@ However, today the experience was really bad: the Waymo got stuck in a construct
Anyway, when it drove into the site, there was a truck that had to move out of the way before we could continue. The Waymo got visibly confused about why there was a truck sideways on the street and started to back up. I think it wanted to turn around because it didnt want to drive in reverse for more than a few meters. Even though the truck was already off the road, it kept backing up and trying to find a way to turn around. When it started to drive into a gap in the pylons, I got really worried and felt sorry for the construction workers—who probably don't get paid enough to deal with this shit—and who were also starting to get concerned about what the fuck was going on. I wasn't sure if the gap was meant for the Waymo to drive into or if it was only for construction vehicles.
{{< figure src="./waymo.jpg" caption="Confused Waymo in construction site" >}}
![](./waymo.webp)
Confused Waymo in construction site
When the Waymo stayed there too long, trying to maneuver by driving back and forth but not really going anywhere, I seriously considered getting out and telling the workers that I'm really sorry, but I don't know what to do since the car drives itself.

View File

@ -1,7 +1,7 @@
---
title: Trust Is the Scaling Solution
date: 2025-05-13
banner: colorado.jpg
banner: colorado.webp
sn_id: 979677
---
@ -45,7 +45,7 @@ I wanted to go over and respect what he had to say by just listening to him, wit
This made me think of the Map of Consciousness:
![Map of Consciousness](map.png)
![Map of Consciousness](map.webp)
_ever since [@anon](https://stacker.news/anon) shared this with me in, I like to mention this map wherever I can._

View File

@ -18,7 +18,8 @@ _Disclaimer: all commit timestamps on Github are wrong because I rebase a lot._
Yesterday, I thought I was done with designing the new wallet schema and I could start to update the Javascript code but then I noticed (again) that I forgot that I haven't updated four other tables (`DirectPayment`, `InvoiceForward`, `Withdrawal` and `WalletLog`) yet.
{{< figure src="diff.png" caption="a snippet of the changeset in 25306b34" >}}
![](./diff.webp)
a snippet of the changeset in 25306b34
I updated them in [`bc0df957`](https://github.com/stackernews/stacker.news/pull/2146/commits/bc0df95781815cc00f7781d7cfa8450bbaa72286) but I knew this wasn't going to work because the new wallets will have different IDs. This is what I fixed in [`25306b34`](https://github.com/stackernews/stacker.news/pull/2146/commits/25306b3408cab815810731ce198cdf5568ced630) today. To test that I actually fixed it, I added some payments to my test data in [`a9705e80`](https://github.com/stackernews/stacker.news/pull/2146/commits/a9705e806d61acffb4344c1eba177acad5e6a502).

View File

@ -60,4 +60,4 @@ Now, I would like my next girlfriend not only to climb but also to go dancing wi
I really liked this song today:
{{< youtube 24Q670UxPGc >}}
![](https://www.youtube.com/watch?v=24Q670UxPGc)

View File

@ -2,7 +2,7 @@
title: Life is about Awareness
date: 2024-02-05
sn_id: 413652
banner: palebluedot.jpg
banner: palebluedot.webp
---
> Some say we're never meant to grow up

View File

@ -10,7 +10,8 @@ For a long time, I couldn't figure out why I couldn't access my self-hosted [vau
I arrived at this intermediate conclusion by comparing the browser's TLS v1.3 handshake with the one from cURL. I noticed the browser's Client Hello is a lot bigger (1866 vs 517) and has a lot of TCP retransmissions:
{{< figure src="/tcp_retransmission.png" caption="Why is the Client Hello so big? Is it related to the TCP transmissions?" >}}
![](./tcp_retransmission.webp)
Why is the Client Hello so big? Is it related to the TCP transmissions?
I also noticed that if I forced Firefox—I couldn't figure this out with Brave—to use TLS v1.2 by setting `security.tls.version.max` to 3 in the advanced config (that you can visit if you type about:config into the address bar), the site loaded immediately. So it was definitely related to TLS v1.3, but specifically the implementation in Brave and Firefox, since I could use TLS v1.3 fine with cURL.
@ -18,7 +19,8 @@ I also noticed that if I forced Firefox—I couldn't figure this out with Brave
I then looked further into why it was so big and noticed the unknown key share 4588. Thanks to this [blog post](https://www.netmeister.org/blog/tls-hybrid-kex.html), I learned that this is a post-quantum cryptography thing.
{{< figure src="/tcp_wow.png" caption="Unknown key share 4588 takes up most of the Client Hello TCP packet" >}}
![](./tcp_wow.webp)
Unknown key share 4588 takes up most of the Client Hello TCP packet
Fortunately, Firefox also had a setting to disable this via `security.tls.enable_kyber`.

View File

@ -4,7 +4,7 @@ date: 2024-01-23
sn_id: 398641
---
{{< youtube u1_EBSlnDlU >}}
![](https://www.youtube.com/watch?v=u1_EBSlnDlU)
_Ignore the YT channel name even though it's kind of funny how unfitting it is for this scene from [_Bojack Horseman_](https://www.rottentomatoes.com/tv/bojack_horseman)._

View File

@ -8,13 +8,13 @@ sn_id: 230179
In my [previous blog post](/demystifying-wireguard-and-iptables), I have shown you how to setup your own VPN with [WireGuard](https://wireguard.com/) and [`iptables`](https://wiki.archlinux.org/title/iptables). We have established a point-to-point connection between two peers where one peer (10.172.16.1) was reachable from the internet:
![point-to-point.png](./point-to-point.png)
![point-to-point.webp](./point-to-point.webp)
Today, I will explain how more peers can be added to our VPN.
One peer ("the router") will be configured to forward packets between all other peers ("the end devices").
Therefore, our VPN will become a star network:
![star-network.png](./star-network.png)
![star-network.webp](./star-network.webp)
You could then [install WireGuard on your mobile dev](https://www.wireguard.com/install/#android-play-store-direct-apk-file) and reach all other machines in your VPN from anywhere with internet connection.

View File

@ -14,7 +14,7 @@ However, since the HTTP server is running inside the VPN, we will need to use po
Essentially, we will update the network topology from the [previous blog post](/wireguard-packet-forwarding) with a connection to the public internet:
![star-network-internet.png](./star-network-internet.png)
![star-network-internet.webp](./star-network-internet.webp)
As always, after reading my blog post, you will not only be able to setup your own HTTP server inside a VPN; but will actually understand what happens at the network layer. With this knowledge, you will be able to adapt and find the best solution for your specific needs.
@ -37,15 +37,15 @@ To enable internet access for hosts with only a private IPv4 address, every pack
[^1]: A router in a private network has two IP addresses: a private address which is commonly the first IP address in the used ranged (so 192.168.0.1 if the range 192.168.0.0/16 is used) and a public one assigned by an internet service provider.
![nat-send.png](./nat-send.png)
![nat-send.webp](./nat-send.webp)
The NAT gateway then stores this replacement in a NAT table:
![nat-table.png](./nat-table.png)
![nat-table.webp](./nat-table.webp)
For arriving packets, this table is consulted to reverse the translation:
![nat-recv.png](./nat-recv.png)
![nat-recv.webp](./nat-recv.webp)
The NAT IP address and NAT port are required to reverse this process. Without them, we would not be able to distinguish multiple connections from the private network to the same destination IP address.
@ -59,7 +59,7 @@ The reversal in both methods is automatically handled using the NAT table.
We will now apply this knowledge to expose an HTTP server running inside a VPN. Our setup will work like this:
![nat-http.png](./nat-http.png)
![nat-http.webp](./nat-http.webp)
We will have to use DNAT _and_ SNAT since we need to change the destination IP address to 10.172.16.2 _and_ the source IP address to 10.172.16.1 since we can only route internal IP addresses (10.172.16.0/24) over the virtual network interface to the HTTP server.

19
deploy
View File

@ -1,24 +1,19 @@
#!/usr/bin/env bash
# https://gohugo.io/hosting-and-deployment/deployment-with-rsync/
RSYNC_OPTS="-avh --delete"
set -e
RSYNC_OPTS="-avh --delete"
COMMIT=$(git rev-parse --short HEAD)
rm -r public/
cp -r static/ public/
sed -i -e "s/^commit\s*=.*$/commit = \"$COMMIT\"/" hugo.toml
rm -rf public/
hugo --minify
tailwindcss -i assets/css/input.css -o static/tailwind.css
ENV=production go run content.go
tailwindcss -i input.css -o public/css/tailwind.css
rsync $RSYNC_OPTS --dry-run public/ ekzy.is:/var/www/ek
echo
read -p "Continue deploy? [yn] " yn
read -n 1 -p "Continue deploy? [yn] " yn
echo
[ "$yn" == "y" ] && rsync $RSYNC_OPTS public/ ekzy.is:/var/www/ek
git checkout hugo.toml
[ "$yn" == "y" ] && set -x && rsync $RSYNC_OPTS public/ ekzy.is:/var/www/ek

61
flake.lock generated Normal file
View File

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1752620740,
"narHash": "sha256-f3pO+9lg66mV7IMmmIqG4PL3223TYMlnlw+pnpelbss=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "32a4e87942101f1c9f9865e04dc3ddb175f5f32e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

35
flake.nix Normal file
View File

@ -0,0 +1,35 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
go
caddy
tailwindcss
ffmpeg
];
};
apps.default = {
type = "app";
program = toString (pkgs.writeShellScript "serve" ''
set -xe
rm -r public/
cp -r static/ public/
${pkgs.go}/bin/go run content.go
${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o public/css/tailwind.css
${pkgs.caddy}/bin/caddy run --config Caddyfile
'');
};
}
);
}

20
go.mod Normal file
View File

@ -0,0 +1,20 @@
module ekzyis-v2
go 1.24.3
require (
github.com/13rac1/goldmark-embed v0.0.0-20201220231550-e6806f2de66a
github.com/alecthomas/chroma/v2 v2.19.0
github.com/ekzyis/snappy v0.8.0
github.com/mangoumbrella/goldmark-figure v1.3.0
github.com/tdewolff/minify/v2 v2.23.9
github.com/yuin/goldmark v1.7.12
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/tdewolff/parse/v2 v2.8.1 // indirect
gopkg.in/guregu/null.v4 v4.0.0 // indirect
)

44
go.sum Normal file
View File

@ -0,0 +1,44 @@
github.com/13rac1/goldmark-embed v0.0.0-20201220231550-e6806f2de66a h1:97tpPJ82VuexbkbPLIzF4BrPy/4XalKF1CKyMFc1fs0=
github.com/13rac1/goldmark-embed v0.0.0-20201220231550-e6806f2de66a/go.mod h1:dxt3ggQZ3euHiXGfETfZPfA5OUpKgJn1s4vS+YT1MEU=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ekzyis/snappy v0.8.0 h1:e7dRR384XJgNYa1FWNIZmqITSHOSanteBFXQJPfcQwg=
github.com/ekzyis/snappy v0.8.0/go.mod h1:UksYI0dU0+cnzz0LQjWB1P0QQP/ghx47e4atP99a5Lk=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/mangoumbrella/goldmark-figure v1.3.0 h1:Ux6SJKeab+iOaiR6rZedGmvac/npXb68ZfgUI8OBfig=
github.com/mangoumbrella/goldmark-figure v1.3.0/go.mod h1:iIL+fhdmCQDpE0l/TKtGhokWzIbo5lo/Y2OIAcx6usI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.23.9 h1:s8hX6wQzOqmanyLxmlynInRPVgZ/xASy6sUHfGsW6kU=
github.com/tdewolff/minify/v2 v2.23.9/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno=
github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po=
github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg=
gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

20
html/404.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en-us">
{{ template "head" }}
<body>
{{ template "nav" .BaseURL }}
<div class="container my-auto">
<div class="flex flex-col">
<pre class="mx-auto">
__ __ ____ __ __
/ // / / __ \/ // /
/ // /_/ / / / // /_
/__ __/ /_/ /__ __/
/_/ \____/ /_/
</pre>
<div class="text-center">page not found</div>
</div>
</div>
{{ template "footer" .Commit }}
</body>
</html>

15
html/index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en-us">
{{ template "head" }}
<body>
{{ template "nav" .BaseURL }}
<div class="container my-auto">
<main class="post-index">
{{ range .Posts }}
{{ template "post/list" . }}
{{ end }}
</main>
</div>
{{ template "footer" .Commit }}
</body>
</html>

11
html/post.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en-us">
{{ template "head" }}
<body>
{{ template "nav" .BaseURL }}
<div class="container">
{{ template "post/single" .Post }}
</div>
{{ template "footer" .Commit }}
</body>
</html>

28
html/template/footer.html Normal file
View File

@ -0,0 +1,28 @@
{{ define "footer" }}
<footer>
<div>
<a href="https://github.com/ekzyis" target="_blank" rel="noopener noreferrer me" title="Github">
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
</a>
<a href="/pgp.txt" target="_blank" rel="noopener noreferrer me" title="Pgpkey">
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 18l2-2h2l1.36-1.36a6.5 6.5 0 1 0-3.997-3.992L2 18v4h4l2-2v-2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="17" cy="7" r="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
<div class="flex flex-col leading-relaxed pt-1">
<small>
running <a class="underline" href="https://git.ekzy.is/ekzyis/ekzyis/commit/{{ . }}" target="_blank" rel="noopener noreferrer me">{{ . }}</a>
</small>
<small>
analytics by <a class="underline" href="https://plausible.io/ekzy.is" target="_blank" rel="noopener noreferrer me">plausible.io</a>
</small>
<small>
<code>EBAF 75DA 7279 CB48</code>
</small>
</div>
</footer>
{{ end }}

12
html/template/head.html Normal file
View File

@ -0,0 +1,12 @@
{{ define "head" }}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ekzyis</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<script defer data-api="/api/event" data-domain="ekzy.is" src="/js/script.js"></script>
<link rel="stylesheet" href="/css/tailwind.css">
<link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/footer.css">
</head>
{{ end }}

14
html/template/nav.html Normal file
View File

@ -0,0 +1,14 @@
{{ define "nav" }}
<div class="container my-3">
<div id="nav">
<nav class="mb-3 sm:px-3">
<a href="{{ . }}">ekzyis</a>
<div class="flex float-right items-center">
<a class="px-2" href="/journal">Journal</a>
<span>|</span>
<a class="px-2" href="/about/">About</a>
</div>
</nav>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,10 @@
{{ define "post/list" }}
<article class="post-item{{ if .Hidden }} !hidden{{ end }}">
<h4 class="post-item-title">
<a href="/{{ .URL }}/">{{ .Title }}</a>
</h4>
<time class="post-item-meta" datetime="{{ formatTime .Time "2006-01-02T15:04:05Z" }}">
{{ formatTime .Time "2 Jan 2006" }}
</time>
</article>
{{ end }}

View File

@ -0,0 +1,34 @@
{{ define "post/single" }}
<main class="post">
<article>
<header class="post-header">
<h1 class="post-title">{{ .Title }}</h1>
{{ if .Banner }}
<img class="post-banner" src="{{ .Banner }}">
{{ end }}
<div class="post-meta">
<time datetime="{{ formatTime .Time "2006-01-02T15:04:05Z" }}" itemprop="datePublished">
{{ formatTime .Time "Jan 2, 2006" }}
</time>
{{ if .SnId }}
<span>
<span class="px-1">|</span>
<span>
<a
class="underline"
href="https://stacker.news/items/{{- .SnId -}}"
target="_blank"
rel="noopener noreferrer me">{{ .Comments }} comments</a>
</span>
<span class="px-1">|</span>
<span>{{ .Sats }} sats</span>
</span>
{{ end }}
</div>
</header>
<div class="post-content">
{{ .HTML }}
</div>
</article>
</main>
{{ end }}

View File

@ -1,28 +0,0 @@
baseURL = 'https://ekzy.is/'
languageCode = 'en-us'
title = 'ekzyis'
[markup]
[markup.highlight]
style = 'catppuccin-mocha'
[[menu.main]]
name = "Journal"
url = "/journal"
weight = 1
[[params.socials]]
name = "github"
url = "https://github.com/ekzyis"
[[params.socials]]
name = "Rss"
url = "/index.xml"
[[params.socials]]
name = "pgpkey"
url = "/pgp.txt"
fingerprint = "EBAF 75DA 7279 CB48"
[params]
commit = "000000"

132
input.css Normal file
View File

@ -0,0 +1,132 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background-color: #131418;
--color: #babdc4;
--border-color: #1b1d25;
--link-color: #77a8fd;
--text-decoration-color: #767f87;
--post-meta-color: #767f87;
--post-header-color: #eaeaea;
--quote-color: #9b9ba3;
--quote-border-color: #4a4d56;
--footer-color: #767f87;
--footnotes-color: #767f87;
}
@layer base {
body {
display: flex;
flex-direction: column;
min-height: 100svh;
background-color: var(--background-color);
color: var(--color);
font: 16px/1.85 Roboto, sans-serif;
overflow-y: scroll;
/* FIXME: border-color transition does not work */
/* transition: background-color 1s ease-in-out, border-color 1s ease-in-out; */
}
a {
text-decoration-color: var(--text-decoration-color);
}
a:hover {
color: var(--link-color);
text-decoration-color: var(--link-color);
}
hr {
border-color: var(--border-color);
margin-bottom: 20px;
}
/* roboto-regular - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: url(/fonts/Roboto/roboto-v30-latin-regular.eot);
/* IE9 Compat Modes */
src: url(/fonts/Roboto/roboto-v30-latin-regular.eot?#iefix) format('embedded-opentype'),
/* IE6-IE8 */
url(/fonts/Roboto/roboto-v30-latin-regular.woff2) format('woff2'),
/* Super Modern Browsers */
url(/fonts/Roboto/roboto-v30-latin-regular.woff) format('woff'),
/* Modern Browsers */
url(/fonts/Roboto/roboto-v30-latin-regular.ttf) format('truetype'),
/* Safari, Android, iOS */
url(/fonts/Roboto/roboto-v30-latin-regular.svg#Roboto) format('svg');
/* Legacy iOS */
}
li {
list-style-type: auto;
list-style-position: inside;
}
li>p {
display: inline;
}
blockquote {
color: var(--quote-color);
opacity: .9;
border-left: 5px solid var(--quote-border-color);
padding: 0 0 0 1rem;
margin-left: .3rem;
margin-top: 10px;
margin-bottom: 10px;
font-size: 1em;
}
pre {
padding: 5px 5px;
overflow-x: auto;
scrollbar-width: thin;
font-size: small;
margin-bottom: 20px;
}
figure {
margin: 1em 0;
}
figcaption {
color: var(--post-meta-color);
font-size: small;
text-align: center;
margin-top: 3px;
margin-bottom: 10px;
}
ul>li {
list-style-type: disc;
list-style-position: inside;
}
img {
max-height: 600px;
object-fit: contain;
}
figure:has(iframe) {
position: relative;
padding-bottom: 56.25% !important;
height: 0;
overflow: hidden;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0
}
}

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
{{ $defaultColor := .Site.Params.defaultColor | default "auto" }}
{{ if eq $defaultColor "dark" }}
<html lang="{{ .Site.LanguageCode }}" class="dark">
{{ else if eq $defaultColor "light" }}
<html lang="{{ .Site.LanguageCode }}" class="light">
{{ else }}
<html lang="{{ .Site.LanguageCode }}">
{{ end }}
{{ partial "head.html" . }}
<body data-theme="{{ $defaultColor }}">
{{ partial "scripts.html" . }}
{{ partial "nav.html" . }}
{{ block "main" . }}{{ end }}
{{ partial "footer.html" . }}
</body>
</html>

View File

@ -1,59 +0,0 @@
{{- $authorEmail := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .email }}
{{- $authorEmail = . }}
{{- end }}
{{- end }}
{{- end }}
{{- $authorName := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .name }}
{{- $authorName = . }}
{{- end }}
{{- else }}
{{- $authorName = . }}
{{- end }}
{{- end }}
{{- $pctx := . }}
{{- if .IsHome }}{{ $pctx = .Site }}{{ end }}
{{- $pages := slice }}
{{- if or $.IsHome $.IsSection }}
{{- $pages = $pctx.RegularPages }}
{{- else }}
{{- $pages = $pctx.Pages }}
{{- end }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $pages = $pages | first $limit }}
{{- end }}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
<copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
<lastBuildDate>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end }}
{{- range where (where $pages "Params.hidden" "ne" true) "Section" "ne" "journal" }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
<guid>{{ .Permalink }}</guid>
<description>{{ .Summary | transform.XMLEscape | safeHTML }}</description>
</item>
{{- end }}
</channel>
</rss>

View File

@ -1,45 +0,0 @@
{{ define "main" }}
<div class="container">
<main class="post">
<article>
<header class="post-header">
<h1 class="post-title">{{ .Title }}</h1>
{{ with .Resources.GetMatch .Params.banner }}
{{ $image := .Fit "1920x1080" }}
<img class="post-banner" src="{{ $image.RelPermalink }}" width="{{ $image.Width }}">
{{ end }}
<div class="post-meta">
{{ with .Date }}
{{ $ISO_time := dateFormat "2006-01-02T15:04:05-07:00" . }}
<time datetime="{{ $ISO_time }}" itemprop="datePublished"> {{ . | time.Format ":date_medium" }} </time>
{{ end }}
{{ with .Params.sn_id }}
<span
hx-get="
{{- if not hugo.IsProduction -}}http://localhost:1337{{- end -}}
/api/content_meta?sn_id={{- . -}}
"
hx-trigger="load"
hx-swap="outerHTML"
>
<span class="px-1">|</span>
<span>
<a
class="underline"
href="https://stacker.news/items/{{- . -}}"
target="_blank"
rel="noopener noreferrer me">0 comments</a>
</span>
<span class="px-1">|</span>
<span>0 sats</span>
<span>
{{ end }}
</div>
</header>
<div class="post-content">
{{ .Content }}
</div>
</article>
</main>
</div>
{{ end }}

View File

@ -1,24 +0,0 @@
{{ define "main" }}
<div class="container my-auto">
<main class="post-index">
<h3 class="posts-item-note">{{ T "home.recent_posts" }}</h3>
{{/* Show last 5 posts in reverse date order */}}
{{ $pagesToShow := where .Site.RegularPages "Section" "ne" "journal" }}
{{ $posts := $pagesToShow.ByDate.Reverse }}
{{ range $posts }}
<article class="post-item {{- if .Params.hidden }} !hidden {{- end -}}">
<h4 class="post-item-title">
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
</h4>
{{/* format date string to create an ISO 8601 string */}}
{{ $ISO_date := "2006-01-02T15:04:05Z0700" }}
{{ $configDateFormat := .Site.Params.dateFormat | default "2 Jan 2006" }}
<time class="post-item-meta" datetime="{{ dateFormat $ISO_date .Date }}">
{{ time.Format $configDateFormat .Date }}
{{/* OLD FORMAT: .Date.Format $configDateFormat */}}
</time>
</article>
{{ end }}
</main>
</div>
{{ end }}

View File

@ -1,28 +0,0 @@
{{ define "main" }}
<div class="container my-auto">
<main class="post-index">
{{ $pagesToShow := where .Pages "Section" "journal" }}
{{ $posts := $pagesToShow.ByDate.Reverse }}
<div class="mb-3">
<blockquote>
<p>
That is perhaps the most solid dating advice I have, by the way—show the inside of your head in public, so people can see if they would like to live in there.
</p>
</blockquote>
<a href="https://www.henrikkarlsson.xyz/p/looking-for-alice" target="_blank">Henrik Karlsson, <span class="italic">Looking for Alice</span></a>
</div>
{{ range $posts }}
<article class="post-item {{- if .Params.hidden }} !hidden {{- end -}}">
<h4 class="post-item-title">
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
</h4>
{{ $ISO_date := "2006-01-02T15:04:05Z0700" }}
{{ $configDateFormat := .Site.Params.dateFormat | default "2 Jan 2006" }}
<time class="post-item-meta" datetime="{{ dateFormat $ISO_date .Date }}">
{{ time.Format $configDateFormat .Date }}
</time>
</article>
{{ end }}
</main>
</div>
{{ end }}

View File

@ -1,21 +0,0 @@
<footer>
<div>
{{- range site.Params.socials -}}
<a href="{{ trim .url " " }}" target="_blank" rel="noopener noreferrer me"
title="{{ (.title | default .name) | title }}">
{{ partial "svg/svgs.html" . }}
</a>
{{- end -}}
</div>
<div class="flex flex-col leading-relaxed pt-1">
<small>
running <a class="underline" href="https://git.ekzy.is/ekzyis/ekzyis/commit/{{ site.Params.commit }}" target="_blank" rel="noopener noreferrer me">{{ site.Params.commit }}</a>
</small>
<small>
analytics by <a class="underline" href="https://plausible.io/ekzy.is" target="_blank" rel="noopener noreferrer me">plausible.io</a>
</small>
<small>
<code>EBAF 75DA 7279 CB48</code>
</small>
</div>
</footer>

View File

@ -1,29 +0,0 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="stylesheet" href="/tailwind.css" />
{{ if hugo.IsProduction }}
{{ $dark := resources.Get "css/dark.css" | minify | fingerprint }}
{{ $nav := resources.Get "css/nav.css" | minify | fingerprint }}
{{ $post := resources.Get "css/post.css" | minify | fingerprint }}
{{ $footer := resources.Get "css/footer.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $dark.RelPermalink }}" integrity="{{ $dark.Data.Integrity }}" />
<link rel="stylesheet" href="{{ $nav.RelPermalink }}" integrity="{{ $nav.Data.Integrity }}" />
<link rel="stylesheet" href="{{ $post.RelPermalink }}" integrity="{{ $post.Data.Integrity }}" />
<link rel="stylesheet" href="{{ $footer.RelPermalink }}" integrity="{{ $footer.Data.Integrity }}" />
<script defer data-api="/api/event" data-domain="ekzy.is" src="/js/script.js"></script>
{{ else }}
{{ $dark := resources.Get "css/dark.css" }}
{{ $nav := resources.Get "css/nav.css" }}
{{ $post := resources.Get "css/post.css" }}
{{ $footer := resources.Get "css/footer.css" }}
<link rel="stylesheet" href="{{ $dark.RelPermalink }}" />
<link rel="stylesheet" href="{{ $nav.RelPermalink }}" />
<link rel="stylesheet" href="{{ $post.RelPermalink }}" />
<link rel="stylesheet" href="{{ $footer.RelPermalink }}" />
{{ end }}
{{ $htmx := resources.Get "js/htmx.js" | minify | fingerprint }}
<script src="{{ $htmx.RelPermalink }}" integrity="{{ $htmx.Data.Integrity }}"></script>
</head>

View File

@ -1,21 +0,0 @@
<div class="container my-3">
<div id="nav">
<nav class="mb-3 sm:px-3">
{{ $currentPage := . }}
<a href="{{ .Site.Home.Permalink }}">ekzyis</a>
<div class="flex float-right items-center">
{{ range site.Menus.main }}
{{ if $currentPage.IsMenuCurrent .Menu . }}
<a class="px-2 font-semibold" href="{{ .URL }}">{{ .Name }}</a>
{{ else }}
<a class="px-2" href="{{ .URL }}">{{ .Name }}</a>
{{ end }}
<span>|</span>
{{ end }}
<a id="mode" class="px-2" href="#">
{{ partial "svg/sun.svg" (dict "height" 21 "width" 21 "title" "LIGHT") . }}
</a>
</div>
</nav>
</div>
</div>

View File

@ -1,7 +0,0 @@
{{ if hugo.IsProduction }}
{{ $theme_script := resources.Get "js/theme.js" | minify | fingerprint }}
<script src="{{ $theme_script.RelPermalink }}" integrity="{{ $theme_script.Data.Integrity }}"></script>
{{ else }}
{{ $theme_script := resources.Get "js/theme.js" }}
<script src="{{ $theme_script.RelPermalink }}"></script>
{{ end}}

View File

@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" class="{{ .class }}" width="{{ .width }}" height="{{ .height }}" viewBox="0 0 14 14" stroke-width="1">
<title>{{ .title }}</title><g><circle cx="7" cy="7" r="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"></circle><line x1="7" y1="0.5" x2="7" y2="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="2.4" y1="2.4" x2="3.82" y2="3.82" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="0.5" y1="7" x2="2.5" y2="7" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="2.4" y1="11.6" x2="3.82" y2="10.18" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="7" y1="13.5" x2="7" y2="11.5" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="11.6" y1="11.6" x2="10.18" y2="10.18" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="13.5" y1="7" x2="11.5" y2="7" fill="none" stroke-linecap="round" stroke-linejoin="round"></line><line x1="11.6" y1="2.4" x2="10.18" y2="3.82" fill="none" stroke-linecap="round" stroke-linejoin="round"></line></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

2
server/.gitignore vendored
View File

@ -1,2 +0,0 @@
# ignore files generated by `templ generate`
*_templ.go

View File

@ -1,20 +0,0 @@
package main
import "github.com/ekzyis/snappy"
import "fmt"
templ contentMeta (item *sn.Item) {
<span class="px-1">|</span>
<span>
<a
class="underline"
href={ templ.URL(fmt.Sprintf("https://stacker.news/items/%d", item.Id)) }
target="_blank"
rel="noopener noreferrer me"
>
{ fmt.Sprintf("%d comments", item.NComments) }
</a>
</span>
<span class="px-1">|</span>
<span>{ fmt.Sprintf("%d sats", item.Sats) }</span>
}

View File

@ -1,8 +0,0 @@
module git.ekzy.is/ekzyis/ekzyis
go 1.21.9
require (
github.com/a-h/templ v0.2.707 // indirect
github.com/ekzyis/snappy v0.4.3-0.20240602063639-f18721853255 // indirect
)

View File

@ -1,6 +0,0 @@
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
github.com/ekzyis/snappy v0.4.2 h1:5Tw13OIm+w4jTmO3O4c7HzyoiAhbxKl1WzqSoi6x8GQ=
github.com/ekzyis/snappy v0.4.2/go.mod h1:BxJwdGlCwUw0Q5pQzBr59weAIS6pkVdivBBaZkkWTSo=
github.com/ekzyis/snappy v0.4.3-0.20240602063639-f18721853255 h1:3wTBDjHMmeskbCXmedyvDS7i1ZBZj0G8ZH/EJiKOgZA=
github.com/ekzyis/snappy v0.4.3-0.20240602063639-f18721853255/go.mod h1:BxJwdGlCwUw0Q5pQzBr59weAIS6pkVdivBBaZkkWTSo=

View File

@ -1,64 +0,0 @@
package main
import (
"fmt"
"log"
"net/http"
"strconv"
sn "github.com/ekzyis/snappy"
)
func main() {
bindAddr := "127.0.0.1:1337"
http.HandleFunc("/api/content_meta", HandleContentMeta)
log.Printf("server running on %s\n", bindAddr)
http.ListenAndServe(bindAddr, logRequest(http.DefaultServeMux))
}
func logRequest(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
handler.ServeHTTP(w, r)
})
}
func HandleContentMeta(w http.ResponseWriter, r *http.Request) {
var (
// stacker.news item id
qid = r.URL.Query().Get("sn_id")
c = sn.NewClient()
item *sn.Item
id int
err error
)
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Methods", "GET")
w.Header().Add("Access-Control-Allow-Headers", "*")
if r.Method == "OPTIONS" {
return
}
if qid == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "sn_id query param required\n")
return
}
if id, err = strconv.Atoi(qid); err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "sn_id must be numeric\n")
return
}
if item, err = c.Item(id); err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
return
}
contentMeta(item).Render(r.Context(), w)
}

View File

@ -1,8 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages;
[
go hugo tailwindcss
];
}

View File

@ -127,60 +127,8 @@
text-wrap: nowrap;
}
blockquote {
color: var(--quote-color);
opacity: .9;
border-left: 5px solid var(--quote-border-color);
padding: 0 0 0 1rem;
margin-left: .3rem;
margin-top: 10px;
margin-bottom: 10px;
font-size: 1em;
}
a {
text-decoration-color: var(--footer-color);
}
a:hover {
text-decoration-color: var(--link-color);
}
pre {
padding: 5px 5px;
overflow-x: auto;
scrollbar-width: thin;
font-size: small;
margin-bottom: 20px;
}
hr {
margin-bottom: 20px;
}
.footnotes {
border-top: solid 2px var(--border-color);
font-size: small;
color: var(--footnotes-color);
}
figure {
margin: 1em 0;
}
figcaption {
color: var(--post-meta-color);
font-size: small;
text-align: center;
margin-top: 3px;
margin-bottom: 10px;
}
ul>li {
list-style-type: disc;
list-style-position: inside;
}
img {
max-height: 600px;
object-fit: contain;
}

860
static/css/tailwind.css Normal file
View File

@ -0,0 +1,860 @@
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
/*
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden]:where(:not([hidden="until-found"])) {
display: none;
}
body {
display: flex;
flex-direction: column;
min-height: 100svh;
background-color: var(--background-color);
color: var(--color);
font: 16px/1.85 Roboto, sans-serif;
overflow-y: scroll;
/* FIXME: border-color transition does not work */
/* transition: background-color 1s ease-in-out, border-color 1s ease-in-out; */
}
a {
text-decoration-color: var(--text-decoration-color);
}
a:hover {
color: var(--link-color);
text-decoration-color: var(--link-color);
}
hr {
border-color: var(--border-color);
margin-bottom: 20px;
}
/* roboto-regular - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: url(/fonts/Roboto/roboto-v30-latin-regular.eot);
/* IE9 Compat Modes */
src: url(/fonts/Roboto/roboto-v30-latin-regular.eot?#iefix) format('embedded-opentype'),
/* IE6-IE8 */
url(/fonts/Roboto/roboto-v30-latin-regular.woff2) format('woff2'),
/* Super Modern Browsers */
url(/fonts/Roboto/roboto-v30-latin-regular.woff) format('woff'),
/* Modern Browsers */
url(/fonts/Roboto/roboto-v30-latin-regular.ttf) format('truetype'),
/* Safari, Android, iOS */
url(/fonts/Roboto/roboto-v30-latin-regular.svg#Roboto) format('svg');
/* Legacy iOS */
}
li {
list-style-type: auto;
list-style-position: inside;
}
li>p {
display: inline;
}
blockquote {
color: var(--quote-color);
opacity: .9;
border-left: 5px solid var(--quote-border-color);
padding: 0 0 0 1rem;
margin-left: .3rem;
margin-top: 10px;
margin-bottom: 10px;
font-size: 1em;
}
pre {
padding: 5px 5px;
overflow-x: auto;
scrollbar-width: thin;
font-size: small;
margin-bottom: 20px;
}
figure {
margin: 1em 0;
}
figcaption {
color: var(--post-meta-color);
font-size: small;
text-align: center;
margin-top: 3px;
margin-bottom: 10px;
}
ul>li {
list-style-type: disc;
list-style-position: inside;
}
img {
max-height: 600px;
-o-object-fit: contain;
object-fit: contain;
}
figure:has(iframe) {
position: relative;
padding-bottom: 56.25% !important;
height: 0;
overflow: hidden;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0
}
.container {
width: 100%;
margin-right: auto;
margin-left: auto;
padding-right: 1rem;
padding-left: 1rem;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
.static {
position: static;
}
.fixed {
position: fixed;
}
.absolute {
position: absolute;
}
.float-right {
float: right;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.my-3 {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.my-auto {
margin-top: auto;
margin-bottom: auto;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.block {
display: block;
}
.flex {
display: flex;
}
.table {
display: table;
}
.\!hidden {
display: none !important;
}
.hidden {
display: none;
}
.grow {
flex-grow: 1;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.px-1 {
padding-left: 0.25rem;
padding-right: 0.25rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.pt-1 {
padding-top: 0.25rem;
}
.text-center {
text-align: center;
}
.leading-relaxed {
line-height: 1.625;
}
.underline {
text-decoration-line: underline;
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
:root {
--background-color: #131418;
--color: #babdc4;
--border-color: #1b1d25;
--link-color: #77a8fd;
--text-decoration-color: #767f87;
--post-meta-color: #767f87;
--post-header-color: #eaeaea;
--quote-color: #9b9ba3;
--quote-border-color: #4a4d56;
--footer-color: #767f87;
--footnotes-color: #767f87;
}
@media (min-width: 640px) {
.sm\:px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
}

1295
static/fonts/slant.flf Normal file

File diff suppressed because it is too large Load Diff

View File

@ -484,19 +484,19 @@ hr {
font-weight: 400;
src: url(fonts/Roboto/roboto-v30-latin-regular.eot);
src: url(/fonts/Roboto/roboto-v30-latin-regular.eot);
/* IE9 Compat Modes */
src: url(fonts/Roboto/roboto-v30-latin-regular.eot?#iefix) format('embedded-opentype'),
src: url(/fonts/Roboto/roboto-v30-latin-regular.eot?#iefix) format('embedded-opentype'),
/* IE6-IE8 */
url(fonts/Roboto/roboto-v30-latin-regular.woff2) format('woff2'),
url(/fonts/Roboto/roboto-v30-latin-regular.woff2) format('woff2'),
/* Super Modern Browsers */
url(fonts/Roboto/roboto-v30-latin-regular.woff) format('woff'),
url(/fonts/Roboto/roboto-v30-latin-regular.woff) format('woff'),
/* Modern Browsers */
url(fonts/Roboto/roboto-v30-latin-regular.ttf) format('truetype'),
url(/fonts/Roboto/roboto-v30-latin-regular.ttf) format('truetype'),
/* Safari, Android, iOS */
url(fonts/Roboto/roboto-v30-latin-regular.svg#Roboto) format('svg');
url(/fonts/Roboto/roboto-v30-latin-regular.svg#Roboto) format('svg');
/* Legacy iOS */
}

View File

@ -1,31 +1,30 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
darkMode: 'selector',
content: ["layouts/**/*.html"],
theme: {
container: {
center: true,
padding: '1rem'
mode: 'jit',
darkMode: 'selector',
content: ["public/**/*.html"],
theme: {
container: {
center: true,
padding: '1rem'
},
borderWidth: {
'1': '1px'
},
extend: {},
},
borderWidth: {
'1': '1px'
},
extend: {},
},
plugins: [
function ({ addComponents }) {
addComponents({
'.container': {
'@screen lg': {
maxWidth: '1280px',
},
'@screen xl': {
maxWidth: '1280px',
},
}
})
}
],
}
plugins: [
function ({ addComponents }) {
addComponents({
'.container': {
'@screen lg': {
maxWidth: '1280px',
},
'@screen xl': {
maxWidth: '1280px',
},
}
})
}
],
}