5 Commits

Author SHA1 Message Date
1d157b529f Improve on profile 2025-09-19 20:53:00 +02:00
3e3c209ac9 Finish styling index 2025-09-19 20:28:21 +02:00
4e68f393bd Links 2025-09-19 19:45:22 +02:00
fcba900a7e add fetchpriority 2025-09-19 15:37:35 +02:00
0478cb71b9 wip redesign 2025-09-18 14:44:35 +02:00
25 changed files with 1540 additions and 3920 deletions

View File

@@ -1,10 +0,0 @@
module.exports = {
port: 3000,
server: "./dist",
files: "./dist/**/*",
watchOptions: {
usePolling: true,
interval: 200,
binaryInterval: 200
}
}

View File

@@ -1,22 +0,0 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files:
["**/*.{js,mjs,cjs,ts,mts,cts}"],
plugins: {
js
},
extends: ["js/recommended"],
languageOptions: {
globals: globals.browser
},
rules: {
"indent": ["error", 2]
}
},
tseslint.configs.recommended,
]);

3179
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,9 +6,9 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "concurrently --kill-others -n Build,Serve -c auto \"npm run watch-build\" \"npm run serve\"", "dev": "concurrently --kill-others -n Build,Serve -c auto \"npm run watch-build\" \"npm run serve\"",
"watch-build": "nodemon --ext js,ts,json,md,html --watch notes --watch src --watch templates --watch packages/@zblog/toolchain/src --watch markdown.ts --exec \"npm run build --workspaces && npm run build && npm run generate-blog\"", "watch-build": "nodemon --ext js,ts,json,md,html --watch notes --watch src --watch packages/@zblog/toolchain/src --watch markdown.ts --exec \"npm run build --workspaces && npm run build && npm run generate-blog\"",
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"serve": "browser-sync start --config bs-config.cjs", "serve": "serve dist",
"generate-blog": "node bin/index.js", "generate-blog": "node bin/index.js",
"initialize-firebase": "npm install -g firebase-tools && firebase login", "initialize-firebase": "npm install -g firebase-tools && firebase login",
"deploy": "npm run generate-blog && firebase deploy" "deploy": "npm run generate-blog && firebase deploy"
@@ -19,14 +19,10 @@
"author": "Wholteza (Zackarias Montell)", "author": "Wholteza (Zackarias Montell)",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^20.11.5", "@types/node": "^20.11.5",
"browser-sync": "^3.0.4",
"concurrently": "^8.2.1", "concurrently": "^8.2.1",
"eslint": "^10.0.3",
"globals": "^17.4.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"typescript": "^5.3.3", "serve": "^14.2.1",
"typescript-eslint": "^8.57.0" "typescript": "^5.3.3"
} }
} }

View File

@@ -19,24 +19,23 @@ export class FileLoader implements IPluginBuilder {
const { menuManifest } = builderContext; const { menuManifest } = builderContext;
const { files } = this.options; const { files } = this.options;
files.forEach((file) => { files.forEach((file) => {
const filePath = file.path.split("/").pop() ?? file.path; const fileName =
const fileName = file.nameOverride ?? filePath; file.nameOverride ?? file.path.split("/").pop() ?? file.path;
const isImage = fileName.match(/\.(png|jpe?g|gif|webp)$/); const isImage = fileName.match(/\.(png|jpe?g|gif|webp)$/);
if (isImage) { if (isImage) {
console.log("Processing image", file.path); console.log("Processing image", file.path);
ffmpeg(file.path) ffmpeg(file.path)
.output(`${builderContext.outputDirectory}/${filePath}`) .output(`${builderContext.outputDirectory}/${fileName}`)
.size(`${this.options.imageWidth ?? 200}x?`) .size(`${this.options.imageWidth ?? 200}x?`)
.run(); .run();
} else { } else {
const destination = `${builderContext.outputDirectory}/${filePath}`; console.log("Copying file", file.path);
console.log("Copying file", file.path, "to", destination); copyFile(file.path, `${builderContext.outputDirectory}/${fileName}`);
copyFile(file.path, destination);
} }
if (file.menuEntry) { if (file.menuEntry) {
menuManifest.push({ menuManifest.push({
name: fileName, name: fileName,
link: filePath, link: file.path,
}); });
} }
}); });

View File

@@ -85,12 +85,10 @@ const writeMarkdownAsHtmlToOutputDirectory = (
) => { ) => {
markdownManifest.forEach((m) => { markdownManifest.forEach((m) => {
const markdownAsHtml = toHtml(m.markdown, m.publicAssetDirectoryPath); const markdownAsHtml = toHtml(m.markdown, m.publicAssetDirectoryPath);
const titleWithoutDashesOrNumbers = (m.name.slice(m.name.indexOf('-') + 1) ?? "").replaceAll('-', ' ');
const noteHtmlDocument = noteHtmlTemplate.replace( const noteHtmlDocument = noteHtmlTemplate.replace(
markdownHtmlReplacementTag, markdownHtmlReplacementTag,
markdownAsHtml markdownAsHtml
);
).replace("{{title}}", titleWithoutDashesOrNumbers);
writeTextAsFile( writeTextAsFile(
`${outputDirectory}/${m.directoryName}.html`, `${outputDirectory}/${m.directoryName}.html`,
noteHtmlDocument noteHtmlDocument

View File

@@ -166,7 +166,6 @@ class Paragraph extends Symbol {
* @returns {boolean} * @returns {boolean}
*/ */
static canParse(line: string): boolean { static canParse(line: string): boolean {
return false;
return line === ""; return line === "";
} }
@@ -212,11 +211,7 @@ class UnorderedListItem extends Symbol {
/** /**
* @type {string} * @type {string}
*/ */
innerHtml: string = ""; text: string = "";
/**
* @type {string}
*/
innerMarkdown: string = "";
/** /**
* @type {number} * @type {number}
*/ */
@@ -238,7 +233,7 @@ class UnorderedListItem extends Symbol {
static create(lineFeed: LineFeed): Symbol { static create(lineFeed: LineFeed): Symbol {
const instance = new UnorderedListItem(); const instance = new UnorderedListItem();
const line = lineFeed.claim(); const line = lineFeed.claim();
instance.innerMarkdown = line.replace("-", "").trim(); instance.text = line.replaceAll("-", "").trim();
instance.level = getAmountOfTokenInBeginningOfFile(" ", line); instance.level = getAmountOfTokenInBeginningOfFile(" ", line);
return instance; return instance;
} }
@@ -249,12 +244,11 @@ class UnorderedListItem extends Symbol {
* @returns {void} * @returns {void}
*/ */
process(assetDirectory: string): void { process(assetDirectory: string): void {
if (this.innerMarkdown.length === 0) return; return;
this.innerHtml = toHtml(this.innerMarkdown, assetDirectory)
} }
render() { render() {
return `<li class="indent-${this.level}">${this.innerHtml}</li>`; return `<li class="indent-${this.level}">${this.text} indentation level ${this.level}</li>`;
} }
} }
@@ -282,7 +276,7 @@ class OrderedListItem extends Symbol {
* @returns {Symbol} * @returns {Symbol}
*/ */
static create(lineFeed: LineFeed): Symbol { static create(lineFeed: LineFeed): Symbol {
const instance = new OrderedListItem(); const instance = new UnorderedListItem();
const line = lineFeed.claim(); const line = lineFeed.claim();
instance.text = line.trim().replace(/^\d\. /, ""); instance.text = line.trim().replace(/^\d\. /, "");
instance.level = getAmountOfTokenInBeginningOfFile(" ", line); instance.level = getAmountOfTokenInBeginningOfFile(" ", line);
@@ -310,9 +304,8 @@ class Link extends Symbol {
/** /**
* @type {RegExp} * @type {RegExp}
*/ */
static textAndLinkRegExp: RegExp = new RegExp static textAndLinkRegExp: RegExp = new RegExp(
( /\[(?<text>.*)\]\((?<link>.*)\)/
/\[(?<text>[^\]]+)\]\((?<link>[^)]+)\)/
); );
/** /**
* @type {string} * @type {string}
@@ -357,8 +350,7 @@ class Link extends Symbol {
} }
render() { render() {
// TODO: This leading space should probably be somewhere else. return `<a href="${this.link}">${this.text}</a>`;
return ` <a href="${this.link}">${this.text}</a>`;
} }
} }
@@ -605,10 +597,6 @@ class MultiLineCode extends Symbol {
* @type {string} * @type {string}
*/ */
text: string = ""; text: string = "";
/**
* @type {string}
*/
language: string = "";
/** /**
* *
* @param {string} line * @param {string} line
@@ -626,7 +614,7 @@ class MultiLineCode extends Symbol {
*/ */
static create(lineFeed: LineFeed): Symbol { static create(lineFeed: LineFeed): Symbol {
const instance = new MultiLineCode(); const instance = new MultiLineCode();
const line = lineFeed.claim(); const line = lineFeed.claim().trim();
const lineWithoutStartTag = line.slice(3, line.length); const lineWithoutStartTag = line.slice(3, line.length);
const sameLineContainsEnding = lineWithoutStartTag.includes("```"); const sameLineContainsEnding = lineWithoutStartTag.includes("```");
@@ -644,10 +632,9 @@ class MultiLineCode extends Symbol {
instance.text = text; instance.text = text;
return instance; return instance;
} }
instance.language = lineWithoutStartTag const lines = [lineWithoutStartTag];
const lines = [];
const endTokenFound = false; let endTokenFound = false;
while (!endTokenFound && !lineFeed.isEmpty()) { while (!endTokenFound && !lineFeed.isEmpty()) {
const nextLine = lineFeed.claim(); const nextLine = lineFeed.claim();
if (!nextLine.includes("```")) { if (!nextLine.includes("```")) {
@@ -670,18 +657,15 @@ class MultiLineCode extends Symbol {
/** /**
* *
* @param {string} _assetDirectory * @param {string} assetDirectory
* @returns {void} * @returns {void}
*/ */
// NOTE: Needed to adhere to interface process(assetDirectory: string): void {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
process(_assetDirectory: string): void {
return; return;
} }
render() { render() {
const codeLanguage = this.language.length > 0 ? `<pre class="code-language">${this.language}</pre>` : ""; return `<pre><code>${this.text}</code></pre>`;
return `${codeLanguage}<pre><code style="white-space: pre;">${this.text}</code></pre>`;
} }
} }
@@ -904,21 +888,18 @@ const AllSymbols: (typeof Symbol)[] = [
export const toHtml = (markdown: string, assetDirectory: string): string => { export const toHtml = (markdown: string, assetDirectory: string): string => {
// Stage one, markdown to symbols // Stage one, markdown to symbols
const symbols = toSymbols(markdown, assetDirectory); const symbols = toSymbols(markdown, assetDirectory);
console.log(symbols)
// Stage two, expanding symbols // Stage two, expanding symbols
symbols.forEach((s) => s.process(assetDirectory)); symbols.forEach((s) => s.process(assetDirectory));
console.log(symbols)
// Stage three, operations based on symbol relationship // Stage three, operations based on symbol relationship
const stageThree = stageThreeProcessing(symbols); const stageThree = stageThreeProcessing(symbols);
// Stage four, rendering // Stage four, rendering
const html = stageThree const html = stageThree
//.filter((s) => !(s instanceof JustALineBreak)) // .filter((s) => !(s instanceof JustALineBreak))
.map((s) => s.render()) .map((s) => s.render())
.join(""); .join("");
return `<div>${html}</div>`;
return html;
}; };
/** /**
@@ -936,10 +917,6 @@ const stageThreeProcessing = (symbols: Symbol[]): Symbol[] => {
const subEndIndex = localSymbols const subEndIndex = localSymbols
.slice(startIndex + 1) .slice(startIndex + 1)
.findIndex((s) => s instanceof JustALineBreak); .findIndex((s) => s instanceof JustALineBreak);
if (subEndIndex === -1) {
localSymbols.splice(startIndex);
break;
}
const endIndex = const endIndex =
subEndIndex === -1 subEndIndex === -1
? localSymbols.length - 1 ? localSymbols.length - 1

View File

@@ -23,7 +23,6 @@ export class StartPagePlugin implements IPluginBuilder {
${links.join("")} ${links.join("")}
`; `;
htmlTemplate = htmlTemplate.replace("{{content}}", html); htmlTemplate = htmlTemplate.replace("{{content}}", html);
console.log("Generating index", this.options.indexTemplatePath);
writeTextAsFile( writeTextAsFile(
`${builderContext.outputDirectory}/index.html`, `${builderContext.outputDirectory}/index.html`,
htmlTemplate htmlTemplate

View File

@@ -1,75 +0,0 @@
:root {
--color-background: #1c1b28;
--color-accent: #00FFFF;
--color-text: #FFF;
--content-width: 800px;
--font-size-base: 18px;
--line-height-base: 26px;
--font-family: monospace;
--space-base: 1rem;
}
.indent-0 {
margin-left: 1rem;
}
.indent-1 {
margin-left: 2rem;
}
.indent-2 {
margin-left: 3rem;
}
.indent-3 {
margin-left: 4rem;
}
.indent-4 {
margin-left: 5rem;
}
.indent-5 {
margin-left: 6rem;
}
.indent-6 {
margin-left: 7rem;
}
.banner {
margin-top: 1rem;
width: 100%;
height: 40px;
display: flex;
flex-direction: row;
margin-bottom: 1rem;
align-items: center;
flex: 1;
font-family: monospace;
}
.banner {
.title {
display: flex;
flex-direction: row;
flex: 1;
.name {
color: #1c1b28;
background-color: #00FFFF;
white-space: nowrap;
padding: 5px;
a {
color: unset;
text-decoration: none;
}
}
}
.title::after {
background: repeating-linear-gradient(90deg, #00FFFF, #00FFFF 2px, transparent 0, transparent 10px);
content: "";
display: block;
width: 100%;
margin-left: 10px;
}
}

View File

@@ -24,6 +24,7 @@ new ToolchainBuilder(paths)
files: [ files: [
{ {
path: "src/templates/404.html", path: "src/templates/404.html",
nameOverride: "Not Found Page",
menuEntry: false, menuEntry: false,
}, },
{ {
@@ -31,13 +32,6 @@ new ToolchainBuilder(paths)
}, },
{ {
path: "src/assets/favicon.ico", path: "src/assets/favicon.ico",
},
{
path: "src/assets/base.css"
},
{
path: "src/playground/playground.html",
nameOverride: "CSS/HTML playground"
} }
], ],
}), }),

View File

@@ -1,6 +1,6 @@
# Initial commit # Initial commit
_2023-09-15 - 1 minute read_ _2023-09-15_
I've been thinking a long time about having a place to publicly publish things. I'm not really into doing that on social media since it would lock my content in their format and make it hard to move anywhere else so i thought i would just write my "things" in plain markdown and then find a way of hosting them online. I've been thinking a long time about having a place to publicly publish things. I'm not really into doing that on social media since it would lock my content in their format and make it hard to move anywhere else so i thought i would just write my "things" in plain markdown and then find a way of hosting them online.

View File

@@ -1,48 +0,0 @@
# Neovim 0.12 Configuration
In neovim's 0.12 nightly release an official package manager was added. This means that we no longer need to rely on third party software to bootstrap our configuration.
The syntax is similar in the way that we previously specified src and name in Lazy, but setup of the package has to be done separately. At least for now. I have never done it in that way since i started using neovim when Lazy was already the go to package manager so it's going to be interesting to see how it affects my configuration structure.
```lua
vim.pack.add({
'https://github.com/folke/todo-comments.nvim',
})
require('todo-comments').setup({
keywords = {
SECTION = {
icon = "󰚟 ",
color = "hint",
}
}
})
```
[Neovim pack](https://neovim.io/doc/user/pack)
I wiped my current 0.11 configuration and started fresh with this new package manger. I try to keep it lean to reduce startup time, but i do work in a couple of different environments so language support is vital. [This is what i ended up with so far](https://git.zacke.dev/wholteza/dotfiles/src/commit/2b2ce07edc5e8eb5b9e4cffc234abe4cdc8512f6/.config/nvim/init.lua):
- Colorscheme: [catppuccin/nvim](https://github.com/catppuccin/nvim)
- File explorer: 🆕[stevearc/oil.nvim](https://github.com/stevearc/oil.nvim)
- File and fuzzy finder: 🆕[nvim-mini/mini.pick](https://github.com/nvim-mini/mini.pick)
- Notifications: 🆕[nvim-mini/mini.notify](https://github.com/nvim-mini/mini.notify)
- Quality of life:
- [nvim-mini/mini.pairs](https://github.com/nvim-mini/mini.pairs)
- Auto create pairs when you type {}, [], () etc.
- [folke/which-key.nvim](https://github.com/folke/which-key.nvim)
- Shows a popup menu of keybindings when pressing leader.
- [folke/todo-comments.nvim](https://github.com/folke/todo-comments.nvim)
- Highlights comments starting with TODO: WARNING: ERROR: etc.
- Language support:
- Easier installation of language servers and dependencies.
- [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig)
- [mason-org/mason.nvim](https://github.com/mason-org/mason.nvim)
- [mason-org/mason-lspconfig.nvim](https://github.com/mason-org/mason-lspconfig.nvim)
- [WhoIsSethDaniel/mason-tool-installer](https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim)
- [folke/lazydev.nvim](https://github.com/folke/lazydev.nvim)
- [nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)
- Syntax highlighting.
- Dependency: [nvim-lua/plenary](https://github.com/nvim-lua/plenary.nvim)
[Here](https://git.zacke.dev/wholteza/dotfiles/src/branch/main/.config/nvim/init.lua) is the up to date configuration if you want to see what it looks like right now.

View File

@@ -1,16 +1,16 @@
# Markdown parser # Markdown parser
_2023-09-16 - 5 minute read_
## Part 1 ## Part 1
_2023-09-16_
While traveling today i thought about ways to structure my markdown parser. While traveling today i thought about ways to structure my markdown parser.
What I want is a function that takes a string containing markdown formatted text and outputs a string of html elements. What I want is a function that takes a string containing markdown formatted text and outputs a string of html elements.
My plan is to solve this using a markdown line feed and symbol classes representing the different possible elements. My plan is to solve this using a markdown line feed and symbol classes representing the different possible elements.
These symbols are for example headings, list items, code snippets, paragraphs and links. These symbols are for example headings, list items, code snippets, paragraphs and links.
Each symbol will know how to render itself as well as being able to tell if it's the right symbol for the current feed line. Each symbol will know how to render itself as well as being able to tell if it's the right symbol for the current line feed line.
Lets say we have a markdown file that looks something like this: Lets say we have a markdown file that looks something like this:

View File

@@ -1,7 +1,5 @@
# Tunneling traffic through ssh # Tunneling traffic through ssh
_2024-12-23 - 1 minute read_
This lets you tunnel port 80 from your machine, to port 80 on another machine, using some ssh server as jump node. This lets you tunnel port 80 from your machine, to port 80 on another machine, using some ssh server as jump node.
```bash ```bash

View File

@@ -1,7 +1,5 @@
# Converting various document formats # Converting various document formats
_2025-01-10 - 1 minute read_
I was in need of a way to convert markdown documents + images into pdfs and came over the [pandoc](https://pandoc.org/) project which seems to be very powerful. I was in need of a way to convert markdown documents + images into pdfs and came over the [pandoc](https://pandoc.org/) project which seems to be very powerful.
This is how i used it. This is how i used it.

View File

@@ -1,4 +1,4 @@
# Measuring mash temperatures # Measuring mash temperatures 🍺 🌡️
_2025-02-03 - 5 minute read_ _2025-02-03 - 5 minute read_
@@ -13,7 +13,7 @@ Esphome made the setup very easy. After defining the onewire hub component i cou
![nodemcu](@asset/3.png) ![nodemcu](@asset/3.png)
My esphome instance is running in my local homeassistant (HASS) instance which integrates neatly and makes all sensors available to both systems. I created a new view in HASS and added the sensors showing the current values as well as a histogram of the last 5 minutes thinking that it should be a reasonable timespan to have when comparing temperatures. To my suprise the 5 minute interval in HASS didn't show 5 minutes at all but rather 24h. It was possible to zoom in on the graph to see it in more detail but i wasn't happy with how it turned out. Ideally I wanted this view to be visible on one of my screens in my brewery and it should show what I want to see without me having to interact with it. My esphome instance is running in my local homeassistant (HASS) instance which integrates neatly and makes all sensors available to both systems. I created a new view in HASS and added the sensors showing the current values as well as a histogram of the last 5 minutes thinking that is should be a reasonable timespan to have when comparing temperatures. To my suprise the 5 minute interval in HASS didn't show 5 minutes at all but rather 24h. It was possible to zoom in on the graph to see it in more detail but i wasn't happy with how it turned out. Ideally I wanted this view to be visible on one of my screens in my brewery and it should show what I want to see without me having to interact with it.
![nodemcu](@asset/4.png) ![nodemcu](@asset/4.png)
@@ -27,7 +27,7 @@ We usually brew two batches the same day since it allows us to do utilize the ti
![mash](@asset/mash.png) ![mash](@asset/mash.png)
We did the first batch as usual, heating up the water to 67c before dumping in the malt pipe along with our 5KG grain bill of mostly pilsner malt and some wheat. Historically we have been going off of the built-in temperature sensor in the bottom of our brewzilla brewsystem, but one of its flaws is that the sensor is located right above the two heating elements which means that it is going to measure the warmest water in the pot. This works for turning the heater on and off while recirculating using a pump while the brewery has no equipment or ingredients in it, but as soon as you add anything it is no longer going do represent the temperature of the whole vessel. We put the first sensor below the malt pipe right above the false bottom of the brewery, then we put the second one in the middle of grains. We did the first batch as usual, heating up the water to 67c before dumping in the malt pipe along with our 5KG grain bill of mostly pilsner malt and some wheat. Historically we have been going off of the bult in temperature sensor in the bottom of our brewzilla brewsystem, but one of its flaws is that the sensor is located right above the two heating elements which means that it is going to measure the warmest water in the pot. This works for turning the heater on and off while recirculating using a pump while the brewery has no equipment or ingredients in it, but as soon as you add anything it is no longer going do represent the temperature of the whole vessel. We put the first sensor below the malt pipe right above the false bottom of the brewery, then we put the second one in the middle of grains.
![Picture of first batch mash temperature graph](@asset/first-batch-mash-graph.png) ![Picture of first batch mash temperature graph](@asset/first-batch-mash-graph.png)
_Yellow: temperature of the grain bed. Green: temperature just below the malt pipe._ _Yellow: temperature of the grain bed. Green: temperature just below the malt pipe._
@@ -46,12 +46,4 @@ For the next batch we heated up the strike water to 75c. After adding the malt p
Now we just had to maintain that temperature, something that we have done before with great success by setting the heaters to 67c and starting to pump. Now we just had to maintain that temperature, something that we have done before with great success by setting the heaters to 67c and starting to pump.
![Picture of second mash histogram](@asset/second-batch-mash-histogram.png) ![Picure of second mash histogram](@asset/second-batch-mash-histogram.png)
By the end of the brew day the last batch measured 1.050 so suprisingly we ended up 4 points below the last batch that was identical recipe-wise. That could of course be due to multiple factors. We use pre-milled grain so depending on if we use the malt from the top or the bottom of the bucket there might be more or less grain flour. Or perhaps the mash is just more efficient at a bit lower temperature. However from what I've read the most efficient mashing range is between 65-67 degrees celcius so I'm a bit confused.
I think we need to brew some more beers using this method and see if we if it increases our result to recipe accuracy.
In the end the only thing that matters is that we brew good tasting beer and i don't think this process will change much when it comes to that. It's just feels good to hit the target!
Cheers!

View File

@@ -1,13 +0,0 @@
# You would not download a theme
_2025-09-19 - 1 minute read_
I read this [blog](https://www.jmaguire.tech/) by a guy called John Maguire. It popped up when i was searching for how to configure [code folding in neovim](https://www.jmaguire.tech/posts/treesitter_folding/) and i thought the style was pretty neat!
I thought the least i could do was to provide some credits and a link to the place.
Both the header and the blog post list double dot underlining was done with ::after elements, which i thought was quite nifty. The header uses a repeating linear gradient of the accent color and transparent, never used that before.
You learn every day!
Also, I just noticed that my blog engine cannot manage file names with single quotes in them, whoopsie.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 MiB

View File

@@ -1,23 +0,0 @@
# Second hand MTB - part 1
_2025-08-17 - 2 minute read_
![bike-in-car](@asset/bike-in-car.jpg)
_Tadaa!_
I have wanted a hardtail for a while. I've been browsing the shops from time to time but never been ready to pull the trigger on buying a second full price bike. Some weeks ago my mother made a great local find on facebook marketplace. It was a modern 11-speed carbon fiber hardtail with SRAM x01 components and it got me determined to find one myself.
My musts were:
- Correct frame size (XL), duh.
- 1x drivetrain or being able to convert to 1x easily.
- No obvious signs of complete neglect such as rust horizontal rule broken components.
I want to keep this bike simple. My focus is for it to just work and be as close to a workhorse as it can be when it comes to mountain biking. That means no extra linkages, suspension or dropper-post to service. When my FS-bike needs fixing or I just want to hit the trail for some stupid fun it should be there ready to go.
So I found the bike I in the picture. After some detective work I found out that it probably is a 2012 KHS. Carbon fiber frame, SRAM x01 2x10 drivetrain, and best of all the guy would throw in some of the stuff that he had for the bike, one of those things was a 1x drive gear. That means that I will be able to convert it into a 1x10 drivetrain with very little effort. It looked pretty good. The top tube has been professionally repaired 5 years ago but I figured that if it had survived for this long it will probably work for me as well. I also got 3 more wheels that need some work and a longer steering bar.
I got to ride it a bit before deciding on the purchase and it did not creek or make any other strange sounds. The drivetrain shifted well and the suspension seemed responsive. Of course things could use a little cleaning and service but a lot of bikes do. I was pretty stoked that i found a carbon fiber bike for a good price.
I know that I probably want to change out the front drive gear pretty soon but first I just want to try it out on the trail to see how it performs without any changes.
Cheerio!

View File

@@ -1,17 +0,0 @@
# Live demos and experiments
I recently attended a Swedish frontend development conference called nordicjs. Something that I noticed was that some of the speakers had made tools and demos that they were using live in their keynotes which I thought was pretty neat!
It got me thinking about being able to host tools like that along with my [static site generator](https://git.zacke.dev/wholteza/blog). As long as they're not too heavy and dependent on JS libraries.
My SSG project is built up around a plugin chain where one of the plugins available is a [file loader](https://git.zacke.dev/wholteza/blog/src/branch/main/packages/@zblog/toolchain/src/file-loader.ts). So to start off i just added a new directory for what I call playgrounds and added a new HTML file into the [files array of the plugin](https://git.zacke.dev/wholteza/blog/src/commit/52089dd9dc163ddbf1dbbc56af31e39bb7a0254f/src/index.ts) which makes sure that it is available in the built site.
The result? The very first [playground](/playground.html) of this site. It's a very simple HTML + CSS playground with a preview pane that will show you the result. To start off it shows the CSS logotype, but change any parameter and it will update immediately. [Source BTW](https://git.zacke.dev/wholteza/blog/src/commit/52089dd9dc163ddbf1dbbc56af31e39bb7a0254f/src/playground/playground.html).
This initial playground is meant to be part of something bigger. I have thoughts about building a presentation framework and I want these kinds of interactive and editable examples to be part of it. But we will see when I get the urge to build it.
Until then I will have a way to make different demos and experiments available.
Right now they will show up as menu alternatives on the home page. I also noticed that since files loaded by the file loader plugin wasn't designed to end up on the home page post flow they always end up at the top which isn't ideal. I'll have to do something about that in the future or just simply have a blog post that link to each playground. I probably want to write something about it anyways. _Edit: I updated the file loading so that is isn't present on the home page._
Until next time.

View File

@@ -1,18 +0,0 @@
# Kubernetes tricks
A collection of nice to haves while managing applications in kubernetes.
## Kubectl proxy
Running `kubectl proxy` will open a tunnel to the selected cluster. You can then access the webapi on the port that was displayed.
Using that webapi you can browse the resources and even access the application hosted within your cluster.
### Explore a namespace
Enter the following address and replace the namespace name and the resource type (pods|services) to get a list of the available resources.
`http://localhost:8001/api/v1/namespaces/<namespace-name>/<resource-type>`
### Make a request to a service or pod
The url pattern looks like this: `http://localhost:8001/api/v1/namespaces/<namespace-name>/<pods|services>/<protocol>:<name>:<port>/proxy/<url-path>`
Example: `http://localhost:8001/api/v1/namespaces/hangfire-services/services/http:hangfire-services:80/proxy/hangfire` will make a request to my hangfire dashboard.

View File

@@ -1,146 +0,0 @@
<!DOCTYPE html>
<meta>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>zacke.dev - playground</title>
<link href="https://blog.zacke.dev" rel="preconnect" />
<link href="base.css" rel="stylesheet" />
<style>
html {
line-height: var(--line-height-base);
font-size: 18px;
display: flex;
justify-content: center;
align-items: center;
font-family: var(--font-family);
}
body {
max-width: var(--content-width);
background-color: var(--color-background);
color: var(--color-text);
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
#description {
box-sizing: border-box;
margin: var(--space-base);
padding: var(--space-base);
a {
color: var(--color-accent);
}
}
#style-editor {
width: 100%;
padding: 1rem;
margin-bottom: 1rem;
background-color: var(--color-background);
color: var(--color-text);
font-size: 18px;
display: flex;
flex: 1;
outline: none;
border: 1px solid var(--color-accent);
box-sizing: border-box;
}
#html-editor {
width: 100%;
padding: 1rem;
margin-bottom: 1rem;
background-color: var(--color-background);
color: var(--color-text);
font-size: 18px;
display: flex;
flex: 1;
outline: none;
border: 1px solid var(--color-accent);
box-sizing: border-box;
}
#preview {
width: 100%;
box-sizing: border-box;
padding: 1rem;
margin-bottom: 1rem;
background-color: var(--color-background);
color: var(--color-text);
font-size: 18px;
display: flex;
flex: 1;
outline: none;
border: 1px solid var(--color-accent);
justify-content: center;
align-items: center;
}
}
</style>
<style id="style">
.square {
height: 150px;
width: 150px;
background-color: #553FF3;
color: white;
font-weight: bold;
font-size: 3rem;
position: ;
border-radius: 30px;
border-top-left-radius: 5px;
}
span {
display: block;
position: relative;
top: 100px;
left: 45px;
}
</style>
<div id="markup" aria-hidden="true" hidden="true" style="display: none;">
<div class="square"><span>CSS</span></div>
</div>
<script type="text/javascript">
window.onload = function () {
const style = document.getElementById('style').innerHTML;
const formattedStyle = style.split("\n").reduce((agg, curr) => {
console.log(curr)
if (curr === "") return agg;
const line = curr.replace('\t', '').replaceAll('\t', ' ');
if (agg.length === 0) return line;
return agg + "\n" + line;;
}, "");
console.log(formattedStyle)
document.getElementById('style-editor').value = formattedStyle;
document.getElementById('style').innerHTML = formattedStyle;
const markup = document.getElementById('markup').innerHTML;
const formattedMarkup = markup.replace('\t', '').replaceAll('\t', ' ').split('\n').filter(line => line !== '').join('\n');
document.getElementById('html-editor').value = formattedMarkup;
document.getElementById('preview').innerHTML = formattedMarkup;
}
</script>
</meta>
<body>
<div class="banner">
<div class="title">
<div class="name"><a href="/">Zackarias Montell</a></div>
</div>
</div>
<p id="description">This is an interactive HTML and CSS playground built with minimal JS. Alter the HTML and CSS below
to preview the changes. <a
href="https://git.zacke.dev/wholteza/blog/src/branch/main/src/playground/playground.html">Source here!</a></p>
<textarea id="html-editor" oninput="document.getElementById('preview').innerHTML=this.value" rows="15"
autocomplete="false" autocorrect="false" id="style-editor">
</textarea>
<textarea rows="15" autocomplete="false" autocorrect="false" id="style-editor"
oninput="document.getElementById('style').innerHTML = this.value">
</textarea>
<div id="preview"></div>
</body>
</html>

View File

@@ -1,18 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Not Found</title> <title>Page Not Found</title>
</head> </head>
<body>
<body>
<div id="message"> <div id="message">
<h2>404</h2> <h2>404</h2>
<h1>Page Not Found</h1> <h1>Page Not Found</h1>
<p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p> <p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -4,12 +4,11 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>zacke.dev</title> <title>Document</title>
<link href="https://blog.zacke.dev" rel="preconnect" />
<link href="base.css" rel="stylesheet" />
<style> <style>
html { html {
font-size: 18px; font-family: Arial, sans-serif;
font-size: 17px;
line-height: 26px; line-height: 26px;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -19,13 +18,13 @@
} }
a { a {
color: var(--color-accent); color: #FFF;
} }
body { body {
max-width: 800px; max-width: 800px;
color: #fbfbfe; color: #fbfbfe;
background-color: var(--color-background); background-color: #1c1b28;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -86,34 +85,70 @@
} }
.profile li {} .profile li {}
.banner {
margin-top: 1rem;
width: 100%;
height: 40px;
display: flex;
flex-direction: row;
margin-bottom: 1rem;
align-items: center;
flex: 1;
font-family: monospace;
}
.banner .title {
display: flex;
flex-direction: row;
flex: 1;
}
.banner .title::after {
background: repeating-linear-gradient(90deg, #00FFFF, #00FFFF 2px, transparent 0, transparent 10px);
content: "";
display: block;
width: 100%;
margin-left: 10px;
}
.banner .title .name {
color: #1c1b28;
background-color: #00FFFF;
white-space: nowrap;
padding: 5px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="banner"> <div class="banner">
<div class="title"> <div class="title">
<div class="name"><a href="/">Zackarias Montell</a></div> <div class="name">Zackarias Montell</div>
</div> </div>
</div> </div>
<div class="profile"> <div class="profile">
<img class="picture" src="profile.jpg" width="150" height="150" fetchpriority="high" /> <img class="picture" src="profile.jpg" width="150" height="150" fetchpriority="high" />
<p>Just a dude from sweden.</p> <p>Software developer from Sweden. This is my place. Sometimes I publish thoughts or projects.</p>
<span>Things I do:</span> <p>Things I do:</p>
<ul> <ul>
<li><a href="https://git.zacke.dev/wholteza/blog">Programming for fun</a>.</li> <li>Program things for fun. This site is built using <a href="https://git.zacke.dev/wholteza/blog">my own blog
<li><a href="https://git.zacke.dev/wholteza/5x6-split-kb/src/branch/main/rev2">Elecronics and product framework</a> that generates a static website from markdown + a little bit of html templating.</li>
projects</a>. <li>I build physical things as well. Lately it has been <a
<li><a href="/5-measuring-mash-temperatures.html">Brew beer in my garage</a>.</li> href="https://git.zacke.dev/wholteza/5x6-split-kb/src/branch/main/rev2">keyboards</a> in multiple <a
<li><a href="https://www.strava.com/athletes/41297534">Running and biking</a>.</li> href="https://git.zacke.dev/wholteza/5x6-split-kb/src/branch/main/rev1">revisions</a>.</li>
<li>Brew beer in my garage. <a href="/5-measuring-mash-temperatures">I find the process interesting..</a> and i
enjoy drinking beer.</li>
<li>Running and biking. You can find me on <a href="https://www.strava.com/athletes/41297534">strava</a>.
</li>
</ul> </ul>
<span style="font-size: 9px; line-height: 1rem; margin-top: 1rem;"> <p><a href="https://git.zacke.dev/explore/repos">Gitea</a></p>
<a href="https://git.zacke.dev/explore/repos">Gitea</a>, <p><a href="https://github.com/Wholteza">Github</a></p>
<a href="https://github.com/Wholteza">Github</a>, <p><a href="mailto:contact@montell.se">contact@montell.se</a> (not actually created yet but try changing out contact
<a href="mailto:contact@montell.se">contact@montell.se</a> for
<span style="display: block; line-height: 1rem;">Not actually my email, try changing out contact my first name.)</p>
for my first name.</span>
</span>
</div> </div>
<div class="content"> <div class="content">

View File

@@ -1,118 +1,56 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>zacke.dev - {{title}}</title> <title>Document</title>
<link href="https://blog.zacke.dev" rel="preconnect" />
<link href="base.css" rel="stylesheet" />
<style> <style>
html { html {
font-family: monospace; font-family: Arial, sans-serif;
font-size: 18px; font-size: 1.3em;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
line-height: 26px;
flex-direction: column; flex-direction: column;
} }
@media screen and (max-width: 800px) {
html {
font-size: 0.9em;
}
}
body { body {
max-width: 800px; max-width: 800px;
color: #fbfbfe; color: #fbfbfe;
background-color: #1c1b28; background-color: #1c1b22;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
flex: 1;
} }
pre {
box-sizing: border-box;
border: 1px solid #00FFFF;
padding: 1rem;
width: fit-content;
max-width: 100%;
white-space: normal;
margin-top: 0;
}
pre.code-language {
padding: 0.1rem 1rem 0 1rem;
margin: 0;
border-bottom: none;
color: #00FFFF;
font-weight:bold;
text-transform: capitalize;
}
img { img {
max-width: 100%; max-width: 100%;
border-radius: 4px; border-radius: 8px;
} }
p{
p { margin: 20px;
margin-top: 20px; margin-top: 20px;
margin-bottom: 0px; margin-bottom: 0px;
} }
h1, h2, h3, h4, h5, h6 {
h1 { margin: 20px;
color: #00FFFF;
}
h1::after {
margin-top: 1rem;
content: "";
width: 100%;
display: block;
border-top: 3px dotted #00FFFF;
border-bottom: 3px dotted #00FFFF;
height: 8px;
box-sizing: border-box;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 20px;
margin-bottom: 0px; margin-bottom: 0px;
text-transform: capitalize; text-transform: capitalize;
line-height: 32px;
} }
h1 + p:has(> i) {
h1+p:has(> i) { font-size: smaller;
margin-top: 1rem; margin-top: 10px;
color: #00FFFF;
} }
p:has(> img) + p:has(> i) {
p:has(> img)+p:has(> i) {
font-size: smaller; font-size: smaller;
margin-top: 0px; margin-top: 0px;
} }
a {
color: #00FFFF;
}
.content {
display: flex;
flex-direction: column;
flex: 1;
}
</style> </style>
</head> </head>
<body> <body>
<div class="banner"> {{markdown}}
<div class="title">
<div class="name"><a href="/">Zackarias Montell</a></div>
</div>
</div>
<div class="content">{{markdown}}</div>
</body> </body>
</html> </html>