Refactor site to new theme
Some checks failed
Build and Deploy Hugo / deploy (push) Failing after 20s
Some checks failed
Build and Deploy Hugo / deploy (push) Failing after 20s
This commit is contained in:
parent
1c5cdb3b97
commit
2519b96a86
16 changed files with 2590 additions and 83 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
[submodule "themes/hugo-theme-terminal"]
|
[submodule "themes/hugo-bearcub"]
|
||||||
path = themes/hugo-theme-terminal
|
path = themes/hugo-bearcub
|
||||||
url = https://forge.alexselimov.com/aselimov/Terminal-Hugo-Theme.git
|
url = https://github.com/clente/hugo-bearcub
|
||||||
|
|
|
||||||
156
assets/auto-theme.css
Normal file
156
assets/auto-theme.css
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
:root {
|
||||||
|
--size: 0.96rem;
|
||||||
|
--spacing: calc(var(--size) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Nunito", "Avenir Next", "Avenir", "Segoe UI", "Helvetica Neue",
|
||||||
|
Helvetica, Arial, sans-serif;
|
||||||
|
font-size: calc(var(--size) * 1.6);
|
||||||
|
line-height: 1.45;
|
||||||
|
padding: 0 2.8rem 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header,
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
max-width: 66ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
header,
|
||||||
|
footer {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mesh-wrap {
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
width: 100vw;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mesh-image {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mesh-wrap-top {
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mesh-image-top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copy {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(var(--size) * 3.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(var(--size) * 2.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title h1 {
|
||||||
|
font-size: calc(var(--size) * 2.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a,
|
||||||
|
a.blog-tags {
|
||||||
|
margin-right: calc(var(--spacing) / 2.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a:focus-visible {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.blog-posts li {
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-block-start: calc(var(--spacing) / 2.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1f2328;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
strong,
|
||||||
|
b {
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0969da;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited,
|
||||||
|
ul.blog-posts li a:visited {
|
||||||
|
color: #8250df;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea,
|
||||||
|
input {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
color: #1f2328;
|
||||||
|
border: 1px solid #d0d7de;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border-color: #d0d7de;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: #24292f;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left-color: #8c959f;
|
||||||
|
color: #57606a;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption > p,
|
||||||
|
.helptext {
|
||||||
|
color: #57606a;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,14 @@
|
||||||
---
|
---
|
||||||
title: ""
|
title: ""
|
||||||
framed: true
|
|
||||||
date: 2022-10-03T14:17:10-04:00
|
date: 2022-10-03T14:17:10-04:00
|
||||||
layout: index
|
layout: index
|
||||||
---
|
---
|
||||||
|
# Hi I'm Alex Selimov.
|
||||||
|
|
||||||
<pre class="figlet-banner">
|
I write about software engineering, Linux, and practical workflows.
|
||||||
_ _ ____ _ _
|
|
||||||
/ \ | | _____ __ / ___| ___| (_)_ __ ___ _____ __
|
|
||||||
/ _ \ | |/ _ \ \/ / \___ \ / _ \ | | '_ ` _ \ / _ \ \ / /
|
|
||||||
/ ___ \| | __/> < ___) | __/ | | | | | | | (_) \ V /
|
|
||||||
/_/ \_\_|\___/_/\_\ |____/ \___|_|_|_| |_| |_|\___/ \_/
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
|
I currently work at [UKG](https://www.ukg.com) as a Java Backend Engineer.
|
||||||
|
|
||||||
I am a materials scientist and software developer proficient in Rust, Fortran, C++, Java, and Python. I have extensive experience with Unix operating systems on both high performance computing resources and on my personal computer (check out [Void Linux](https://voidlinux.org)). I currently work at [UKG](https://www.ukg.com) as a Java Backend Engineer.
|
My background is in materials science and scientific computing, and I build in Rust, Java, Python, C++, and Fortran.
|
||||||
|
|
||||||
**Checkout recent posts below or posts organized by topic [here](/topics)**
|
You can also browse posts by topic [here](/topics).
|
||||||
|
|
||||||
**Thanks for stopping by!**
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
+++
|
---
|
||||||
title = "About"
|
title: "About"
|
||||||
date = "2019-01-25"
|
date: 2026-03-02
|
||||||
author = "Radek"
|
---
|
||||||
+++
|
|
||||||
|
|
||||||
# Hi there
|
I am a software engineer with a background in materials science and scientific computing.
|
||||||
|
|
||||||
My name is Radek and I'm the author of this theme. I made it to help you present your ideas easier.
|
I currently work at UKG as a Java Backend Engineer. My interests span backend systems, developer tooling, Linux workflows, and performance-oriented programming.
|
||||||
|
|
||||||
We all know how hard is to start something on the web, especially these days. You need to prepare a bunch of stuff, configure them and when that’s done — create the content.
|
I write here about practical engineering lessons, tools I use, and projects I build.
|
||||||
|
|
||||||
This theme is pretty basic and covers all of the essentials. All you have to do is start typing!
|
|
||||||
|
|
||||||
The theme includes:
|
|
||||||
|
|
||||||
- fully customizable color schemes generated by [**terminal.css**](https://panr.github.io/terminal-css/).
|
|
||||||
- great reading experience thanks to [**Fira Code**](https://github.com/tonsky/FiraCode).
|
|
||||||
- nice code highlighting thanks to [**PrismJS**](https://prismjs.com) that matches the theme's color scheme.
|
|
||||||
|
|
||||||
So, there you have it... enjoy!
|
|
||||||
|
|
|
||||||
20
content/projects.md
Normal file
20
content/projects.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
title: "Projects"
|
||||||
|
date: 2026-03-02
|
||||||
|
---
|
||||||
|
|
||||||
|
This page highlights selected projects across software engineering and technical research.
|
||||||
|
|
||||||
|
## Current Focus
|
||||||
|
|
||||||
|
- Backend and platform engineering with Java and distributed systems.
|
||||||
|
- Rust-based tools and utilities for local workflows.
|
||||||
|
- Practical automation and scripting on Linux.
|
||||||
|
|
||||||
|
## Selected Work
|
||||||
|
|
||||||
|
- Open source and personal utilities.
|
||||||
|
- Technical writing and implementation notes.
|
||||||
|
- Simulation and scientific computing work from prior research roles.
|
||||||
|
|
||||||
|
More detailed project write-ups will be added here over time.
|
||||||
99
hugo.toml
99
hugo.toml
|
|
@ -1,58 +1,73 @@
|
||||||
baseurl = "https://www.alexselimov.com/"
|
baseURL = "https://www.alexselimov.com/"
|
||||||
languageCode = "en-us"
|
languageCode = "en-US"
|
||||||
theme = "hugo-theme-terminal"
|
theme = "hugo-bearcub"
|
||||||
pagination.pagerSize = 5
|
defaultContentLanguage = "en"
|
||||||
|
copyright = "© Alex Selimov"
|
||||||
|
enableRobotsTXT = true
|
||||||
|
|
||||||
|
[pagination]
|
||||||
|
pagerSize = 5
|
||||||
|
|
||||||
|
[markup]
|
||||||
[markup.goldmark.renderer]
|
[markup.goldmark.renderer]
|
||||||
unsafe = true
|
unsafe = true
|
||||||
|
[markup.highlight]
|
||||||
[params]
|
lineNos = true
|
||||||
contentTypeName = "posts"
|
lineNumbersInTable = false
|
||||||
showMenuItems = 3
|
noClasses = false
|
||||||
postsToShowOnIndex = 3
|
|
||||||
fullWidthTheme = false
|
|
||||||
centerTheme = true
|
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tag = "tags"
|
tag = "tags"
|
||||||
topic = "topics"
|
topic = "topics"
|
||||||
|
|
||||||
[languages]
|
[languages]
|
||||||
|
[languages.en]
|
||||||
[languages.en.params]
|
|
||||||
title = "Alex Selimov"
|
title = "Alex Selimov"
|
||||||
subtitle = ""
|
languageName = "en-US"
|
||||||
keywords = ""
|
languageCode = "en-US"
|
||||||
copyright = "© Alex Selimov"
|
|
||||||
menuMore = "Show more"
|
|
||||||
readMore = "Read more"
|
|
||||||
readOtherPosts = "Read other posts"
|
|
||||||
|
|
||||||
[languages.en.params.logo]
|
|
||||||
logoText = "Alex Selimov"
|
|
||||||
logoHomeLink = "/"
|
|
||||||
|
|
||||||
[languages.en.menu]
|
[languages.en.menu]
|
||||||
[[languages.en.menu.main]]
|
[[languages.en.menu.main]]
|
||||||
identifier = "cv"
|
identifier = "home"
|
||||||
name = "[CV]"
|
name = "Home"
|
||||||
url = "/cv"
|
url = "/"
|
||||||
weight = 1
|
weight = 1
|
||||||
[[languages.en.menu.main]]
|
[[languages.en.menu.main]]
|
||||||
identifier = "git"
|
identifier = "projects"
|
||||||
name = "[Git]"
|
name = "Projects"
|
||||||
url = "https://forge.alexselimov.com/aselimov"
|
url = "/projects/"
|
||||||
weight = 1
|
|
||||||
[[languages.en.menu.main]]
|
|
||||||
identier = "linkedIn"
|
|
||||||
name = "[LinkedIn]"
|
|
||||||
url = "https://www.linkedin.com/in/alex-selimov/"
|
|
||||||
weight = 2
|
weight = 2
|
||||||
[[languages.en.menu.main]]
|
[[languages.en.menu.main]]
|
||||||
identier = "Scholar"
|
identifier = "about"
|
||||||
name = "[Google Scholar]"
|
name = "About"
|
||||||
url = "https://scholar.google.com/citations?user=w6unVk8AAAAJ&hl=en&oi=ao"
|
url = "/about/"
|
||||||
[[languages.en.menu.main]]
|
weight = 3
|
||||||
identifier = "gpg"
|
|
||||||
name = "[GPG public key]"
|
[params]
|
||||||
url = "./public.key"
|
description = "Alex Selimov personal site and blog."
|
||||||
|
title = "Alex Selimov"
|
||||||
|
favicon = "favicon.png"
|
||||||
|
images = ["og-image.png"]
|
||||||
|
dateFormat = "2006-01-02"
|
||||||
|
hideUntranslated = false
|
||||||
|
themeStyle = "herman"
|
||||||
|
generateSocialCard = true
|
||||||
|
postsToShowOnIndex = 5
|
||||||
|
|
||||||
|
[[params.popularPosts]]
|
||||||
|
title = "Say goodbye to st and say hello to ghostty"
|
||||||
|
url = "/posts/st_to_ghostty/"
|
||||||
|
[[params.popularPosts]]
|
||||||
|
title = "Getting CUDA toolkit installed on Void Linux"
|
||||||
|
url = "/posts/cuda_on_void/"
|
||||||
|
[[params.popularPosts]]
|
||||||
|
title = "Rust is pretty good (Short thoughts on Rust)"
|
||||||
|
url = "/posts/thoughts_on_rust/"
|
||||||
|
[[params.popularPosts]]
|
||||||
|
title = "My nvim/tmux workflow"
|
||||||
|
url = "/posts/tmux_and_nvim/"
|
||||||
|
[[params.popularPosts]]
|
||||||
|
title = "Fighting the web obesity crisis using Hugo and Skeleton CSS"
|
||||||
|
url = "/posts/web_obesity/"
|
||||||
|
|
||||||
|
[params.author]
|
||||||
|
name = "Alex Selimov"
|
||||||
|
|
|
||||||
19
layouts/index.html
Normal file
19
layouts/index.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<content>
|
||||||
|
{{ .Content }}
|
||||||
|
|
||||||
|
<h2>My most recent posts</h2>
|
||||||
|
<ul class="blog-posts">
|
||||||
|
{{ $max := default 3 .Site.Params.postsToShowOnIndex }}
|
||||||
|
{{ $posts := where .Site.RegularPages "Section" "posts" }}
|
||||||
|
{{ range first $max $posts.ByDate.Reverse }}
|
||||||
|
<li>
|
||||||
|
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><a href="/posts/">All posts →</a></p>
|
||||||
|
|
||||||
|
</content>
|
||||||
|
{{ end }}
|
||||||
2
layouts/partials/custom_head.html
Normal file
2
layouts/partials/custom_head.html
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
{{ $autoTheme := resources.Get "auto-theme.css" | minify }}
|
||||||
|
<link href="{{ $autoTheme.RelPermalink }}" rel="stylesheet">
|
||||||
4
layouts/partials/footer.html
Normal file
4
layouts/partials/footer.html
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div class="footer-copy">
|
||||||
|
{{ .Site.Copyright }}
|
||||||
|
{{ with .Site.Params.madeWith }} | {{ markdownify . }}{{ end }}
|
||||||
|
</div>
|
||||||
6
layouts/partials/header.html
Normal file
6
layouts/partials/header.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="mesh-wrap mesh-wrap-top" aria-hidden="true">
|
||||||
|
<img src="/mesh.svg" alt="" class="mesh-image mesh-image-top">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ relURL .Site.Home.RelPermalink }}" class="title"><h1>{{ .Site.Title }}</h1></a>
|
||||||
|
<nav>{{- partial "nav.html" . -}}</nav>
|
||||||
4
layouts/partials/nav.html
Normal file
4
layouts/partials/nav.html
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{{ range .Site.Menus.main.ByWeight }}
|
||||||
|
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
<a href='{{ absURL "index.xml" }}'>RSS</a>
|
||||||
6
layouts/shortcodes/code.html
Normal file
6
layouts/shortcodes/code.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{{ .Page.Store.Set "hasCodeBlock" true }}
|
||||||
|
{{ $lang := .Get "language" | default "" }}
|
||||||
|
{{ with .Get "title" }}
|
||||||
|
<p><strong>{{ . }}</strong></p>
|
||||||
|
{{ end }}
|
||||||
|
{{ highlight (trim .Inner "\r\n") $lang "" }}
|
||||||
235
scripts/generate_triangulated_mesh.py
Normal file
235
scripts/generate_triangulated_mesh.py
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate a triangulated mesh graphic as an SVG file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python scripts/generate_triangulated_mesh.py --width 1920 --height 1080 --output mesh.svg
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Point:
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
|
||||||
|
|
||||||
|
def clamp(value: float, low: float, high: float) -> float:
|
||||||
|
return max(low, min(high, value))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Generate a triangulated mesh SVG.")
|
||||||
|
parser.add_argument("--width", type=int, required=True, help="Graphic width in pixels.")
|
||||||
|
parser.add_argument("--height", type=int, required=True, help="Graphic height in pixels.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
default=Path("triangulated-mesh.svg"),
|
||||||
|
help="Output SVG path (default: triangulated-mesh.svg)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--cell-size",
|
||||||
|
type=float,
|
||||||
|
default=120.0,
|
||||||
|
help="Approximate grid cell size in pixels when x/y step are not set.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--x-step",
|
||||||
|
type=float,
|
||||||
|
default=None,
|
||||||
|
help="Horizontal point spacing in pixels (overrides --cell-size on x-axis).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--y-step",
|
||||||
|
type=float,
|
||||||
|
default=None,
|
||||||
|
help="Vertical point spacing in pixels (overrides --cell-size on y-axis).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--jitter",
|
||||||
|
type=float,
|
||||||
|
default=0.35,
|
||||||
|
help="Point jitter amount as a fraction of cell size (0.0-0.5).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--seed",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Random seed for reproducible output.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stroke",
|
||||||
|
type=float,
|
||||||
|
default=1.25,
|
||||||
|
help="Edge stroke width in pixels.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dot-radius",
|
||||||
|
type=float,
|
||||||
|
default=1.9,
|
||||||
|
help="Vertex dot radius in pixels.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--line-color",
|
||||||
|
type=str,
|
||||||
|
default="#334155",
|
||||||
|
help="Edge color as hex (default: #334155).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dot-color",
|
||||||
|
type=str,
|
||||||
|
default="#0f172a",
|
||||||
|
help="Dot color as hex (default: #0f172a).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--background",
|
||||||
|
type=str,
|
||||||
|
default="#f8fafc",
|
||||||
|
help="Background color as hex (default: #f8fafc).",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def build_points(
|
||||||
|
width: int,
|
||||||
|
height: int,
|
||||||
|
x_step: float,
|
||||||
|
y_step: float,
|
||||||
|
jitter_frac: float,
|
||||||
|
rng: random.Random,
|
||||||
|
) -> list[list[Point]]:
|
||||||
|
cols = max(2, math.ceil(width / x_step) + 1)
|
||||||
|
rows = max(2, math.ceil(height / y_step) + 1)
|
||||||
|
|
||||||
|
points: list[list[Point]] = []
|
||||||
|
jitter = clamp(jitter_frac, 0.0, 0.5) * min(x_step, y_step)
|
||||||
|
|
||||||
|
for row in range(rows):
|
||||||
|
y = (height * row) / (rows - 1)
|
||||||
|
row_points: list[Point] = []
|
||||||
|
for col in range(cols):
|
||||||
|
x = (width * col) / (cols - 1)
|
||||||
|
|
||||||
|
# Keep border anchored to make the mesh fill the canvas cleanly.
|
||||||
|
if 0 < row < rows - 1 and 0 < col < cols - 1:
|
||||||
|
x += rng.uniform(-jitter, jitter)
|
||||||
|
y_jittered = y + rng.uniform(-jitter, jitter)
|
||||||
|
else:
|
||||||
|
y_jittered = y
|
||||||
|
|
||||||
|
row_points.append(Point(x=clamp(x, 0.0, float(width)), y=clamp(y_jittered, 0.0, float(height))))
|
||||||
|
points.append(row_points)
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def generate_triangles(points: list[list[Point]], rng: random.Random) -> list[tuple[Point, Point, Point]]:
|
||||||
|
triangles: list[tuple[Point, Point, Point]] = []
|
||||||
|
rows = len(points)
|
||||||
|
cols = len(points[0]) if rows else 0
|
||||||
|
|
||||||
|
for row in range(rows - 1):
|
||||||
|
for col in range(cols - 1):
|
||||||
|
p00 = points[row][col]
|
||||||
|
p10 = points[row][col + 1]
|
||||||
|
p01 = points[row + 1][col]
|
||||||
|
p11 = points[row + 1][col + 1]
|
||||||
|
|
||||||
|
if rng.random() < 0.5:
|
||||||
|
triangles.append((p00, p10, p11))
|
||||||
|
triangles.append((p00, p11, p01))
|
||||||
|
else:
|
||||||
|
triangles.append((p00, p10, p01))
|
||||||
|
triangles.append((p10, p11, p01))
|
||||||
|
|
||||||
|
return triangles
|
||||||
|
|
||||||
|
|
||||||
|
def svg_polygon(points: tuple[Point, Point, Point], stroke: str, stroke_width: float) -> str:
|
||||||
|
pts = " ".join(f"{p.x:.2f},{p.y:.2f}" for p in points)
|
||||||
|
return (
|
||||||
|
f'<polygon points="{pts}" fill="none" '
|
||||||
|
f'stroke="{stroke}" stroke-width="{stroke_width:.3f}" stroke-linejoin="round" />'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_svg(
|
||||||
|
width: int,
|
||||||
|
height: int,
|
||||||
|
triangles: list[tuple[Point, Point, Point]],
|
||||||
|
stroke_width: float,
|
||||||
|
dot_radius: float,
|
||||||
|
line_color: str,
|
||||||
|
dot_color: str,
|
||||||
|
background: str,
|
||||||
|
output: Path,
|
||||||
|
points: list[list[Point]],
|
||||||
|
) -> None:
|
||||||
|
elements: list[str] = []
|
||||||
|
|
||||||
|
for tri in triangles:
|
||||||
|
elements.append(svg_polygon(tri, stroke=line_color, stroke_width=stroke_width))
|
||||||
|
|
||||||
|
circles: list[str] = []
|
||||||
|
for row in points:
|
||||||
|
for p in row:
|
||||||
|
circles.append(f'<circle cx="{p.x:.2f}" cy="{p.y:.2f}" r="{dot_radius:.2f}" fill="{dot_color}" />')
|
||||||
|
|
||||||
|
svg = "\n".join(
|
||||||
|
[
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
|
||||||
|
f'<rect x="0" y="0" width="100%" height="100%" fill="{background}" />',
|
||||||
|
*elements,
|
||||||
|
*circles,
|
||||||
|
"</svg>",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output.write_text(svg, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if args.width <= 0 or args.height <= 0:
|
||||||
|
raise SystemExit("--width and --height must be positive integers")
|
||||||
|
|
||||||
|
if args.cell_size <= 1:
|
||||||
|
raise SystemExit("--cell-size must be > 1")
|
||||||
|
|
||||||
|
x_step = args.x_step if args.x_step is not None else args.cell_size
|
||||||
|
y_step = args.y_step if args.y_step is not None else args.cell_size
|
||||||
|
|
||||||
|
if x_step <= 1 or y_step <= 1:
|
||||||
|
raise SystemExit("--x-step and --y-step must be > 1")
|
||||||
|
|
||||||
|
rng = random.Random(args.seed)
|
||||||
|
points = build_points(args.width, args.height, x_step, y_step, args.jitter, rng)
|
||||||
|
triangles = generate_triangles(points, rng)
|
||||||
|
write_svg(
|
||||||
|
args.width,
|
||||||
|
args.height,
|
||||||
|
triangles,
|
||||||
|
args.stroke,
|
||||||
|
args.dot_radius,
|
||||||
|
args.line_color,
|
||||||
|
args.dot_color,
|
||||||
|
args.background,
|
||||||
|
args.output,
|
||||||
|
points,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Generated {len(triangles)} triangles -> {args.output}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
2058
static/mesh.svg
Normal file
2058
static/mesh.svg
Normal file
File diff suppressed because it is too large
Load diff
|
After Width: | Height: | Size: 214 KiB |
1
themes/hugo-bearcub
Submodule
1
themes/hugo-bearcub
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1d12a76549445b767fa02902caf30cec7ceaecf9
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 7cd0b57a0276349b9f107afb48f692e72f0d931a
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue