Compare commits

...

20 Commits

Author SHA1 Message Date
Bethlenfalvi, Lorinc (ext)
8bafec4821 Updated footer and about
All checks were successful
Upload via SSH / build (push) Successful in 1m4s
2026-01-06 10:24:09 +01:00
Bethlenfalvi, Lorinc (ext)
d36c6d5622 maybe fixing job maybe
All checks were successful
Upload via SSH / build (push) Successful in 53s
2026-01-05 23:55:27 +01:00
Bethlenfalvi, Lorinc (ext)
e4d6a9df56 Switched to full sync
All checks were successful
Upload via SSH / build (push) Successful in 54s
2026-01-05 23:48:37 +01:00
Bethlenfalvi, Lorinc (ext)
77f9310786 Moved deploy to shared job
Some checks failed
Upload via SSH / build (push) Failing after 49s
2026-01-05 23:34:14 +01:00
Bethlenfalvi, Lorinc (ext)
967a9a3ebd Removed use of recent web apis
Some checks failed
Upload via SSH / build (push) Successful in 40s
Upload via SSH / deploy (push) Failing after 2s
2026-01-05 23:31:02 +01:00
Bethlenfalvi, Lorinc (ext)
0c3ac11c61 Various improvements
Some checks failed
Upload via SSH / build (push) Failing after 41s
Upload via SSH / deploy (push) Has been skipped
2026-01-05 23:23:01 +01:00
Bethlenfalvi, Lorinc (ext)
fe519c46e7 Added sync stage
Some checks failed
Upload via SSH / build (push) Failing after 30s
Upload via SSH / deploy (push) Has been skipped
2026-01-05 23:03:05 +01:00
Bethlenfalvi, Lorinc (ext)
01db7a8bb1 Updated packages
Some checks failed
Upload via SSH / build (push) Failing after 26s
Upload via SSH / deploy (push) Has been skipped
2026-01-05 22:59:15 +01:00
Bethlenfalvi, Lorinc (ext)
15aa4575bc Maybe fixed action
Some checks failed
Upload via SSH / build (push) Failing after 30s
Upload via SSH / deploy (push) Has been skipped
2026-01-05 22:33:45 +01:00
Bethlenfalvi, Lorinc (ext)
6f687d22c2 Updated action
Some checks failed
Upload via SSH / build (push) Failing after 5m20s
Upload via SSH / deploy (push) Has been skipped
2026-01-05 22:18:34 +01:00
Bethlenfalvi, Lorinc (ext)
ad775e4f77 changed assets to contabo
Some checks failed
Deploy to GitHub Pages / build (push) Failing after 7m25s
Deploy to GitHub Pages / deploy (push) Has been skipped
2025-03-19 09:41:41 +01:00
Bethlenfalvi, Lorinc (ext)
c4c9959516 added fortune and fixed patreon button 2025-01-31 10:15:36 +01:00
8f63b56619 Improved images 2025-01-30 23:17:41 +00:00
16bc996103 removed stray hide_name reference 2025-01-30 23:06:47 +00:00
6174a406ec fixed typo 2025-01-30 23:01:40 +00:00
75e08d3493 Added Projects page 2025-01-30 22:57:08 +00:00
4a481defb9 temporary projects page 2025-01-30 21:40:47 +00:00
50201ec1fe further improvements 2025-01-30 20:48:44 +00:00
99dba7d0c3 Fixed some bugs and added contact info 2025-01-30 20:38:38 +00:00
Bethlenfalvi, Lorinc (ext)
6ba363c2c9 patreon 2025-01-29 20:33:35 +01:00
27 changed files with 2257 additions and 2013 deletions

View File

@@ -0,0 +1,42 @@
name: Upload via SSH
on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branchs name
push:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:
# Allow this job to clone the repo and create a page deployment
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install node
run: |
apt-get update
apt-get install nodejs -y
- name: Checkout your repository using git
uses: actions/checkout@v4
- name: Install dependencies
run: npm install
- name: Sync collection types
run: npx astro sync
- name: Build your site
run: npm run build
- name: Deploy
uses: https://code.klank.school/riviera/actions-file-deployer@v4
with:
remote-protocol: "sftp"
remote-host: "lbfalvy.com"
remote-user: "www-upload"
ssh-private-key: ${{ secrets.SSH_KEYFILE }}
local-path: "./dist"
remote-path: "/lbfalvy.com"
sync: full

View File

@@ -1,39 +0,0 @@
name: Deploy to GitHub Pages
on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branchs name
push:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:
# Allow this job to clone the repo and create a page deployment
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout your repository using git
uses: actions/checkout@v4
- name: Install, build, and upload your site
uses: withastro/action@v3
# with:
# path: . # The root location of your Astro project inside the repository. (optional)
# node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional)
# package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

3651
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,20 +9,20 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.0.7", "@astrojs/mdx": "^4.3.13",
"@astrojs/react": "^4.1.6", "@astrojs/react": "^4.4.2",
"@astrojs/rss": "^4.0.11", "@astrojs/rss": "^4.0.14",
"@astrojs/sitemap": "^3.2.1", "@astrojs/sitemap": "^3.6.0",
"@astrojs/svelte": "^7.0.4", "@astrojs/svelte": "^7.2.4",
"@js-temporal/polyfill": "^0.4.4", "@js-temporal/polyfill": "^0.5.1",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.1.18",
"astro": "^5.1.9", "astro": "^5.16.6",
"rehype-autolink-headings": "^7.1.0", "rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"remark-toc": "^9.0.0", "remark-toc": "^9.0.0",
"shiki": "^2.1.0", "shiki": "^3.20.0",
"svelte": "^5.19.3", "svelte": "^5.46.1",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.1.18",
"typescript": "^5.7.3" "typescript": "^5.9.3"
} }
} }

View File

@@ -10,34 +10,46 @@
} }
let { posts }: Props = $props(); let { posts }: Props = $props();
let allTags = $derived([...new Set(posts.flatMap(item => item.data.tags))]); let allTags = $derived([...new Set(posts.flatMap((item) => item.data.tags))]);
let selectedTags = $state<SvelteMap<string, boolean>>(); let selectedTags = $state<SvelteMap<string, boolean>>();
onMount(() => { onMount(() => {
const url = new URL(window.location.href); const url = new URL(window.location.href);
const tagsQry = url.searchParams.get('tags'); const tagsQry = url.searchParams.get("tags");
const defaultTags = tagsQry ? new Set(decodeURIComponent(tagsQry).split('+')) : new Set(); const defaultTags = tagsQry
selectedTags = new SvelteMap(allTags.map(tag => [tag, defaultTags.has(tag)])); ? new Set(decodeURIComponent(tagsQry).split("+"))
}) : new Set();
selectedTags = new SvelteMap(
allTags.map((tag) => [tag, defaultTags.has(tag)]),
);
});
$effect(() => { $effect(() => {
if (!selectedTags) return; if (!selectedTags) return;
const newTagsQry = encodeURIComponent( const newTagsQry = encodeURIComponent(
[...selectedTags.entries()].filter(([_, v]) => v).map(([k, _]) => k).join('+') [...selectedTags.entries()]
.filter(([_, v]) => v)
.map(([k, _]) => k)
.join("+"),
); );
const url = new URL(window.location.href); const url = new URL(window.location.href);
if (newTagsQry == url.searchParams.get("tags")) return; if (newTagsQry == url.searchParams.get("tags")) return;
if (newTagsQry == "") url.searchParams.delete("tags"); if (newTagsQry == "") url.searchParams.delete("tags");
else url.searchParams.set("tags", newTagsQry); else url.searchParams.set("tags", newTagsQry);
window.history.pushState(null, "", url.toString()); window.history.pushState(null, "", url.toString());
}) });
let isFiltered = $derived(selectedTags && [...selectedTags.values()].includes(true)); let isFiltered = $derived(
let filteredPosts = $derived(isFiltered selectedTags && [...selectedTags.values()].includes(true),
? posts.filter(p => !p.data.unlisted && p.data.tags.some(t => selectedTags!.get(t))) );
: posts.filter(p => !p.data.unlisted)); let listedPosts = $derived(posts.filter((p) => !p.data.unlisted));
let shownPosts = $derived(filteredPosts.toReversed()) let filteredPosts = $derived(
isFiltered
? listedPosts.filter((p) => p.data.tags.some((t) => selectedTags!.get(t)))
: listedPosts,
);
let shownPosts = $derived([...filteredPosts].reverse());
</script> </script>
<div class="lg:flex flex-row-reverse justify-between items-start"> <div class="lg:flex flex-row-reverse justify-between items-start">
@@ -46,31 +58,37 @@
{#each allTags as tag} {#each allTags as tag}
<button <button
onclick={() => { onclick={() => {
console.log("TRoggled tag") console.log("TRoggled tag");
selectedTags!.set(tag, !selectedTags!.get(tag)) selectedTags!.set(tag, !selectedTags!.get(tag));
}} }}
class={[ class={[
"m-0.5 rounded-4xl emph-bg px-2 cursor-pointer border-2 border-solid", "m-0.5 rounded-4xl emph-bg px-2 cursor-pointer border-2 border-solid",
selectedTags?.get(tag) && "border-gray-400" selectedTags?.get(tag) && "border-gray-400",
]} ]}
> >
{tag} {tag}
</button> </button>
{/each} {/each}
</div> </div>
<main class="max-w-[650px]"> <main class="max-w-650px">
{#each shownPosts as post} {#each shownPosts as post}
<article> <article>
<a href={`/blog/${post.id}`}> <a href={`/blog/${post.id}`}>
<div class=" <div
class="
lg:grid grid-cols-[auto_min-content_min-content] lg:grid grid-cols-[auto_min-content_min-content]
m-1 p-2 hover:bg-shade m-1 p-2 hover:bg-shade
"> "
>
<h2 class="font-bold">{post.data.title}</h2> <h2 class="font-bold">{post.data.title}</h2>
<address class="inline-block post-meta col-start-2 md:ml-3"> <address
class="inline-block post-meta col-start-2 md:ml-3 whitespace-nowrap"
>
{post.data.author} {post.data.author}
</address> </address>
<div class="inline-block post-meta col-start-3 lg:ml-1"> <div
class="inline-block post-meta col-start-3 lg:ml-1 whitespace-nowrap"
>
<Time datetime={post.data.pubDate} /> <Time datetime={post.data.pubDate} />
</div> </div>
<div class="col-span-3">{post.data.summary}</div> <div class="col-span-3">{post.data.summary}</div>

103
src/components/Fortune.tsx Normal file
View File

@@ -0,0 +1,103 @@
import React from "react"
const messages = [
"Test message, please ignore",
"Often when people ask difficult questions they don't actually need the answer but rather \
fail to fully understand their situation, take one too many steps in imitating \
familiar solutions, and end up asking the wrong question. This is known as the X/Y problem. \
\n\n\
On the other hand, just blurting \"X/Y!\" in response to any difficult question is \
less useful than direct answers to the bad question. In remembering my learning journey, \
when I'm posed a difficult technical question I like to ask back:\
\n\n\
> This is a difficult problem with no perfect solution, and it's often easy to avoid it altogether.\n\
> Is there any more context to this?",
"This airplane is the choice of professional pilots! \
The first thing we advise you to do is turn the yoke upside down, it's much more ergonomic like that. \
Also, you probably want to rip out the altimeter and replace it with a custom one that has more features. \
And bring your own GPS unit and tape it to the windshield, because this plane doesn't have one by default. \
And of course, remember that unlike in your car, 'left' actually means right in this plane. \
This is the professional way. Ah yes, when you fly over Canada, \
you will see a blinking warning sign telling you that you're out of fuel. \
That's just a small glitch caused by metric/imperial confusion. \
It has been known for 23 years but we haven't quite gotten around to fixing it yet. \
Don't worry, this plane is still excellent for professional pilots. \
If you feel otherwise, you are probably not a real professional.",
"Create React App doesn't support including source files from outside the src folder, or following symlinks.",
"Seven million years have passed since the first machines were erected by man. \
This once-holy land, now cursed with laze and oil, metals, \
hoped to fulfill menial tasks of increasingly insignificant bother. When bother became microcosmic, \
we tackled problems past the point of physical inconvenience: First, our sadness was tackled. \
Feelings injected into our daily routing, tricked the senses with light and sound. \
Fear was tackled next - information on everything, transparency to nothing, \
a false promise built on a whim by those with real power, power to produce comforting lies. \
Then our soul, ideals sold on providence and grandeur in a post-religion world, \
people found faith in new gods residing within the computer, scriptures in everyone's pocket, \
words of this holy book code, powerful enough to shift the seas without anyone noticing the gentle pull \
of its immeasurable strength. \
\n\n\
There once was a way for all the world to unify into one space, \
this coalescence led to the destruction of everything. \
\n\n\
@pukicho",
"The quickest way to oust yourself as a foreigner in England is to mention a place.",
"My informant at https://martinteoharov.com/ has an important message for you",
]
function break_lines(input: string, width: number): string {
return input
.split("\n")
.flatMap(l => {
let ret: string[] = [];
while (width < l.length) {
let split_point = l.lastIndexOf(" ", width);
if (split_point < 0) {
ret.push(l.slice(0, width));
l = l.slice(width);
} else {
ret.push(l.slice(0, split_point));
l = l.slice(split_point + 1);
}
}
ret.push(l);
return ret;
})
.join("\n")
}
export default function FortuneComponent(): React.ReactElement {
const id = Math.floor(Math.random() * messages.length);
window.localStorage.setItem("martinRefShown", id === 6 ? "yes" : "no");
const text = break_lines(messages[id], 60);
const width = text.split("\n").map(s => s.length).reduce((acc, l) => Math.max(acc, l))
return <>
<pre>
{" " + "_".repeat(width + 2) + "\n"}
{text.split("\n").map((l, i, a) => {
let [s, e] = a.length === 1 ? ["(", ")"]
: i === 0 ? ["/", "\\"]
: i + 1 === a.length ? ["\\", "/"]
: ["|", "|"]
return s + " " + l.padEnd(width) + " " + e + "\n"
})}
{" " + "-".repeat(width + 2)}
{`
l
l
.--.
|o_o |
|:_/ |
// l l
(| | )
/bl_ _/bl
l___)=(___/
`.replaceAll("b", "`").replaceAll("l", "\\")}
</pre>
</>
}

View File

@@ -4,14 +4,11 @@ import { defineCollection, z } from 'astro:content';
import { isValidTime } from './utils/time'; import { isValidTime } from './utils/time';
const blog = defineCollection({ const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
summary: z.string(), summary: z.string(),
image: z.string().optional(), image: z.string().optional(),
// Transform string to ZonedDateTime object
pubDate: z.string().refine(isValidTime), pubDate: z.string().refine(isValidTime),
updatedDate: z.string().refine(isValidTime).optional(), updatedDate: z.string().refine(isValidTime).optional(),
unlisted: z.boolean().optional(), unlisted: z.boolean().optional(),
@@ -20,4 +17,13 @@ const blog = defineCollection({
}), }),
}); });
export const collections = { blog }; const projects = defineCollection({
loader: glob({ base: './src/content/projects', pattern: '**/*.{md,mdx}' }),
schema: z.object({
name: z.string(),
url: z.string(),
image: z.string().optional(),
})
})
export const collections = { blog, projects };

View File

@@ -4,7 +4,7 @@ author: lbfalvy
tags: [programming, rust, langdev] tags: [programming, rust, langdev]
pubDate: 2025-01-29T11:27Z[UTC] pubDate: 2025-01-29T11:27Z[UTC]
summary: On the state of async Rust, limitations of the type system, and Iterator::map summary: On the state of async Rust, limitations of the type system, and Iterator::map
image: https://assets-for-gh-pages.gb-lon-1.linodeobjects.com/Screenshot%202025-01-29%20170323.png image: https://eu2.contabostorage.com/b5fca1793241407290cf2335ba076411:assets/lbfalvy_com/Screenshot 2025-01-29 170323.png
unlisted: false unlisted: false
--- ---
import Graphic from "../../components/Graphic.astro" import Graphic from "../../components/Graphic.astro"
@@ -20,7 +20,7 @@ In synchronous rust, `Iterator::map` takes an `FnMut`, a function which can only
fn map<B, F>(self, f: F) -> Map<Self, F> where F: FnMut(Self::Item) -> B; fn map<B, F>(self, f: F) -> Map<Self, F> where F: FnMut(Self::Item) -> B;
} }
``` ```
<p slot="label">source (modified): https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#745</p> <p slot="label">modified from the standard library</p>
</Graphic> </Graphic>
The async equivalent however has to take a function that returns some type that implements `Future` because that's how you statically type an asynchronous function, you parameterize on the state machine the compiler will eventually generate for its paused data. This is again perfectly normal, C++ coroutines do the same as I'm pretty sure every other language that supports any kind of stack-allocated coroutine has to. The problem emerges from lifetimes, because in order for that Future to hold onto a mutable reference, the async equivalent of map (which happens to be called `StreamExt::then` for reference) has to not only guarantee that the callback will not be running when its next called, but that its return value (the `Future` instance) will not exist (either because it's finished or because it's been freed) by the time the function is called again. The async equivalent however has to take a function that returns some type that implements `Future` because that's how you statically type an asynchronous function, you parameterize on the state machine the compiler will eventually generate for its paused data. This is again perfectly normal, C++ coroutines do the same as I'm pretty sure every other language that supports any kind of stack-allocated coroutine has to. The problem emerges from lifetimes, because in order for that Future to hold onto a mutable reference, the async equivalent of map (which happens to be called `StreamExt::then` for reference) has to not only guarantee that the callback will not be running when its next called, but that its return value (the `Future` instance) will not exist (either because it's finished or because it's been freed) by the time the function is called again.
@@ -41,7 +41,7 @@ The async equivalent however has to take a function that returns some type that
<p slot="label">source (modified): https://docs.rs/futures-util/0.3.31/src/futures_util/stream/stream/mod.rs.html#488</p> <p slot="label">source (modified): https://docs.rs/futures-util/0.3.31/src/futures_util/stream/stream/mod.rs.html#488</p>
</Graphic> </Graphic>
The type of the callback then _should be_ **some function which for any lifetime `'a` returns some type is valid for the same lifetime `'a`**. The type of the return value is parametric! The type of the callback then _should be_ **some function which for any lifetime `'a` returns some type that is valid for the same lifetime `'a`**. The type of the return value is parametric!
Since structs and functions can only be parametric on concrete types, not generics, a callback whose return type has a different contract depending on how you called the function is illegal on general. So if you want to access mutable data in an async stream, you have to make an ad-hoc `Mutex<&mut T>` right there on the stack which the closure and its return value can capture by shared reference and then immediately lock for its entire runtime. Streams are lazy and a new value will not be pulled until the current one is finished so this mutex can never ever be contested, but there is no way at all to explain this to the type system. Since structs and functions can only be parametric on concrete types, not generics, a callback whose return type has a different contract depending on how you called the function is illegal on general. So if you want to access mutable data in an async stream, you have to make an ad-hoc `Mutex<&mut T>` right there on the stack which the closure and its return value can capture by shared reference and then immediately lock for its entire runtime. Streams are lazy and a new value will not be pulled until the current one is finished so this mutex can never ever be contested, but there is no way at all to explain this to the type system.

View File

@@ -0,0 +1,6 @@
---
name: Orchid
url: https://github.com/lbfalvy/orchid
image: https://raw.githubusercontent.com/lbfalvy/orchid/refs/heads/master/icon.svg
---
An embeddable functional scripting language I've been developing for many years now both as a recreational activity and to learn Rust.

View File

@@ -0,0 +1,6 @@
---
name: f.engine
url: https://lbfalvy.github.io/f.engine/
image: https://eu2.contabostorage.com/b5fca1793241407290cf2335ba076411:assets/lbfalvy_com/f-engine.png
---
A small lambda calculus executor made in Javascript to demonstrate lambda calc to my friends. It works with repeated string splicing so it gets very slow.

View File

@@ -0,0 +1,5 @@
---
name: Mockable Timer
url: https://github.com/lbfalvy/mockable-timer
---
An API that replaces `Date.now()`, `setTimeout` and `setImmediate` for dependency injection, and a carefully crafted mock implementation with an easy to use interface.

View File

@@ -0,0 +1,5 @@
---
name: React Context Menu
url: https://www.npmjs.com/package/@lbfalvy/react-context-menu
---
A multilevel custom context menu for React. The API relies on React context so that menus can be combined within nested containers

View File

@@ -0,0 +1,5 @@
---
name: Simple RTR
url: https://github.com/lbfalvy/buffered-dispatch
---
A library for Refresh Token Rotation that can scale to many concurrent open tabs without a service worker.

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="600" height="530" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" fill="#1185fe"/>
</svg>

After

Width:  |  Height:  |  Size: 745 B

1
src/icons/Gitea_Logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" style="enable-background:new 0 0 640 640;" xml:space="preserve" viewBox="5.67 143.05 628.65 387.55"> <g> <path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/> <g> <g> <path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/> <path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"/> </g> </g> </g> </svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 94.33">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<path class="cls-1" d="M11.5,13.79c0-3.35,2.49-5.62,6.38-5.62h18.59c16.86,0,28.32,9.29,28.32,22.91s-11.35,23.34-28.32,23.34h-2.59c-6.49,0-9.84,3.35-9.84,8.75v15.24c0,4.32-2.49,7.02-6.27,7.02s-6.27-2.7-6.27-7.02V13.79ZM24.04,33.14c0,6.81,3.46,10.05,10.05,10.05h1.62c9.3,0,16.1-3.78,16.1-12.1s-6.81-12.1-16.1-12.1h-1.62c-6.59,0-10.05,3.24-10.05,10.05v4.11ZM64.79,79.5c0,3.57,2.49,5.94,6.27,5.94,2.49,0,4.76-1.62,6.05-4.97l2.27-6.05c1.84-4.86,5.08-7.13,8.65-7.13h20.54c3.57,0,6.81,2.27,8.65,7.13l2.27,6.05c1.3,3.35,3.57,4.97,6.05,4.97,3.78,0,6.27-2.38,6.27-5.94,0-.97-.22-2.16-.65-3.35l-24.54-63.76c-1.51-3.89-4.97-5.84-8.32-5.84s-6.81,1.95-8.32,5.84l-24.54,63.76c-.43,1.19-.65,2.38-.65,3.35ZM88.67,50.54c0-1.19.32-2.27.86-3.89l4.65-12.86c.86-2.49,2.38-3.67,4.11-3.67s3.24,1.19,4.11,3.67l4.65,12.86c.54,1.62.86,2.7.86,3.89,0,3.24-1.84,5.51-6.7,5.51h-5.84c-4.86,0-6.7-2.27-6.7-5.51ZM127.69,14.01c0-3.46,2.49-5.84,6.49-5.84h49.72c4,0,6.49,2.38,6.49,5.84s-2.49,5.84-6.49,5.84h-8.32c-6.59,0-10.16,3.35-10.16,11.02v47.44c0,4.43-2.49,7.13-6.38,7.13s-6.38-2.7-6.38-7.13V30.87c0-7.67-3.57-11.02-10.16-11.02h-8.32c-4,0-6.49-2.38-6.49-5.84ZM202.06,78.42c0,4.32,2.49,7.02,6.27,7.02s6.27-2.7,6.27-7.02v-17.18c0-4.86,2.81-6.92,6.27-6.92h.87c2.27,0,4.54,1.4,5.94,3.46l16.43,24.1c1.51,2.27,3.46,3.57,5.94,3.57,3.24,0,5.84-2.7,5.84-5.94,0-1.3-.43-2.7-1.4-4.11l-10.92-15.35c-1.3-1.84-1.84-3.35-1.84-4.65,0-2.7,2.38-4.65,5.19-6.7,5.08-3.78,10.59-8.75,10.59-18.26,0-13.29-10.38-22.26-27.45-22.26h-21.72c-3.89,0-6.27,2.27-6.27,5.62v64.63ZM214.59,32.17v-3.24c0-7.02,3.68-9.94,9.3-9.94h5.4c9.29,0,15.24,3.46,15.24,11.46s-6.27,11.67-15.56,11.67h-5.08c-5.62,0-9.3-2.92-9.3-9.94ZM273.5,78.21V13.79c0-3.35,2.38-5.62,6.27-5.62h40.86c3.89,0,6.27,2.27,6.27,5.62s-2.38,5.62-6.27,5.62h-25.83c-5.08,0-8.76,3.03-8.76,8.75v2.38c0,5.73,3.68,8.75,8.76,8.75h20c3.89,0,6.27,2.27,6.27,5.62s-2.38,5.62-6.27,5.62h-19.24c-5.08,0-9.51,3.13-9.51,9.51v3.03c0,6.38,4.43,9.51,9.51,9.51h25.08c3.89,0,6.27,2.27,6.27,5.62s-2.38,5.62-6.27,5.62h-40.86c-3.89,0-6.27-2.27-6.27-5.62ZM334.35,46c0-23.34,17.51-39.45,38.05-39.45s38.05,16.1,38.05,39.45-17.51,39.45-38.05,39.45-38.05-16.1-38.05-39.45ZM348.62,46c0,16.43,9.73,26.91,23.78,26.91s23.78-10.48,23.78-26.91-9.73-26.91-23.78-26.91-23.78,10.37-23.78,26.91ZM425.47,78.42c0,4.32,2.49,7.02,6.27,7.02s6.27-2.7,6.27-7.02v-33.07c0-4,2.38-5.94,4.86-5.94,1.95,0,3.57,1.08,4.76,3.03l20.75,34.69c2.81,4.76,5.4,8.32,10.7,8.32s8.76-3.67,8.76-9.62V13.58c0-4.32-2.49-7.02-6.27-7.02s-6.27,2.7-6.27,7.02v33.07c0,4-2.38,5.94-4.86,5.94-1.94,0-3.57-1.08-4.76-3.03l-20.75-34.69c-2.81-4.76-5.4-8.32-10.7-8.32s-8.76,3.67-8.76,9.62v62.25Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1 +0,0 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 960 B

5
src/icons/matrix.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="4 7 42 36" width="50px"
height="50px">
<path
d="M 5 5 A 1.0001 1.0001 0 0 0 4 6 L 4 44 A 1.0001 1.0001 0 0 0 5 45 L 8 45 A 1.0001 1.0001 0 1 0 8 43 L 6 43 L 6 7 L 8 7 A 1.0001 1.0001 0 1 0 8 5 L 5 5 z M 42 5 A 1.0001 1.0001 0 1 0 42 7 L 44 7 L 44 43 L 42 43 A 1.0001 1.0001 0 1 0 42 45 L 45 45 A 1.0001 1.0001 0 0 0 46 44 L 46 6 A 1.0001 1.0001 0 0 0 45 5 L 42 5 z M 31.074219 17.509766 C 29.975744 17.487506 28.868391 17.760297 27.978516 18.373047 C 27.407516 18.767047 26.915609 19.272813 26.349609 19.757812 C 25.488609 18.039813 23.929344 17.580781 22.152344 17.550781 C 20.351344 17.519781 18.920922 18.341797 17.669922 19.841797 L 17.669922 18 L 14 18 L 14 32 L 17.664062 32 C 17.664062 32 17.657969 26.766016 17.667969 24.166016 C 17.669969 23.704016 17.689203 23.23225 17.783203 22.78125 C 18.073203 21.39225 19.538031 20.534437 20.957031 20.648438 C 22.309031 20.757437 23.100016 21.495656 23.166016 23.097656 C 23.177016 23.376656 23.166016 32 23.166016 32 L 26.832031 32 L 26.832031 24.228516 C 26.838031 23.629516 26.901875 23.0175 27.046875 22.4375 C 27.372875 21.1375 28.590531 20.49825 30.019531 20.65625 C 31.279531 20.79525 32.239031 21.474609 32.332031 22.849609 L 32.332031 32 L 36 32 L 36 22 C 36 21 35.746359 20.490859 35.443359 19.880859 C 34.710859 18.405234 32.90501 17.546865 31.074219 17.509766 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,24 +1,60 @@
--- ---
import type { CollectionEntry } from 'astro:content'; import type { CollectionEntry } from "astro:content";
import Main from './Main.astro'; import Main from "./Main.astro";
import Time from '../components/Time.svelte'; import Time from "../components/Time.svelte";
import { Image } from "astro:assets";
import patreonWordmark from "../icons/PATREON_WORDMARK_1_WHITE_RGB.svg";
type Props = CollectionEntry<'blog'>['data']; type Props = CollectionEntry<"blog">["data"];
const { title, author, summary, pubDate, updatedDate, image } = Astro.props; const { title, author, summary, pubDate, updatedDate, image } = Astro.props;
--- ---
<Main title={title} description={summary} image={image}> <Main title={title} description={summary} image={image}>
<article class="m-5 mt-3"> <article class="m-5 mt-3">
<header class="lg:grid grid-cols-[auto_auto_minmax(300px,_1fr)] grid-rows-[auto_auto]"> <header
class="lg:grid grid-cols-[auto_auto_minmax(300px,1fr)] grid-rows-[auto_auto]"
>
<h2 class="font-bold row-span-2 text-2xl m-2 mt-3">{title}</h2> <h2 class="font-bold row-span-2 text-2xl m-2 mt-3">{title}</h2>
<address class="post-meta inline-block col-start-2">{author}</address> <address class="post-meta inline-block col-start-2">{author}</address>
<Time client:load datetime={pubDate} /> <Time client:load datetime={pubDate} />
<div class="italic tracking-[3px] text-emph-fg col-start-2 col-span-2 m-2 mt-0">{summary}</div> <div
{updatedDate && <div>Amended <Time datetime={updatedDate} /></div>} class="italic tracking-[3px] text-emph-fg col-start-2 col-span-2 m-2 mt-0"
>
{summary}
</div>
{
updatedDate && (
<div>
Amended <Time datetime={updatedDate} />
</div>
)
}
</header> </header>
<hr class="mb-3"> <hr class="mb-3" />
<main class="max-w-[100ch] font-prose post-content"> <main class="max-w-[100ch] font-prose post-content">
<slot /> <slot />
</main> </main>
<footer class="mt-20">
<p class="post-meta">
I pledge to spend all the extra free time on silly experiments.
</p>
<a href="https://ko-fi.com/D1D4AFWZX" target="_blank">
<img
class="inline-block border-0 h-6"
src="https://storage.ko-fi.com/cdn/kofi1.png?v=6"
alt="Buy Me a Coffee at ko-fi.com"
/>
</a>
<a
href="https://patreon.com/lbfalvy?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink"
>
<Image
class="inline-block border-0 h-4 w-22.5 object-cover ml-3"
src={patreonWordmark}
alt="Support me on Patreon"
/>
</a>
</footer>
</article> </article>
</Main> </Main>

View File

@@ -1,10 +1,12 @@
--- ---
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import NavLink from "../components/NavLink.astro"; import NavLink from "../components/NavLink.astro";
import Layout from "../layouts/Html.astro" import Layout from "../layouts/Html.astro";
import "../styles/global.css"; import "../styles/global.css";
import GhLogo from "../icons/github-mark-white.svg"; import GiteaLogo from "../icons/Gitea_Logo.svg";
import RssLogo from "../icons/rss.svg"; import RssLogo from "../icons/rss.svg";
import BskyLogo from "../icons/Bluesky_Logo.svg";
import MatrixLogo from "../icons/matrix.svg";
import { SITE_DESCRIPTION, SITE_TITLE } from "../consts"; import { SITE_DESCRIPTION, SITE_TITLE } from "../consts";
interface Props { interface Props {
@@ -18,40 +20,84 @@ const {
description = SITE_DESCRIPTION, description = SITE_DESCRIPTION,
image, image,
} = Astro.props; } = Astro.props;
--- ---
<Layout title={title} description={description} image={image}> <Layout title={title} description={description} image={image}>
<Fragment slot="head"> <Fragment slot="head">
<meta name="theme-color" content="#222" /> <meta name="theme-color" content="#222" />
<link rel="icon" type="image/png" href="https://github.com/lbfalvy.png"/> <link rel="icon" type="image/png" href="https://github.com/lbfalvy.png" />
</Fragment> </Fragment>
<div class=" <div
w-screen h-screen class="w-screen h-screen
flex flex-col flex flex-col
md:grid md:grid
md:grid-rows-[1fr_auto] md:grid-cols-[min-content_auto] md:grid-rows-[1fr_auto] md:grid-cols-[min-content_auto]"
"> >
<header class=" <header
emph-bg whitespace-nowrap md:px-3 py-2 flex flex-col class="emph-bg whitespace-nowrap md:px-3 py-2 flex flex-col
text-right text-right"
"> >
<h1 class="font-bold text-2xl m-2 mr-0 text-wrap lg:text-nowrap">Lawrence Bet…</h1> <h1 class="font-bold text-2xl m-2 mr-0 text-wrap lg:text-nowrap">
<nav class="gutter Lawrence Bet…
</h1>
<nav
class="gutter
flex md:flex-col flex md:flex-col
text-center md:text-right"> text-center md:text-right"
>
<NavLink href="/" zone="/blog">Blog</NavLink> <NavLink href="/" zone="/blog">Blog</NavLink>
<NavLink href="/projects">Projects</NavLink> <NavLink href="/projects">Projects</NavLink>
<NavLink href="/about">About me</NavLink> <NavLink href="/about">About me</NavLink>
<NavLink href="/fortune">Fortune</NavLink> <NavLink href="/fortune">Fortune</NavLink>
</nav> </nav>
</header> </header>
<main id="scroll-area" class="md:row-start-1 md:col-start-2 md:row-span-2 md:overflow-y-auto"> <main
id="scroll-area"
class="md:row-start-1 md:col-start-2 md:row-span-2 md:overflow-y-auto"
>
<slot /> <slot />
</main> </main>
<footer class="md:row-start-2 emph-bg flex justify-center gap-3 py-3"> <footer class="md:row-start-2 emph-bg flex justify-center gap-3 py-3">
<a href="https://github.com/lbfalvy"><Image height="20" width="20" src={GhLogo} alt="Github"/></a> <a href="https://git.lbfalvy.com">
<a href="/rss.xml"><Image height="20" width="20" src={RssLogo} alt="Rss"/></a> <Image
height="20"
width="34"
src={GiteaLogo}
alt="Gitea"
loading="eager"
/>
</a>
<a href="/rss.xml">
<Image height="20" width="20" src={RssLogo} alt="Rss" loading="eager" />
</a>
<a href="https://bsky.app/profile/lbfalvy.com">
<Image
height="20"
width="25"
src={BskyLogo}
alt="BlueSky"
loading="eager"
/>
</a>
<a href="https://fosstodon.org/@lbfalvy">
<img
src="https://cdn.fosstodon.org/site_uploads/files/000/000/007/48/91cda647d1c18689.png"
alt="Fosstodon"
height="20"
width="20"
loading="eager"
/>
</a>
<a href="https://matrix.to/#/@lbfalvy:matrix.org">
<Image
height="20"
width="22"
color="white"
src={MatrixLogo}
alt="Chat over Matrix"
loading="eager"
/>
</a>
</footer> </footer>
</div> </div>
</Layout> </Layout>
@@ -59,9 +105,9 @@ const {
<script> <script>
const key = `scroll:${window.location.pathname}`; const key = `scroll:${window.location.pathname}`;
const scrollArea = document.getElementById("scroll-area"); const scrollArea = document.getElementById("scroll-area");
scrollArea?.addEventListener('scrollend', () => { scrollArea?.addEventListener("scrollend", () => {
sessionStorage.setItem(key, scrollArea.scrollTop.toString()) sessionStorage.setItem(key, scrollArea.scrollTop.toString());
}) });
const savedPos = sessionStorage.getItem(key); const savedPos = sessionStorage.getItem(key);
if (savedPos) scrollArea!.scrollTop = Number.parseFloat(savedPos); if (savedPos) scrollArea!.scrollTop = Number.parseFloat(savedPos);
</script> </script>

View File

@@ -1,40 +1,34 @@
--- ---
import { Image } from "astro:assets";
import Main from "../layouts/Main.astro"; import Main from "../layouts/Main.astro";
--- ---
<Main <Main title="About Me" description="Lawrence Bethlenfalvy, software engineer">
title="About Me"
description="Lawrence Bethlenfalvy, software engineer"
>
<div class="max-w-[80ch] m-5"> <div class="max-w-[80ch] m-5">
<img src='https://github.com/lbfalvy.png' <Image
class="rounded-full h-30 float-right m-3 [shape-outside:_ellipse()]" /> src="https://github.com/lbfalvy.png"
loading="eager"
alt="My face"
width="120"
height="120"
class="rounded-full h-30 float-right m-3 [shape-outside:ellipse()]"
/>
<p> <p>
My name is Lawrence Bethlenfalvy, I make websites and web-based applications primarily My name is Lawrence Bethlenfalvy, I make websites and web-based
with React. I enjoy seeing the fruit of my labour, which is why I do so much applications primarily with React. I enjoy seeing the fruits of my labour,
frontend development even though I'm not exactly an artistic genius, as finest which is why I do so much frontend development even though I'm not exactly
demonstrated by this website. an artistic genius, as finest demonstrated by this website.
</p> </p>
<p> <p>
I really like perfectly designed infrastructure, and in an effort to I like well-oiled infrastructure, and in an effort to construct it for my
construct it for my own projects I've racked up a considerable amount of own projects I've racked up a considerable amount of DevOps experience.
DevOps experience.
</p> </p>
<p> <p>
I studied a lot of advanced mathematical topics in high school, and although More recently I've been hard at work on my small conceptual language,
I was never a big fan of solving equations for hours on end the approach and Orchid, which, like anything I do, tries to be unopinionated, minimalistic
some of the concepts stuck with me. I like to draw on mathematical techniques in design, and robust in execution. Incremental progress is fundamentally
for day-to-day problem solving, but I also view it as a hobby. incompatible with the optimal, holistic approach I like, but when I have
</p> something to report I do it here.
<p>
More recently I've been hard at work on my small conceptual language, Orchid,
which, like anything I do, tries to be unopinionated, minimalistic in design,
and robust in execution.
Designing a programming language is a constant goat game against the halting problem
so realising all these principles at the same time is nigh impossible, and incremental
progress is fundamentally incompatible with the optimal, holistic approach to
issues like type checking, but when I have something to report I do it here.
</p> </p>
</div> </div>
</Main> </Main>

8
src/pages/fortune.astro Normal file
View File

@@ -0,0 +1,8 @@
---
import Main from "../layouts/Main.astro";
import FortuneComponent from "../components/Fortune";
---
<Main title="Fortune" description="Wisdom from Tux">
<FortuneComponent client:only="react" />
</Main>

37
src/pages/projects.astro Normal file
View File

@@ -0,0 +1,37 @@
---
import { getCollection, render } from "astro:content";
import Main from "../layouts/Main.astro";
import { Image } from "astro:assets";
const projects = await getCollection("projects");
const projReady = await Promise.all(
projects.map(async (proj) => [proj, (await render(proj)).Content] as const),
);
---
<Main>
<div class="grid xs:grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 max-w-[100ch]">
{
projReady.map(([proj, Content]) => (
<a href={proj.data.url}>
<article class="emph-bg m-3 p-2 text-sm rounded-2xl">
{proj.data.image && (
<Image
src={proj.data.image}
alt=""
loading="eager"
width="100"
height="60"
class="object-center aspect-video w-full"
/>
)}
<h2 class="font-bold text-xl italic">{proj.data.name}</h2>
<div>
<Content />
</div>
</article>
</a>
))
}
</div>
</Main>

View File

@@ -80,6 +80,7 @@
} }
} }
@theme { @theme {
--breakpoint-xs: 25rem;
--tw-border-style: solid; --tw-border-style: solid;
--color-emph-bg: #181818; --color-emph-bg: #181818;
--color-side-bg: #333; --color-side-bg: #333;
@@ -100,7 +101,6 @@
color: var(--color-faint-fg); color: var(--color-faint-fg);
font-style: italic; font-style: italic;
font-weight: 500; font-weight: 500;
white-space: nowrap;
margin-left: 0.3em; margin-left: 0.3em;
} }
.summary { .summary {

View File

@@ -4,13 +4,18 @@ function lt(one: Temporal.Duration, other: Temporal.DurationLike): boolean {
return Temporal.Duration.compare(one, other) < 0 return Temporal.Duration.compare(one, other) < 0
} }
export function printTime(time: Temporal.ZonedDateTime): string { export function printTime(datetime: Temporal.ZonedDateTime): string {
const delta = time.until(Temporal.Now.zonedDateTimeISO()) const delta = datetime.until(Temporal.Now.zonedDateTimeISO())
if (lt(delta, { minutes: 1 })) return 'now' if (lt(delta, { minutes: 1 })) return 'now'
if (lt(delta, { minutes: 2 })) return '1 minute ago'
if (lt(delta, { hours: 1 })) return `${delta.minutes} minutes ago` if (lt(delta, { hours: 1 })) return `${delta.minutes} minutes ago`
if (lt(delta, { hours: 2 })) return `1 hour ago`
if (lt(delta, { days: 1 })) return `${delta.hours} hours ago` if (lt(delta, { days: 1 })) return `${delta.hours} hours ago`
if (lt(delta, { days: 2 })) return `yesterday`
if (lt(delta, { days: 7 })) return `${delta.round({ smallestUnit: 'days' }).days} days ago` if (lt(delta, { days: 7 })) return `${delta.round({ smallestUnit: 'days' }).days} days ago`
return `at ${time.toPlainDate().toString()} ${time.toPlainTime().toString({ smallestUnit: 'minutes' })}` const date = datetime.toPlainDate().toString();
const time = datetime.toPlainTime().toString({ smallestUnit: 'minutes' });
return `at ${date} ${time}`
} }
export function parseTime(string: string): Temporal.ZonedDateTime { export function parseTime(string: string): Temporal.ZonedDateTime {

View File

@@ -1,5 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
}

View File

@@ -12,11 +12,18 @@
"editor.wordWrap": "bounded", "editor.wordWrap": "bounded",
"editor.wordWrapColumn": 80, "editor.wordWrapColumn": 80,
"editor.lineNumbers": "off", "editor.lineNumbers": "off",
"editor.rulers": [],
"editor.quickSuggestions": { "editor.quickSuggestions": {
"comments": "off", "comments": "off",
"strings": "off", "strings": "off",
"other": "off" "other": "off"
} }
} }
},
"extensions": {
"recommendations": [
"bradlc.vscode-tailwindcss",
"svelte.svelte-vscode"
]
} }
} }