diff --git a/.gitmodules b/.gitmodules index 9502d96..4d81b35 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "themes/hugo-theme-terminal"] - path = themes/hugo-theme-terminal - url = https://forge.alexselimov.com/aselimov/Terminal-Hugo-Theme.git +[submodule "themes/hugo-bearcub"] + path = themes/hugo-bearcub + url = https://github.com/clente/hugo-bearcub diff --git a/assets/auto-theme.css b/assets/auto-theme.css new file mode 100644 index 0000000..37a5355 --- /dev/null +++ b/assets/auto-theme.css @@ -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; + } + +} diff --git a/content/_index.md b/content/_index.md index 5b3d9ee..fe664fb 100644 --- a/content/_index.md +++ b/content/_index.md @@ -1,21 +1,14 @@ --- title: "" -framed: true date: 2022-10-03T14:17:10-04:00 layout: index --- +# Hi I'm Alex Selimov. -
-     _    _             ____       _ _                      
-    / \  | | _____  __ / ___|  ___| (_)_ __ ___   _____   __
-   / _ \ | |/ _ \ \/ / \___ \ / _ \ | | '_ ` _ \ / _ \ \ / /
-  / ___ \| |  __/>  <   ___) |  __/ | | | | | | | (_) \ V /
- /_/   \_\_|\___/_/\_\ |____/ \___|_|_|_| |_| |_|\___/ \_/
-
+I write about software engineering, Linux, and practical workflows. +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)** - -**Thanks for stopping by!** +You can also browse posts by topic [here](/topics). diff --git a/content/about.md b/content/about.md index e957768..d85d80e 100644 --- a/content/about.md +++ b/content/about.md @@ -1,21 +1,10 @@ -+++ -title = "About" -date = "2019-01-25" -author = "Radek" -+++ +--- +title: "About" +date: 2026-03-02 +--- -# 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. - -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! +I write here about practical engineering lessons, tools I use, and projects I build. diff --git a/content/projects.md b/content/projects.md new file mode 100644 index 0000000..7af14bf --- /dev/null +++ b/content/projects.md @@ -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. diff --git a/hugo.toml b/hugo.toml index 80318f5..55295b4 100644 --- a/hugo.toml +++ b/hugo.toml @@ -1,58 +1,73 @@ -baseurl = "https://www.alexselimov.com/" -languageCode = "en-us" -theme = "hugo-theme-terminal" -pagination.pagerSize = 5 +baseURL = "https://www.alexselimov.com/" +languageCode = "en-US" +theme = "hugo-bearcub" +defaultContentLanguage = "en" +copyright = "© Alex Selimov" +enableRobotsTXT = true -[markup.goldmark.renderer] -unsafe = true +[pagination] +pagerSize = 5 -[params] -contentTypeName = "posts" -showMenuItems = 3 -postsToShowOnIndex = 3 -fullWidthTheme = false -centerTheme = true +[markup] + [markup.goldmark.renderer] + unsafe = true + [markup.highlight] + lineNos = true + lineNumbersInTable = false + noClasses = false [taxonomies] -tag = "tags" -topic = "topics" + tag = "tags" + topic = "topics" [languages] + [languages.en] + title = "Alex Selimov" + languageName = "en-US" + languageCode = "en-US" + [languages.en.menu] + [[languages.en.menu.main]] + identifier = "home" + name = "Home" + url = "/" + weight = 1 + [[languages.en.menu.main]] + identifier = "projects" + name = "Projects" + url = "/projects/" + weight = 2 + [[languages.en.menu.main]] + identifier = "about" + name = "About" + url = "/about/" + weight = 3 -[languages.en.params] -title = "Alex Selimov" -subtitle = "" -keywords = "" -copyright = "© Alex Selimov" -menuMore = "Show more" -readMore = "Read more" -readOtherPosts = "Read other posts" +[params] + 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 -[languages.en.params.logo] -logoText = "Alex Selimov" -logoHomeLink = "/" + [[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/" -[languages.en.menu] -[[languages.en.menu.main]] -identifier = "cv" -name = "[CV]" -url = "/cv" -weight = 1 -[[languages.en.menu.main]] -identifier = "git" -name = "[Git]" -url = "https://forge.alexselimov.com/aselimov" -weight = 1 -[[languages.en.menu.main]] -identier = "linkedIn" -name = "[LinkedIn]" -url = "https://www.linkedin.com/in/alex-selimov/" -weight = 2 -[[languages.en.menu.main]] -identier = "Scholar" -name = "[Google Scholar]" -url = "https://scholar.google.com/citations?user=w6unVk8AAAAJ&hl=en&oi=ao" -[[languages.en.menu.main]] -identifier = "gpg" -name = "[GPG public key]" -url = "./public.key" + [params.author] + name = "Alex Selimov" diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..833cae1 --- /dev/null +++ b/layouts/index.html @@ -0,0 +1,19 @@ +{{ define "main" }} + + {{ .Content }} + +

My most recent posts

+ + +

All posts →

+ +
+{{ end }} diff --git a/layouts/partials/custom_head.html b/layouts/partials/custom_head.html new file mode 100644 index 0000000..74a4f70 --- /dev/null +++ b/layouts/partials/custom_head.html @@ -0,0 +1,2 @@ +{{ $autoTheme := resources.Get "auto-theme.css" | minify }} + diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html new file mode 100644 index 0000000..e0d702a --- /dev/null +++ b/layouts/partials/footer.html @@ -0,0 +1,4 @@ + diff --git a/layouts/partials/header.html b/layouts/partials/header.html new file mode 100644 index 0000000..804be55 --- /dev/null +++ b/layouts/partials/header.html @@ -0,0 +1,6 @@ + + +

{{ .Site.Title }}

+ diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html new file mode 100644 index 0000000..5a7ea58 --- /dev/null +++ b/layouts/partials/nav.html @@ -0,0 +1,4 @@ +{{ range .Site.Menus.main.ByWeight }} + {{ .Name }} +{{ end }} +RSS diff --git a/layouts/shortcodes/code.html b/layouts/shortcodes/code.html new file mode 100644 index 0000000..47be546 --- /dev/null +++ b/layouts/shortcodes/code.html @@ -0,0 +1,6 @@ +{{ .Page.Store.Set "hasCodeBlock" true }} +{{ $lang := .Get "language" | default "" }} +{{ with .Get "title" }} +

{{ . }}

+{{ end }} +{{ highlight (trim .Inner "\r\n") $lang "" }} diff --git a/scripts/generate_triangulated_mesh.py b/scripts/generate_triangulated_mesh.py new file mode 100644 index 0000000..fa36ce1 --- /dev/null +++ b/scripts/generate_triangulated_mesh.py @@ -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'' + ) + + +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'') + + svg = "\n".join( + [ + '', + f'', + f'', + *elements, + *circles, + "", + ] + ) + + 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() diff --git a/static/mesh.svg b/static/mesh.svg new file mode 100644 index 0000000..a2cc8d6 --- /dev/null +++ b/static/mesh.svg @@ -0,0 +1,2058 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/themes/hugo-bearcub b/themes/hugo-bearcub new file mode 160000 index 0000000..1d12a76 --- /dev/null +++ b/themes/hugo-bearcub @@ -0,0 +1 @@ +Subproject commit 1d12a76549445b767fa02902caf30cec7ceaecf9 diff --git a/themes/hugo-theme-terminal b/themes/hugo-theme-terminal deleted file mode 160000 index 7cd0b57..0000000 --- a/themes/hugo-theme-terminal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7cd0b57a0276349b9f107afb48f692e72f0d931a