Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
770cca3259 add custom fonts
All checks were successful
Deploy Blog / deploy (push) Successful in 1m56s
2026-03-24 17:42:26 +00:00
febb0af236 blog tweak
All checks were successful
Deploy Blog / deploy (push) Successful in 2m8s
2026-03-16 19:42:23 +00:00
1a358c64dd complete blog
All checks were successful
Deploy Blog / deploy (push) Successful in 2m7s
2026-03-16 19:33:58 +00:00
91d7c965a7 interim commit 2026-03-13 18:55:40 +00:00
a52c80333d further restyle tweaks
All checks were successful
Deploy Blog / deploy (push) Successful in 3m7s
2026-03-13 18:40:02 +00:00
dbf853be9e restyle tweaks
All checks were successful
Deploy Blog / deploy (push) Successful in 2m18s
2026-03-11 16:06:45 +00:00
f62963f5c9 complete restyle
All checks were successful
Deploy Blog / deploy (push) Successful in 2m16s
2026-03-08 19:08:26 +00:00
27 changed files with 507 additions and 468 deletions

53
@s
View file

@ -1,53 +0,0 @@
---
title: "Replacing garage guttering"
slug: /replacing-garage-guttering/
date: 2025-12-06
tags: ["projects", "DIY"]
---
I faced the following problems with the guttering on my garage:
- It had been bent out of shape by the wind
- The seals on the joins had worn away causing leaks
- There was sitting water that wasn't making it to the downpipe
- It was old and gross
Whenever it rained heavily, these problems would compound and lead to water
pouring over onto the garage brickwork.
The sitting water was caused by the lack of a sufficient drop from the union
join with my neighbour's gutter to the downpipe. A further impediment was that
the water had to turn a 90 degree angle, around the side of the garabe, before
reaching the downpipe. As a result, water was only making it to the downpipe
when there was very heavy rain and/or high winds. During normal drip-drainage of
the daily dew condensation on the roof, the water was just pooling in the
gutter.
I decided to redesign the passage to the downpipe. Instead of trying to make the
water turn a bend I thought it would be better to work _with_ gravity and have
the drop start at the end of the guttering, not around the corner. This way, the
water would have increased velocity at the beginning of its descent into the
downpipe.
By fashioning a "swans neck" sequence of joins, the downpipe now turns the
corner _during_ descent and is fed downwards along the wall to the water butt.
This has been working very well and the water no longer pools. I've noticed
however that condensation forms on the underside of the downpipe. This doesn't
look great and I worry about it wearing away the sealant I have applied at the
joins.
Overall, however I think it looks much neater as well as being more satisfying
from an engineering perspective. The white half-round gutters blend in nicely
with the neighbours' and look a lot cleaner.
I didn't keep track of costs for this project. I think in total it cost around
£80. This included the cost of the Floplast guttering and fixtures, the downpipe
and the protective mesh I applied to the top of the gutters to prevent blockages
from leaves and roof moss.
In order to drill the fixtures into the garage masonry I needed a more powerful
drill than my 18V battery-powered Erbauer. I bought this a few years ago before
I knew much about brands and power-tool quality. So I bought a wired Makita
hammer-drill for around £90. I think when I buy new power-tools in future I will
stick to Makita. The build quality and performance is excellent.

10
package-lock.json generated
View file

@ -80,7 +80,6 @@
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@ -2263,7 +2262,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.7.tgz",
"integrity": "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tanstack/query-core": "5.90.7"
},
@ -2419,7 +2417,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -2532,7 +2529,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
@ -2951,7 +2947,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -4386,7 +4381,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -4396,7 +4390,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@ -4814,7 +4807,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -5004,7 +4996,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@ -5093,7 +5084,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},

View file

@ -0,0 +1,78 @@
---
title: "Amateur radio licence"
slug: /amateur-radio-licence/
date: 2026-03-12
tags: ["radio"]
---
In February I bit the bullet and booked the exam for the UK Amateur Radio
Licence (Foundation Level).
Having the exam in the calendar helped me concentrate and remain focused, as my
learning style is naturally quite slow and meandering. I really wanted to earn
my licence so that I could get on the air and begin to advance my practical
knowledge.
I used the official Radio Society of Great Britain (RSGB) textbook and a
simplified syllabus from [Essex Ham Train](https://www.essexham.co.uk/train/).
![Flash cards from my studying](./img/flash-cards.jpg)
In the mocks, I was averaging an 80% pass rate but in the exam itself I did
particularly well and scored 92%, getting only two questions wrong (one of which
didn't come up at all in the training materials). I completed the exam remotely
but there was an RSGB invigilator observing my screen and environment to check I
wasn't cheating! (Thank you Malcolm, G3ZNU.)
Having received my certificate I was able to apply to Ofcom for my callsign. I
chose **M7SYO**. The first two characters are mandatory and denote my
qualification level ("Foundation") and the last three are your personal choice,
based on what is available. Luckily I was able to choose letters reflecting
"Systems Obscure". When appropriate to do so, I can add "E" as my regional
secondary locator to indicate that I am operating from England (e.g. ME7SYO).
The Foundation level is pretty generous given that it is the entry-level. I can
transmit up to 25W and am permitted to use most of the amateur bands (10m, 12m,
15m, 17m, 20m, 30m, 40m, 80m) at HF, apart from 60m, and many of the VHF/UHF
bands.
I am lucky that my uncle, Greg, is an experienced amateur operator and secretary
of his local amateur radio club in Macclesfield. He has been helping me with my
practical knowledge and advising on what gear to start with. For now, I have
ordered the affordable and well-regarded
[Baofeng UV-5RM Plus](https://www.baofengradio.com/products/uv-5rm-plus-8w-multi-band-radio)
handheld along with a NA-771 antenna. This will enable me to connect to
repeaters and hopefully make my first QSOs (contacts). I also plan to construct
some home-made antennas that I can attach to the handheld and experiment with.
![Monitoring over SDR](./img/monitoring-sdr.jpg)
As I'm still waiting for the radio to arrive, I am currently limited to
monitoring transmissions via my
[SDR](https://en.wikipedia.org/wiki/Software-defined_radio#Amateur_and_home_use)
(RTL-SDR v.4). I've been able to pick up some Morse conversations and a faint
read of the strange - possibly Iranian - number station that is currently
[perplexing the amateur community](https://youtu.be/ErmbTpxAM7Q?si=auxTnY8HSSnu1xZ5).
In order to access the HF band and reach contacts further afield by exploiting
[ionospheric propagation](https://www.qsl.net/4x4xm/Propagation/Ionosphere-propagation-of-radio-waves.htm),
I will need a better and more powerful tranceiver. I am looking into getting a
second-hand portable Yaesu through one of Greg's contacts. My plan is to drive
or cycle to good spots locally and work in the field. I also eventually want to
communicate over CW using Morse code and experiment with some radio astronomy.
The possibilities are pretty limitless with this hobby!
![The new to me X1 and some light reading](./img/thinkpad-x1-carbon.jpg)
To facilitate my field work I wanted a small and robust laptop so I bought a
second hand ThinkPad X1 Carbon (Gen.9). It's very sleek compared to my T15 which
I mainly use as a desktop device. I'm running Fedora Workstation for a
hassle-free and easily maintainable Linux environment.
Finally, I joined the RSGB. This gives me access to lots of useful radio
resources, free access to the
[National Radio Center](https://rsgb.org/main/about-us/national-radio-centre-gb3rs/)
at Bletchley Park, and the print edition of the monthly _RadCom_ magazine which
is a really good read.
Once I start transmitting I will report here on my progress...

BIN
posts/img/flash-cards.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

Binary file not shown.

View file

@ -13,7 +13,7 @@ const renderer = {
}
const highlighter = await createHighlighter({
themes: ["gruvbox-dark-hard"],
themes: ["light-plus"],
langs: [
"javascript",
@ -49,7 +49,7 @@ const posts = files.map((file) => {
(match, lang, code) => {
return highlighter.codeToHtml(code.trim(), {
lang: lang || "text",
theme: "gruvbox-dark-hard",
theme: "light-plus",
transformers: [transformerColorizedBrackets()],
})

View file

@ -1,29 +1,35 @@
import MetricBar from "./MetricBar"
const LanguagesChart = ({ chartData, error }) => {
return (
<div className="bg-sidebar p-3 my-4">
<div className="text-muted-foreground text-sm pb-2">
programming languages
</div>
return (
<div style={{ marginTop: "1rem" }}>
<div
className="code-stat-label"
style={{
paddingBottom: ".5rem",
marginBottom: ".5rem",
}}
>
Programming languages
</div>
{error ? (
<div>Data could not be found!</div>
) : !chartData?.length ? (
<div>No data for time period.</div>
) : (
chartData.map((x) => (
<MetricBar
key={x.language}
metric={x.language}
hours={x.hours}
percentage={x.percentage}
color="#458588"
/>
))
)}
</div>
)
{error ? (
<div>Data could not be found!</div>
) : !chartData?.length ? (
<div>No data for time period.</div>
) : (
chartData.map((x) => (
<MetricBar
key={x.language}
metric={x.language}
hours={x.hours}
percentage={x.percentage}
color="black"
/>
))
)}
</div>
)
}
export default LanguagesChart

View file

@ -1,37 +1,36 @@
const MetricBar = ({ metric, hours, percentage, color }) => (
<div style={{ marginBottom: "12px" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "4px",
fontSize: "14px",
}}
>
<span style={{}}>{metric}</span>
<span style={{ color: "#bdae93" }}>
{hours}h ({percentage}%)
</span>
</div>
<div
style={{
width: "100%",
height: "8px",
backgroundColor: "#32302f",
borderRadius: "4px",
overflow: "hidden",
}}
>
<div
style={{
width: `${percentage}%`,
height: "100%",
backgroundColor: color,
transition: "width 0.3s ease",
}}
/>
</div>
</div>
<div style={{ marginBottom: "12px" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "4px",
fontSize: "14px",
}}
>
<span style={{}}>{metric}</span>
<span style={{ color: "black" }}>
{hours}h ({percentage}%)
</span>
</div>
<div
style={{
width: "100%",
height: "8px",
backgroundColor: "lightgrey",
overflow: "hidden",
}}
>
<div
style={{
width: `${percentage}%`,
height: "100%",
backgroundColor: color,
transition: "width 0.3s ease",
}}
/>
</div>
</div>
)
export default MetricBar

View file

@ -1,30 +1,38 @@
import MetricBar from "./MetricBar"
const ProjectsChart = ({ chartData, error }) => {
return (
<div className="bg-sidebar p-3 my-4">
<div className="text-muted-foreground text-sm pb-2">projects</div>
return (
<div style={{ marginTop: "1rem" }}>
<div
className="code-stat-label"
style={{
paddingBottom: ".5rem",
marginBottom: ".5rem",
}}
>
Repos
</div>
{error ? (
<div>Data could not be found!</div>
) : !chartData?.length ? (
<div>No data for time period.</div>
) : (
chartData.map((x) => (
<MetricBar
key={x.project}
metric={x.project}
hours={x.hours}
percentage={x.percentage}
color="#fe8019"
/>
))
)}
<div className="text-sm text-muted-foreground">
Data excludes workplace repos.
</div>
</div>
)
{error ? (
<div>Data could not be found!</div>
) : !chartData?.length ? (
<div>No data for time period.</div>
) : (
chartData.map((x) => (
<MetricBar
key={x.project}
metric={x.project}
hours={x.hours}
percentage={x.percentage}
color="black"
/>
))
)}
<div className="code-stats-disclaimer">
Data excludes workplace repos.
</div>
</div>
)
}
export default ProjectsChart

View file

@ -1,8 +1,8 @@
const Scorecard = ({ title, metric }) => {
return (
<div className="bg-sidebar p-3">
<div className="text-muted-foreground text-sm">{title}</div>
<div className="text-lg">{metric}</div>
<div className="code-stat-grid">
<div className="code-stat-label">{title}</div>
<div className="code-stat-metric">{metric}</div>
</div>
)
}

View file

@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query"
import wakapiApi from "../api/wakapi-api"
import { convertDateFriendly } from "../utils/convertDate"
import Scorecard from "../components/Scorecard"
@ -32,7 +33,9 @@ const CodeStats = () => {
data &&
data?.projects.filter(
(project) =>
!project.key.includes("gp-") && !project.key.includes("unknown")
!project.key.includes("gp-") &&
!project.key.includes("unknown") &&
!project.key.includes("amber")
)
const personalProjectsSorted =
@ -67,45 +70,52 @@ const CodeStats = () => {
.slice(0, 4)
return (
<div className="container mx-auto py-4 px-1 md:p-4 grow ">
<div className="space-my-8">
<section className="container">
<div className="flex flex-col md:flex-row items-start md:items-center md:justify-between">
<h2 className="text-2xl font-semibold mb-4 text-[#458588]! h2-home scanlined px-2">
{`code stats`}
</h2>
<div className="mb-4 text-sm text-muted">
{convertDateFriendly(data?.from)} -{" "}
{convertDateFriendly(data?.to)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Scorecard
title="time coding"
metric={
error ? "Error" : isLoading ? "Loading..." : grandTotalFormatted
}
/>
<div className="code-stats-sect">
<div className="">
<section className="">
<div className="">
<h2 className="big-title">{`Code this month`}</h2>
<Scorecard
title="main project"
metric={error ? "Error" : isLoading ? "Loading..." : mainProject}
/>
<Scorecard
title="OS"
metric={error ? "Error" : isLoading ? "Loading..." : osMetric}
/>
</div>
<LanguagesChart chartData={languagesChartData} error={error} />
<ProjectsChart chartData={projectsChartData} error={error} />
<div className="text-sm text-center text-muted-foreground">
Data sourced from my self-hosted{" "}
<a
href="https://wakapi.dev/"
target="__blank"
className="underline decoration-1 hover:text-primary/80 underline-offset-4 text-primary"
{/*
<table
border="1"
width="100%"
style={{ borderCollapse: "collapse" }}
>
<tr>
<td style={{ paddingLeft: "4px" }}>Period:</td>
<td style={{ paddingLeft: "4px" }}>
{convertDateFriendly(data?.from)} -{" "}
{convertDateFriendly(data?.to)}
</td>
</tr>
<tr>
<td style={{ paddingLeft: "4px" }}>Total time:</td>
<td style={{ paddingLeft: "4px" }}>
{error
? "Error"
: isLoading
? "Loading..."
: grandTotalFormatted}
</td>
</tr>
<tr>
<td style={{ paddingLeft: "4px" }}>Main project:</td>
<td style={{ paddingLeft: "4px" }}>
{error ? "Error" : isLoading ? "Loading..." : mainProject}
</td>
</tr>
</table>
*/}
</div>
<LanguagesChart chartData={languagesChartData} error={error} />
<ProjectsChart chartData={projectsChartData} error={error} />
<div className="code-stats-disclaimer">
Data sourced from my self-hosted{" "}
<a href="https://wakapi.dev/" target="__blank" className="">
Wakapi
</a>{" "}
instance.

View file

@ -4,40 +4,34 @@ import { Link } from "react-router"
import { convertDate } from "@/utils/convertDate"
const PostListing = ({ posts, title, showAllButton }) => {
return (
<div className="container mx-auto py-4 px-1 md:p-4 grow">
<div className="space-my-8">
<section className="container">
<h2 className="text-2xl font-semibold mb-4 block h2-home scanlined px-2">
{`${title}`}
</h2>
{posts.map((post) => (
<ul>
<li className="mb-4">
<div className="flex justify-between flex-col relative hover:bg-[#504945]">
<span className="overflow-hidden whitespace-nowrap text-muted-foreground shrink-0 condensed">
{convertDate(post.date)}
</span>
<Link
to={`/posts/${post.slug}`}
key={post.slug}
className="overflow-hidden text-ellipsis whitespace-nowrap min-w-0"
>
{post.title}
</Link>
</div>
</li>
</ul>
))}
</section>
</div>
</div>
)
return (
<div className="">
<div className="">
<section className="">
<h2 className="big-title">{`${title}`}</h2>
{posts.map((post) => (
<ul className="no-bullets">
<li className="">
<div className="post-listing-item">
<span style={{ marginRight: "1rem" }}>
{convertDate(post.date)}
</span>
<Link to={`/posts/${post.slug}`} key={post.slug} className="">
{post.title}
</Link>
</div>
</li>
</ul>
))}
</section>
</div>
</div>
)
}
export default PostListing
{
/*
/*
<Link
to={`/posts/${post.slug}`}

View file

@ -1,94 +1,149 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap");
@import "./styles/_variables.css";
@import "tailwindcss";
@import "tw-animate-css";
@media (min-width: 768px) {
main {
max-width: 768px;
margin: 0 1.5rem;
}
* {
outline-color: color-mix(in srgb, var(--ring) 50%, transparent);
.post-listing-item {
display: flex;
flex-direction: row;
}
}
@media (max-width: 768px) {
.post-listing-item {
display: flex;
flex-direction: column;
}
.title-link {
letter-spacing: 0 !important;
}
}
.post-listing-item {
display: flex;
}
html {
font-family: var(--font-sansserif);
font-family: "DejaVu Sans", sans-serif;
}
body {
background-color: var(--background);
color: var(--foreground);
.header-links {
display: flex;
list-style: none;
gap: 1rem;
margin: 0;
padding: 0;
}
.condensed {
font-family: "Inter";
.plain-link {
color: inherit;
text-decoration: none;
}
.no-bullets {
list-style: none;
padding-left: 0;
}
blockquote {
font-style: italic;
}
figcaption {
font-weight: 500;
font-family: "Inter";
}
h1 {
color: var(--color-orange-light);
font-family: "Inter";
}
h2 {
font-family: "Inter";
color: var(--color-green-light);
}
.h2-home {
font-family: "Inter";
font-weight: 600;
}
h3 {
font-family: "Inter";
font-weight: 600 !important;
}
.monospaced-font {
font-family: "iA Writer Mono";
}
.scanlined {
position: relative;
/* Add this */
}
.scanlined::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(rgba(0, 0, 0, 0.4) 1px, transparent 1px);
background-size: 2px 2px;
background-repeat: repeat;
pointer-events: none;
z-index: 9999;
/* Might want to lower this too */
}
code {
font-family: var(--font-monospaced);
}
p code {
color: var(--foreground);
background: #504945;
text-align: center;
font-style: italic;
font-size: 14px;
padding: 0.2rem 0.3rem;
border-radius: var(--radius);
font-weight: 500;
}
figure {
text-align: center;
}
figure img {
width: 100%;
max-width: 500px;
min-width: 300px;
height: auto;
}
.shiki {
padding: 1rem 1.2rem;
border-radius: 0;
overflow-x: auto;
margin: 1.5rem 0;
line-height: 1.3;
/* counter-reset: line; */
font-family: var(--font-monospaced) !important;
font-size: 14px !important;
padding: 1rem;
/* border: 1pt solid black; */
background-color: whitesmoke !important;
overflow: auto;
}
.code-stats-sect {
font-family: sans-serif !important;
}
.code-stat-grid {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.code-stat-label {
font-weight: bold;
font-family: sans-serif;
}
.code-stats-disclaimer {
font-style: italic;
font-size: 14px;
text-align: center;
}
.about-li-padding {
padding-right: 1rem;
}
h1.site-title,
h1.post-title {
margin-bottom: 0.75rem;
}
.big-title {
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
border-bottom: 1px solid black;
}
.blog-meta {
font-size: 14px;
}
@font-face {
font-family: "DejaVu Sans";
src: url("/fonts/DejaVuSans.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DejaVu Sans";
src: url("/fonts/DejaVuSans-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DejaVu Sans";
src: url("/fonts/DejaVuSans-Oblique.woff2") format("woff2");
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "DejaVu Sans";
src: url("/fonts/DejaVuSans-BoldOblique.woff2") format("woff2");
font-weight: 700;
font-style: italic;
font-display: swap;
}

View file

@ -4,16 +4,16 @@ import portrait from "../images/portrait-compressed.jpg"
const AboutPage = () => {
return (
<MainTemplate>
<div className="container mx-auto py-4 px-1 md:p-4">
<figure className="w-full flex flex-col items-center mb-6">
<div className="scanlined inline-block">
<div className="">
<figure className="">
<div className="">
<img
alt="A portrait of the blog author"
src={portrait}
className="w-80 flex"
style={{ width: '25%' }}
/>
</div>
<figcaption className="text-sm text-muted-foreground mt-3 text-center">
<figcaption className="">
Pictured with the WITCH computer at the{" "}
<a
href="https://www.tnmoc.org/"
@ -57,29 +57,33 @@ const AboutPage = () => {
>
Arria NLG
</a>{" "}
and in several web developer roles. Before software I was a
and in several web developer roles. Before software, I was a
teacher.{" "}
</p>
<p>I am licenced amateur radio operator. My callsign is ME7SYO.</p>
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
Some things I like:
<ul className="pt-2">
<li className="mb-1">🐶 Staffies and other bull-breeds</li>
<li className="mb-1">🎼 Classical music (Haydn, Mozart, JSB)</li>
<li className="mb-1">🛸 Science fiction </li>
<ul className="no-bullets">
<li className=""><span className="about-li-padding">🐶</span>Staffies and other bull-breeds</li>
<li className=""><span className="about-li-padding">🎼</span>Classical music (Haydn, Mozart, JSB)</li>
<li className=""><span className="about-li-padding">🛸</span>Science fiction </li>
<li className=""><span className="about-li-padding">🐦</span>Bird watching</li>
</ul>
</p>
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
<p className="">
Some things I'm interested in:
<ul className="pt-2">
<li className="mb-1">🧑💻 Self-hosting and digital resiliance</li>
<li className="mb-1">🖳 The history of computing and networks</li>
<li className="mb-1"> Buddhism</li>
{/*
<ul className="no-bullets">
<li className=""><span className="about-li-padding">🧑💻</span>Self-hosting and digital resiliance</li>
<li className=""><span className="about-li-padding">🖥</span>The history of computing and networks</li>
<li className=""><span className="about-li-padding"></span>Buddhism</li>
<li className=""><span className="about-li-padding">📡</span>Amateur radio</li>
<li className=""><span className="about-li-padding">👽</span>SETI</li>
<li className="mb-1">📡 Civil communications infrastructure</li>
*/}
</ul>
</p>
</div>

View file

@ -4,72 +4,25 @@ import { usePosts } from "@/hooks/usePosts"
import gruvboxComputer from "../images/gruvbox-computer.svg"
import EolasListing from "@/components/EolasListing"
import CodeStats from "../containers/CodeStats"
// import TodayILearned from "@/containers/TodayILearned"
import { Link } from "react-router"
const HomePage = () => {
const { posts } = usePosts()
return (
<MainTemplate>
<div className="container mx-auto md:p-4 py-4 px-1 grow">
<div className="space-my-8">
<section className="space-y-4">
<div className="gap-6 flex flex-col items-center sm:flex-row">
<div className="scanlined">
<img src={gruvboxComputer} className="md:w-80 w-50" />
</div>
<div>
<h1 className="text-4xl font-bold py-3 text-center sm:text-left md:text-left">
<div className="scanlined inline-block py-1 px-2">
systems obscure
</div>
</h1>
<p className="text-center sm:text-left md:text-left text-muted font-medium">
A wizard who goes to bed early. This is my technical scrapbook
and digital garden.
</p>
</div>
</div>
</section>
</div>
</div>
const { posts } = usePosts()
return (
<MainTemplate>
<p>
A wizard who goes to bed early. This is <Link to="/about">my</Link>{" "}
technical scrapbook and digital garden.
</p>
<PostListing title="recent posts" posts={posts.slice(0, 5)} />
<PostListing title="Recent posts" posts={posts.slice(0, 5)} />
<PostListing
title="highlights"
posts={posts.filter((post) => post.tags.includes("highlight"))}
/>
<div className="container mx-auto md:p-4 py-4 px-1 grow">
<div className="space-my-8">
<section className="container">
<h2 className="text-2xl font-semibold mb-4 text-[#d3869b]! h2-home scanlined px-2">
{`projects`}
</h2>
<ul>
<li className="pb-2">
<a
className="underline underline-offset-4 text-[18px] text-primary hover:text-primary/80 font-medium"
href="https://eolas.systemsobscure.net"
target="_blank"
>
eolas
</a>
</li>
<li>
<p className="">
A public frontend for my local Zettelkasten created with
NodeJS, Python and React.
</p>
</li>
</ul>
</section>
</div>
</div>
<CodeStats />
<EolasListing />
</MainTemplate>
)
<PostListing
title="Highlights"
posts={posts.filter((post) => post.tags.includes("highlight"))}
/>
</MainTemplate>
)
}
export { HomePage }

View file

@ -9,7 +9,7 @@ const PostsPage = () => {
return (
<MainTemplate>
<PostListing title="all posts" posts={posts} />
<PostListing title="All posts" posts={posts} />
</MainTemplate>
)
}

View file

@ -3,6 +3,7 @@ import MainTemplate from "./MainTemplate"
import { Link, useParams } from "react-router"
import { convertDate } from "@/utils/convertDate"
import { usePosts } from "@/hooks/usePosts"
import { convertDateFriendly } from "../utils/convertDate"
const BlogTemplate = () => {
const { slug } = useParams()
@ -11,23 +12,32 @@ const BlogTemplate = () => {
return (
<MainTemplate>
<div className="container mx-auto py-4 px-1 md:p-4 grow">
<div className="">
{!post ? (
<div>Loading...</div>
) : (
<article className="prose prose-lg max-w-none">
<header className="mb-6 pb-4">
<h1 className="text-4xl font-bold mb-4 leading-tight inline-block scanlined px-2">
{post?.title}
</h1>
<div className="flex flex-wrap align-center gap-4 text-[#928374] condensed font-medium">
<article className="">
<header
className="big-title"
style={{ paddingBottom: "1rem", marginBottom: "2rem" }}
>
<h1 className="post-title">{post?.title}</h1>
<div
className="blog-meta"
style={{
display: "flex",
flexDirection: "row",
gap: "1rem",
}}
>
<time datetime={convertDate(post?.date)} className="text-sm">
{convertDate(post?.date)}
{convertDateFriendly(post?.date)}
</time>
<div className="flex flex-wrap gap-3 align-center">
<div className="">
<span>Tag(s): </span>
{post?.tags?.map((tag, i) => (
<Link
className="text-primary text-sm underline underline-offset-3 hover:text-[#689d6a]"
style={{ marginRight: "0.5rem" }}
key={i}
to={`/tags/${tag}`}
>
@ -38,35 +48,7 @@ const BlogTemplate = () => {
</div>
</header>
<div
className="
[&>h2]:text-2xl [&>h2]:font-bold [&>h2]:my-4 [&>h2]:text-[#ebdbb2]!
[&>h3]:text-xl [&>h3]:font-bold [&>h3]:my-4 [&>h3]:text-[#ebdbb2]!
[&>h4]:text-lg [&>h4]:font-bold [&>h4]:my-4 [&>h4]:text-[#ebdbb2]!
[&>p]:leading-7 [&>p:not(:first-child)]:mt-4
[&>p+:is(h1,h2,h3,h4,h5,h6)]:mt-6
[&>blockquote]:mt-4 [&>blockquote]:border-l-2 [&>blockquote]:pl-6 [&>blockquote]:text-muted-foreground
[&>ul]:my-4 [&>ul]:ml-6 [&>ul]:list-disc [&>ul>li]:mt-2
[&>pre]:mt-6 [&>pre]:mb-6
[&>p+pre]:mt-6
[&>pre+p]:mt-6
[&>ul+pre]:mt-6
[&>li]:leading-[1.6]
[&_li_code]:relative [&_li_code]:rounded [&_li_code]:bg-[#504945] [&_li_code]:px-[0.3rem] [&_li_code]:py-[0.2rem] [&_li_code]:font-mono [&_li_code]:text-sm [&_li_code]:font-normal
[&>code]:relative [&>code]:rounded [&>code]:bg-[#504945] [&>code]:px-[0.3rem] [&>code]:py-[0.2rem] [&>code]:font-mono [&>code]:text-sm [&>code]:font-normal
[&>figure]:w-full [&>figure]:max-w-2xl [&>figure]:flex [&>figure]:flex-col [&>figure]:items-center [&>figure]:justify-center [&>figure]:mb-6 [&>figure]:mx-auto [&>figure>img]:max-w-full [&>figure>img]:max-h-[700px] [&>figure>img]:w-auto [&>figure>img]:h-auto [&>figure>img]:object-contain
[&>figure>img]:max-w-2xl [&>figure>img]:max-h-[700px] [&>figure>img]:w-auto [&>figure>img]:h-auto [&>figure>img]:object-contain
[&>figure>figcaption]:text-sm [&>figure>figcaption]:text-[#bdae93] [&>figure>figcaption]:mt-3 [&>figure>figcaption]:text-center
[&>figure>figcaption>a]:text-primary [&>figure>figcaption>a:hover]:text-primary/80
[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-[#689d6a] [&_a]:text-primary
[&>table]:w-full [&>table]:my-4
[&>table>thead>tr]:m-0 [&>table>thead>tr]:border-t [&>table>thead>tr]:p-0 [&>table>thead>tr:even]:bg-muted
[&>table>thead>tr>th]:border [&>table>thead>tr>th]:px-4 [&>table>thead>tr>th]:py-2 [&>table>thead>tr>th]:text-left [&>table>thead>tr>th]:font-bold [&>table>thead>tr>th[align=center]]:text-center [&>table>thead>tr>th[align=right]]:text-right
[&>table>tbody>tr]:m-0 [&>table>tbody>tr]:border-t [&>table>tbody>tr]:p-0 [&>table>tbody>tr:even]:bg-muted
[&>table>tbody>tr>td]:border [&>table>tbody>tr>td]:px-4 [&>table>tbody>tr>td]:py-2 [&>table>tbody>tr>td]:text-left [&>table>tbody>tr>td[align=center]]:text-center [&>table>tbody>tr>td[align=right]]:text-right
"
dangerouslySetInnerHTML={{ __html: post?.html }}
/>
<div dangerouslySetInnerHTML={{ __html: post?.html }} />
</article>
)}
</div>

View file

@ -1,68 +1,81 @@
// @ts-nocheck
import gruvboxComputer from "../images/gruvbox-computer.svg"
import { Link } from "react-router"
const Header = () => {
return (
<header className="md:py-6 pb-4">
<nav className="bg-sidebar container mx-auto justify-between flex gap-1">
<Link to="/">
<div className="scanlined">
<img src={gruvboxComputer} className="w-11" />
</div>
</Link>
<ul class="flex space-x-4 px-4 py-2 text-sm">
<li class="flex flex-col items-center justify-center">
<Link
class="text-primary underline underline-offset-3 hover:text-[#689d6a] condensed font-semibold text-lg"
to="/posts"
>
posts
</Link>
</li>
<li class="flex flex-col items-center justify-center">
<Link
class="text-primary underline underline-offset-3 hover:text-[#689d6a] condensed font-semibold text-lg"
to="/about"
>
about
</Link>
</li>
</ul>
</nav>
</header>
)
return (
<header>
<nav className="">
<h1 className="site-title">
<Link className="title-link plain-link" to="/">
Systems Obscure
</Link>
</h1>
<ul className="header-links">
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/posts">Posts</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li className="flex flex-col items-center justify-center">
<a
className=""
href="https://forgejo.systemsobscure.net/thomasabishop"
target="blank"
>
Code
</a>
</li>
<li className="flex flex-col items-center justify-center">
<a
className=""
href="https://fosstodon.org/@systemsobscure"
rel="me"
target="blank"
>
Fosstodon
</a>
</li>
</ul>
</nav>
</header>
)
}
const Footer = () => {
return (
<footer className="bg-sidebar container mx-auto px-4 mt-10 mb-8">
<nav>
<ul className="flex flex-row justify-start gap-4">
<li className="flex flex-col items-center justify-center">
<a
className="text-primary underline underline-offset-3 hover:text-[#689d6a] font-semibold"
href="https://forgejo.systemsobscure.net/thomasabishop"
target="blank"
>
forgejo
</a>
</li>
</ul>
</nav>
</footer>
)
return (
<footer className="mx-auto">
<nav>
<ul className="">
<li className="flex flex-col items-center justify-center">
<a
className=""
href="https://forgejo.systemsobscure.net/thomasabishop"
target="blank"
>
Forgejo
</a>
</li>
</ul>
</nav>
</footer>
)
}
const MainTemplate = ({ children }) => {
return (
<div className="antialiased max-w-3xl mt-3 mx-auto bg-[#282828] no-scanlines wrapper">
<main className="flex-auto min-w-0 mt-0 flex flex-col px-2 md:px-0">
<Header />
<div>{children}</div>
<Footer />
</main>
</div>
)
return (
<div className="">
<main className="">
<Header />
<div>{children}</div>
</main>
</div>
)
}
export default MainTemplate
//antialiased max-w-3xl mt-3 mx-auto bg-[#282828] no-scanlines wrapper
//main: flex-auto min-w-0 mt-0 flex flex-col px-2 md:px-0