Merge branch 'v2' into develop
This commit is contained in:
commit
19552799ab
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1 @@
|
||||
# hugo
|
||||
public/
|
||||
resources/
|
||||
.hugo_build.lock
|
||||
|
||||
8
Caddyfile
Normal file
8
Caddyfile
Normal file
@ -0,0 +1,8 @@
|
||||
:8080 {
|
||||
root * public
|
||||
file_server
|
||||
handle_errors 404 {
|
||||
rewrite * /404.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
```
|
||||
@ -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>
|
||||
@ -1,5 +0,0 @@
|
||||
+++
|
||||
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
date = {{ .Date }}
|
||||
draft = true
|
||||
+++
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
3925
assets/js/htmx.js
3925
assets/js/htmx.js
File diff suppressed because it is too large
Load Diff
@ -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
416
content.go
Normal 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
|
||||
}
|
||||
@ -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 >}}
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
_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_
|
||||

|
||||
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---------
|
||||
|
||||
|
||||
@ -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 >}}
|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -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
|
||||
---
|
||||
|
||||
|
||||
@ -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" >}}
|
||||

|
||||
The Stacker News Door
|
||||
|
||||
* supertestnet introducing me to everyone; ek vs ek-zee-kee-yas
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Between Moments of Wonder and Euphoria
|
||||
date: 2024-07-13
|
||||
banner: landscape.jpg
|
||||
banner: landscape.webp
|
||||
sn_id: 607554
|
||||
---
|
||||
|
||||
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 365 KiB |
@ -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):
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 didn’t 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" >}}
|
||||

|
||||
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.
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||

|
||||

|
||||
|
||||
_ever since [@anon](https://stacker.news/anon) shared this with me in, I like to mention this map wherever I can._
|
||||
|
||||
|
||||
@ -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" >}}
|
||||

|
||||
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).
|
||||
|
||||
|
||||
@ -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 >}}
|
||||

|
||||
|
||||
@ -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
|
||||
|
||||
@ -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?" >}}
|
||||

|
||||
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" >}}
|
||||

|
||||
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`.
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ date: 2024-01-23
|
||||
sn_id: 398641
|
||||
---
|
||||
|
||||
{{< youtube 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)._
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
The NAT gateway then stores this replacement in a NAT table:
|
||||
|
||||

|
||||

|
||||
|
||||
For arriving packets, this table is consulted to reverse the translation:
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
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
19
deploy
@ -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
61
flake.lock
generated
Normal 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
35
flake.nix
Normal 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
20
go.mod
Normal 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
44
go.sum
Normal 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
20
html/404.html
Normal 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
15
html/index.html
Normal 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
11
html/post.html
Normal 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
28
html/template/footer.html
Normal 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
12
html/template/head.html
Normal 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
14
html/template/nav.html
Normal 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 }}
|
||||
10
html/template/post/list.html
Normal file
10
html/template/post/list.html
Normal 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 }}
|
||||
34
html/template/post/single.html
Normal file
34
html/template/post/single.html
Normal 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 }}
|
||||
28
hugo.toml
28
hugo.toml
@ -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
132
input.css
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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}}
|
||||
@ -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
2
server/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
# ignore files generated by `templ generate`
|
||||
*_templ.go
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
@ -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=
|
||||
@ -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)
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs.buildPackages;
|
||||
[
|
||||
go hugo tailwindcss
|
||||
];
|
||||
}
|
||||
@ -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
860
static/css/tailwind.css
Normal 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
1295
static/fonts/slant.flf
Normal file
File diff suppressed because it is too large
Load Diff
@ -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 */
|
||||
}
|
||||
|
||||
@ -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',
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user