Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 770cca3259 | |||
| febb0af236 | |||
| 1a358c64dd | |||
| 91d7c965a7 | |||
| a52c80333d | |||
| dbf853be9e | |||
| f62963f5c9 |
29 changed files with 615 additions and 582 deletions
53
@s
53
@s
|
|
@ -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
10
package-lock.json
generated
|
|
@ -80,7 +80,6 @@
|
||||||
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
|
|
@ -2263,7 +2262,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.7.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.7.tgz",
|
||||||
"integrity": "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==",
|
"integrity": "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.90.7"
|
"@tanstack/query-core": "5.90.7"
|
||||||
},
|
},
|
||||||
|
|
@ -2419,7 +2417,6 @@
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -2532,7 +2529,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001716",
|
"caniuse-lite": "^1.0.30001716",
|
||||||
"electron-to-chromium": "^1.5.149",
|
"electron-to-chromium": "^1.5.149",
|
||||||
|
|
@ -2951,7 +2947,6 @@
|
||||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
|
|
@ -4386,7 +4381,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -4396,7 +4390,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4814,7 +4807,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -5004,7 +4996,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
|
|
@ -5093,7 +5084,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
78
posts/amateur-radio-licence.md
Normal file
78
posts/amateur-radio-licence.md
Normal 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/).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
BIN
posts/img/flash-cards.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 565 KiB |
BIN
posts/img/monitoring-sdr.jpg
Normal file
BIN
posts/img/monitoring-sdr.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 483 KiB |
BIN
posts/img/thinkpad-x1-carbon.jpg
Normal file
BIN
posts/img/thinkpad-x1-carbon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 467 KiB |
BIN
public/fonts/DejaVuSans-Bold.ttf
Normal file
BIN
public/fonts/DejaVuSans-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans-Bold.woff2
Normal file
BIN
public/fonts/DejaVuSans-Bold.woff2
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans-BoldOblique.ttf
Normal file
BIN
public/fonts/DejaVuSans-BoldOblique.ttf
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans-BoldOblique.woff2
Normal file
BIN
public/fonts/DejaVuSans-BoldOblique.woff2
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans-Oblique.ttf
Normal file
BIN
public/fonts/DejaVuSans-Oblique.ttf
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans-Oblique.woff2
Normal file
BIN
public/fonts/DejaVuSans-Oblique.woff2
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans.ttf
Normal file
BIN
public/fonts/DejaVuSans.ttf
Normal file
Binary file not shown.
BIN
public/fonts/DejaVuSans.woff2
Normal file
BIN
public/fonts/DejaVuSans.woff2
Normal file
Binary file not shown.
|
|
@ -13,7 +13,7 @@ const renderer = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlighter = await createHighlighter({
|
const highlighter = await createHighlighter({
|
||||||
themes: ["gruvbox-dark-hard"],
|
themes: ["light-plus"],
|
||||||
|
|
||||||
langs: [
|
langs: [
|
||||||
"javascript",
|
"javascript",
|
||||||
|
|
@ -49,7 +49,7 @@ const posts = files.map((file) => {
|
||||||
(match, lang, code) => {
|
(match, lang, code) => {
|
||||||
return highlighter.codeToHtml(code.trim(), {
|
return highlighter.codeToHtml(code.trim(), {
|
||||||
lang: lang || "text",
|
lang: lang || "text",
|
||||||
theme: "gruvbox-dark-hard",
|
theme: "light-plus",
|
||||||
|
|
||||||
transformers: [transformerColorizedBrackets()],
|
transformers: [transformerColorizedBrackets()],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,57 +3,57 @@ import eolasApi from "@/api/eolas-api"
|
||||||
import { convertDate } from "@/utils/convertDate"
|
import { convertDate } from "@/utils/convertDate"
|
||||||
|
|
||||||
const EolasEntries = ({ entries }) => {
|
const EolasEntries = ({ entries }) => {
|
||||||
return (
|
return (
|
||||||
entries &&
|
entries &&
|
||||||
entries.map((entry, i) => (
|
entries.map((entry, i) => (
|
||||||
<ul>
|
<ul>
|
||||||
<li className="mb-4">
|
<li className="mb-4">
|
||||||
<div className="flex justify-between items-center relative gap-3 hover:bg-[#504945]">
|
<div className="flex justify-between items-center relative gap-3 hover:bg-[#504945]">
|
||||||
<span className="overflow-hidden whitespace-nowrap text-muted-foreground shrink-0 condensed">
|
<span className="overflow-hidden whitespace-nowrap text-muted-foreground shrink-0 condensed">
|
||||||
{convertDate(entry.last_modified)}
|
{convertDate(entry.last_modified)}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href={`https://eolas.systemsobscure.net/entries/${entry.title}`}
|
href={`https://eolas.systemsobscure.net/entries/${entry.title}`}
|
||||||
key={i}
|
key={i}
|
||||||
className="text-right overflow-hidden text-ellipsis whitespace-nowrap min-w-0 flex-1 "
|
className="text-right overflow-hidden text-ellipsis whitespace-nowrap min-w-0 flex-1 "
|
||||||
>
|
>
|
||||||
{entry.title.replace(/_/g, " ")}
|
{entry.title.replace(/_/g, " ")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const EolasListing = () => {
|
const EolasListing = () => {
|
||||||
const { data, isLoading, error } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
queryKey: [`eolas_listing`],
|
queryKey: [`eolas_listing`],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
eolasApi.get(`entries?limit=5&sort=date`).then((res) => res.data),
|
eolasApi.get(`entries?limit=5&sort=date`).then((res) => res.data),
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto py-4 px-1 md:p-4 grow">
|
<div className="container mx-auto py-4 px-1 md:p-4 grow">
|
||||||
<div className="space-my-8">
|
<div className="space-my-8">
|
||||||
<section className="container">
|
<section className="container">
|
||||||
<h2 className="text-2xl font-semibold mb-4 text-[#d65d0e]! h2-home">
|
<h2 className="text-2xl font-semibold mb-4 text-[#d65d0e]! h2-home scanlined px-2">
|
||||||
{`Recent learning`}
|
{`eolas recent`}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{isLoading && <div>Loading...</div>}
|
{isLoading && <div>Loading...</div>}
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="border-l-2 border-[#cc241d] px-3 bg-[#cc241d]/20">
|
<div className="border-l-2 border-[#cc241d] px-3 bg-[#cc241d]/20">
|
||||||
Error fetching recent learning
|
Error fetching recent learning
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EolasEntries entries={data?.data} />
|
<EolasEntries entries={data?.data} />
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EolasListing
|
export default EolasListing
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,35 @@
|
||||||
import MetricBar from "./MetricBar"
|
import MetricBar from "./MetricBar"
|
||||||
|
|
||||||
const LanguagesChart = ({ chartData, error }) => {
|
const LanguagesChart = ({ chartData, error }) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-sidebar p-3 my-4 mx-2 rounded-2xl">
|
<div style={{ marginTop: "1rem" }}>
|
||||||
<div className="text-muted-foreground text-sm pb-2">
|
<div
|
||||||
programming languages
|
className="code-stat-label"
|
||||||
</div>
|
style={{
|
||||||
|
paddingBottom: ".5rem",
|
||||||
|
marginBottom: ".5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Programming languages
|
||||||
|
</div>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<div>Data could not be found!</div>
|
<div>Data could not be found!</div>
|
||||||
) : !chartData?.length ? (
|
) : !chartData?.length ? (
|
||||||
<div>No data for time period.</div>
|
<div>No data for time period.</div>
|
||||||
) : (
|
) : (
|
||||||
chartData.map((x) => (
|
chartData.map((x) => (
|
||||||
<MetricBar
|
<MetricBar
|
||||||
key={x.language}
|
key={x.language}
|
||||||
metric={x.language}
|
metric={x.language}
|
||||||
hours={x.hours}
|
hours={x.hours}
|
||||||
percentage={x.percentage}
|
percentage={x.percentage}
|
||||||
color="#458588"
|
color="black"
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LanguagesChart
|
export default LanguagesChart
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,36 @@
|
||||||
const MetricBar = ({ metric, hours, percentage, color }) => (
|
const MetricBar = ({ metric, hours, percentage, color }) => (
|
||||||
<div style={{ marginBottom: "12px" }}>
|
<div style={{ marginBottom: "12px" }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
marginBottom: "4px",
|
marginBottom: "4px",
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{}}>{metric}</span>
|
<span style={{}}>{metric}</span>
|
||||||
<span style={{ color: "#bdae93" }}>
|
<span style={{ color: "black" }}>
|
||||||
{hours}h ({percentage}%)
|
{hours}h ({percentage}%)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "8px",
|
height: "8px",
|
||||||
backgroundColor: "#32302f",
|
backgroundColor: "lightgrey",
|
||||||
borderRadius: "4px",
|
overflow: "hidden",
|
||||||
overflow: "hidden",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<div
|
||||||
<div
|
style={{
|
||||||
style={{
|
width: `${percentage}%`,
|
||||||
width: `${percentage}%`,
|
height: "100%",
|
||||||
height: "100%",
|
backgroundColor: color,
|
||||||
backgroundColor: color,
|
transition: "width 0.3s ease",
|
||||||
transition: "width 0.3s ease",
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export default MetricBar
|
export default MetricBar
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,38 @@
|
||||||
import MetricBar from "./MetricBar"
|
import MetricBar from "./MetricBar"
|
||||||
|
|
||||||
const ProjectsChart = ({ chartData, error }) => {
|
const ProjectsChart = ({ chartData, error }) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-sidebar p-3 my-4">
|
<div style={{ marginTop: "1rem" }}>
|
||||||
<div className="text-muted-foreground text-sm pb-2">projects</div>
|
<div
|
||||||
|
className="code-stat-label"
|
||||||
|
style={{
|
||||||
|
paddingBottom: ".5rem",
|
||||||
|
marginBottom: ".5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Repos
|
||||||
|
</div>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<div>Data could not be found!</div>
|
<div>Data could not be found!</div>
|
||||||
) : !chartData?.length ? (
|
) : !chartData?.length ? (
|
||||||
<div>No data for time period.</div>
|
<div>No data for time period.</div>
|
||||||
) : (
|
) : (
|
||||||
chartData.map((x) => (
|
chartData.map((x) => (
|
||||||
<MetricBar
|
<MetricBar
|
||||||
key={x.project}
|
key={x.project}
|
||||||
metric={x.project}
|
metric={x.project}
|
||||||
hours={x.hours}
|
hours={x.hours}
|
||||||
percentage={x.percentage}
|
percentage={x.percentage}
|
||||||
color="#fe8019"
|
color="black"
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="code-stats-disclaimer">
|
||||||
Data excludes workplace repos.
|
Data excludes workplace repos.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectsChart
|
export default ProjectsChart
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
const Scorecard = ({ title, metric }) => {
|
const Scorecard = ({ title, metric }) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-sidebar p-3 rounded-2xl">
|
<div className="code-stat-grid">
|
||||||
<div className="text-muted-foreground text-sm">{title}</div>
|
<div className="code-stat-label">{title}</div>
|
||||||
<div className="text-lg">{metric}</div>
|
<div className="code-stat-metric">{metric}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
|
||||||
import wakapiApi from "../api/wakapi-api"
|
import wakapiApi from "../api/wakapi-api"
|
||||||
import { convertDateFriendly } from "../utils/convertDate"
|
import { convertDateFriendly } from "../utils/convertDate"
|
||||||
import Scorecard from "../components/Scorecard"
|
import Scorecard from "../components/Scorecard"
|
||||||
|
|
@ -32,7 +33,9 @@ const CodeStats = () => {
|
||||||
data &&
|
data &&
|
||||||
data?.projects.filter(
|
data?.projects.filter(
|
||||||
(project) =>
|
(project) =>
|
||||||
!project.key.includes("gp-") && !project.key.includes("unknown")
|
!project.key.includes("gp-") &&
|
||||||
|
!project.key.includes("unknown") &&
|
||||||
|
!project.key.includes("amber")
|
||||||
)
|
)
|
||||||
|
|
||||||
const personalProjectsSorted =
|
const personalProjectsSorted =
|
||||||
|
|
@ -67,45 +70,52 @@ const CodeStats = () => {
|
||||||
.slice(0, 4)
|
.slice(0, 4)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto py-4 px-1 md:p-4 grow ">
|
<div className="code-stats-sect">
|
||||||
<div className="space-my-8">
|
<div className="">
|
||||||
<section className="container">
|
<section className="">
|
||||||
<div className="flex flex-col md:flex-row items-start md:items-center md:justify-between">
|
<div className="">
|
||||||
<h2 className="text-3xl font-semibold mb-4 text-[#458588]! h2-home">
|
<h2 className="big-title">{`Code this month`}</h2>
|
||||||
{`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 mx-2">
|
|
||||||
<Scorecard
|
|
||||||
title="time coding"
|
|
||||||
metric={
|
|
||||||
error ? "Error" : isLoading ? "Loading..." : grandTotalFormatted
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Scorecard
|
{/*
|
||||||
title="main project"
|
<table
|
||||||
metric={error ? "Error" : isLoading ? "Loading..." : mainProject}
|
border="1"
|
||||||
/>
|
width="100%"
|
||||||
|
style={{ borderCollapse: "collapse" }}
|
||||||
<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"
|
|
||||||
>
|
>
|
||||||
|
<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
|
Wakapi
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
instance.
|
instance.
|
||||||
|
|
|
||||||
|
|
@ -4,40 +4,34 @@ import { Link } from "react-router"
|
||||||
import { convertDate } from "@/utils/convertDate"
|
import { convertDate } from "@/utils/convertDate"
|
||||||
|
|
||||||
const PostListing = ({ posts, title, showAllButton }) => {
|
const PostListing = ({ posts, title, showAllButton }) => {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto py-4 px-1 md:p-4 grow">
|
<div className="">
|
||||||
<div className="space-my-8">
|
<div className="">
|
||||||
<section className="container">
|
<section className="">
|
||||||
<h2 className="text-3xl font-semibold mb-4 block h2-home">
|
<h2 className="big-title">{`${title}`}</h2>
|
||||||
{`${title}`}
|
{posts.map((post) => (
|
||||||
</h2>
|
<ul className="no-bullets">
|
||||||
{posts.map((post) => (
|
<li className="">
|
||||||
<ul>
|
<div className="post-listing-item">
|
||||||
<li className="mb-4">
|
<span style={{ marginRight: "1rem" }}>
|
||||||
<div className="flex justify-between flex-col relative hover:bg-[#504945] hover:rounded-2xl p-2">
|
{convertDate(post.date)}
|
||||||
<span className="overflow-hidden whitespace-nowrap text-muted-foreground shrink-0 condensed">
|
</span>
|
||||||
{convertDate(post.date)}
|
<Link to={`/posts/${post.slug}`} key={post.slug} className="">
|
||||||
</span>
|
{post.title}
|
||||||
<Link
|
</Link>
|
||||||
to={`/posts/${post.slug}`}
|
</div>
|
||||||
key={post.slug}
|
</li>
|
||||||
className="overflow-hidden text-ellipsis whitespace-nowrap min-w-0"
|
</ul>
|
||||||
>
|
))}
|
||||||
{post.title}
|
</section>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
)
|
||||||
</ul>
|
|
||||||
))}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PostListing
|
export default PostListing
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={`/posts/${post.slug}`}
|
to={`/posts/${post.slug}`}
|
||||||
|
|
|
||||||
211
src/index.css
211
src/index.css
|
|
@ -1,100 +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");
|
@media (min-width: 768px) {
|
||||||
@import "./styles/_variables.css";
|
main {
|
||||||
@import "tailwindcss";
|
max-width: 768px;
|
||||||
@import "tw-animate-css";
|
margin: 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
.post-listing-item {
|
||||||
outline-color: color-mix(in srgb, var(--ring) 50%, transparent);
|
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 {
|
html {
|
||||||
font-family: var(--font-sansserif);
|
font-family: "DejaVu Sans", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
.header-links {
|
||||||
background-color: var(--background);
|
display: flex;
|
||||||
color: var(--foreground);
|
list-style: none;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.condensed {
|
.plain-link {
|
||||||
font-family: "IBM Plex Sans";
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-bullets {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
figcaption {
|
figcaption {
|
||||||
font-weight: 500;
|
text-align: center;
|
||||||
font-family: "IBM Plex Sans";
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
figure {
|
||||||
color: var(--color-orange-light);
|
text-align: center;
|
||||||
font-family: "IBM Plex Sans";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
figure img {
|
||||||
font-family: "IBM Plex Sans";
|
width: 100%;
|
||||||
color: var(--color-green-light);
|
max-width: 500px;
|
||||||
}
|
min-width: 300px;
|
||||||
|
height: auto;
|
||||||
.h2-home {
|
|
||||||
font-family: "IBM Plex Sans";
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-family: "IBM Plex Sans";
|
|
||||||
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,
|
|
||||||
li {
|
|
||||||
font-size: 1.125rem !important;
|
|
||||||
line-height: 1.8 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p code {
|
|
||||||
color: var(--foreground);
|
|
||||||
background: #504945;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0.2rem 0.3rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shiki {
|
.shiki {
|
||||||
padding: 1rem 1.2rem;
|
padding: 1rem;
|
||||||
border-radius: 0;
|
/* border: 1pt solid black; */
|
||||||
overflow-x: auto;
|
background-color: whitesmoke !important;
|
||||||
margin: 1.5rem 0;
|
overflow: auto;
|
||||||
line-height: 1.3;
|
}
|
||||||
/* counter-reset: line; */
|
|
||||||
font-family: var(--font-monospaced) !important;
|
.code-stats-sect {
|
||||||
font-size: 14px !important;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@ import portrait from "../images/portrait-compressed.jpg"
|
||||||
const AboutPage = () => {
|
const AboutPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="container mx-auto py-4 px-1 md:p-4">
|
<div className="">
|
||||||
<figure className="w-full flex flex-col items-center mb-6">
|
<figure className="">
|
||||||
<div className="scanlined inline-block">
|
<div className="">
|
||||||
<img
|
<img
|
||||||
alt="A portrait of the blog author"
|
alt="A portrait of the blog author"
|
||||||
src={portrait}
|
src={portrait}
|
||||||
className="w-80 flex"
|
style={{ width: '25%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<figcaption className="text-sm text-muted-foreground mt-3 text-center">
|
<figcaption className="">
|
||||||
Pictured with the WITCH computer at the{" "}
|
Pictured with the WITCH computer at the{" "}
|
||||||
<a
|
<a
|
||||||
href="https://www.tnmoc.org/"
|
href="https://www.tnmoc.org/"
|
||||||
|
|
@ -57,29 +57,33 @@ const AboutPage = () => {
|
||||||
>
|
>
|
||||||
Arria NLG
|
Arria NLG
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
and in several web developer roles. Before software I was a
|
and in several web developer roles. Before software, I was a
|
||||||
teacher.{" "}
|
teacher.{" "}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>I am licenced amateur radio operator. My callsign is ME7SYO.</p>
|
||||||
|
|
||||||
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
||||||
Some things I like:
|
Some things I like:
|
||||||
<ul className="pt-2">
|
<ul className="no-bullets">
|
||||||
<li className="mb-1">🐶 Staffies and other bull-breeds</li>
|
<li className=""><span className="about-li-padding">🐶</span>Staffies and other bull-breeds</li>
|
||||||
<li className="mb-1">🎼 Classical music (Haydn, Mozart, JSB)</li>
|
<li className=""><span className="about-li-padding">🎼</span>Classical music (Haydn, Mozart, JSB)</li>
|
||||||
<li className="mb-1">🛸 Science fiction </li>
|
<li className=""><span className="about-li-padding">🛸</span>Science fiction </li>
|
||||||
|
<li className=""><span className="about-li-padding">🐦⬛</span>Bird watching</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
<p className="">
|
||||||
Some things I'm interested in:
|
Some things I'm interested in:
|
||||||
<ul className="pt-2">
|
<ul className="no-bullets">
|
||||||
<li className="mb-1">🧑💻 Self-hosting and digital resiliance</li>
|
<li className=""><span className="about-li-padding">🧑💻</span>Self-hosting and digital resiliance</li>
|
||||||
<li className="mb-1">🖳 The history of computing and networks</li>
|
|
||||||
<li className="mb-1">☸️ Buddhism</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>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,70 +4,23 @@ import { usePosts } from "@/hooks/usePosts"
|
||||||
import gruvboxComputer from "../images/gruvbox-computer.svg"
|
import gruvboxComputer from "../images/gruvbox-computer.svg"
|
||||||
import EolasListing from "@/components/EolasListing"
|
import EolasListing from "@/components/EolasListing"
|
||||||
import CodeStats from "../containers/CodeStats"
|
import CodeStats from "../containers/CodeStats"
|
||||||
|
import { Link } from "react-router"
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="container mx-auto md:p-4 py-4 px-1 grow">
|
<p>
|
||||||
<div className="space-my-8">
|
A wizard who goes to bed early. This is <Link to="/about">my</Link>{" "}
|
||||||
<section className="space-y-4">
|
technical scrapbook and digital garden.
|
||||||
<div className="gap-6 flex flex-col items-center sm:flex-row">
|
</p>
|
||||||
<div className="">
|
|
||||||
<img
|
|
||||||
src={gruvboxComputer}
|
|
||||||
className="md:w-80 w-50 rounded-2xl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-5xl font-bold py-3 text-center sm:text-left md:text-left">
|
|
||||||
<div className="">systems obscure</div>
|
|
||||||
</h1>
|
|
||||||
<p className="text-center sm:text-left md:text-left text-muted font-medium text-lg mt-1">
|
|
||||||
A wizard who goes to bed early. This is my technical scrapbook
|
|
||||||
and digital garden.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PostListing title="recent posts" posts={posts.slice(0, 5)} />
|
<PostListing title="Recent posts" posts={posts.slice(0, 5)} />
|
||||||
|
|
||||||
<PostListing
|
<PostListing
|
||||||
title="highlights"
|
title="Highlights"
|
||||||
posts={posts.filter((post) => post.tags.includes("highlight"))}
|
posts={posts.filter((post) => post.tags.includes("highlight"))}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="container mx-auto md:p-4 py-4 px-1 grow mb-4">
|
|
||||||
<div className="">
|
|
||||||
<section className="container">
|
|
||||||
<h2 className="text-3xl font-semibold mb-4 text-[#d3869b]! h2-home">
|
|
||||||
{`projects`}
|
|
||||||
</h2>
|
|
||||||
<ul className="p-2">
|
|
||||||
<li className="pb-2">
|
|
||||||
<a
|
|
||||||
className="underline underline-offset-4 text-primary hover:text-primary/80 font-medium text-xl"
|
|
||||||
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>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const PostsPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<PostListing title="all posts" posts={posts} />
|
<PostListing title="All posts" posts={posts} />
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,79 @@
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.3rem;
|
--radius: 0.3rem;
|
||||||
--background: #282828;
|
--background: #282828;
|
||||||
--foreground: #fbf1c7;
|
--foreground: #ebdbb2;
|
||||||
--sidebar: #3c3836;
|
--sidebar: #3c3836;
|
||||||
--color-red-light: #fb4934;
|
--color-red-light: #fb4934;
|
||||||
--color-orange-light: #fe8019;
|
--color-orange-light: #fe8019;
|
||||||
--color-green-light: #b8bb26;
|
--color-green-light: #b8bb26;
|
||||||
--color-aqua-muted: #689d6a;
|
--color-aqua-muted: #689d6a;
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--primary: #8ec07c;
|
--primary: #8ec07c;
|
||||||
--primary-muted: #689d6a;
|
--primary-muted: #689d6a;
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
--secondary: oklch(0.97 0 0);
|
--secondary: oklch(0.97 0 0);
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--muted: #bdae93;
|
--muted: #bdae93;
|
||||||
--muted-foreground: #928374;
|
--muted-foreground: #928374;
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.97 0 0);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.922 0 0);
|
--border: oklch(0.922 0 0);
|
||||||
--input: oklch(0.922 0 0);
|
--input: oklch(0.922 0 0);
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: oklch(0.708 0 0);
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
--font-monospaced: "Jetbrains Mono";
|
--font-monospaced: "Jetbrains Mono";
|
||||||
--font-sansserif: "IBM Plex Sans", sans-serif;
|
--font-sansserif: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
--radius-lg: var(--radius);
|
--radius-lg: var(--radius);
|
||||||
--radius-xl: calc(var(--radius));
|
--radius-xl: calc(var(--radius));
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--color-card: var(--card);
|
--color-card: var(--card);
|
||||||
--color-card-foreground: var(--card-foreground);
|
--color-card-foreground: var(--card-foreground);
|
||||||
--color-popover: var(--popover);
|
--color-popover: var(--popover);
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
--color-muted: var(--muted);
|
--color-muted: var(--muted);
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
--color-destructive: var(--destructive);
|
--color-destructive: var(--destructive);
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
--color-chart-1: var(--chart-1);
|
--color-chart-1: var(--chart-1);
|
||||||
--color-chart-2: var(--chart-2);
|
--color-chart-2: var(--chart-2);
|
||||||
--color-chart-3: var(--chart-3);
|
--color-chart-3: var(--chart-3);
|
||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
--color-sidebar: var(--sidebar);
|
--color-sidebar: var(--sidebar);
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,75 +3,57 @@ import MainTemplate from "./MainTemplate"
|
||||||
import { Link, useParams } from "react-router"
|
import { Link, useParams } from "react-router"
|
||||||
import { convertDate } from "@/utils/convertDate"
|
import { convertDate } from "@/utils/convertDate"
|
||||||
import { usePosts } from "@/hooks/usePosts"
|
import { usePosts } from "@/hooks/usePosts"
|
||||||
|
import { convertDateFriendly } from "../utils/convertDate"
|
||||||
|
|
||||||
const BlogTemplate = () => {
|
const BlogTemplate = () => {
|
||||||
const { slug } = useParams()
|
const { slug } = useParams()
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
const post = posts?.find((x) => x.slug === slug)
|
const post = posts?.find((x) => x.slug === slug)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="container mx-auto py-4 px-1 md:p-4 grow">
|
<div className="">
|
||||||
{!post ? (
|
{!post ? (
|
||||||
<div>Loading...</div>
|
<div>Loading...</div>
|
||||||
) : (
|
) : (
|
||||||
<article className="prose prose-lg max-w-none">
|
<article className="">
|
||||||
<header className="mb-6 pb-4">
|
<header
|
||||||
<h1 className="text-5xl font-bold mb-4 inline-block">
|
className="big-title"
|
||||||
{post?.title}
|
style={{ paddingBottom: "1rem", marginBottom: "2rem" }}
|
||||||
</h1>
|
>
|
||||||
<div className="flex flex-wrap align-center gap-4 text-[#928374] condensed font-medium">
|
<h1 className="post-title">{post?.title}</h1>
|
||||||
<time datetime={convertDate(post?.date)} className="">
|
<div
|
||||||
{convertDate(post?.date)}
|
className="blog-meta"
|
||||||
</time>
|
style={{
|
||||||
<div className="flex flex-wrap gap-3 align-center">
|
display: "flex",
|
||||||
{post?.tags?.map((tag, i) => (
|
flexDirection: "row",
|
||||||
<Link
|
gap: "1rem",
|
||||||
className="text-primary underline underline-offset-3 hover:text-[#689d6a]"
|
}}
|
||||||
key={i}
|
>
|
||||||
to={`/tags/${tag}`}
|
<time datetime={convertDate(post?.date)} className="text-sm">
|
||||||
>
|
{convertDateFriendly(post?.date)}
|
||||||
{tag}
|
</time>
|
||||||
</Link>
|
<div className="">
|
||||||
))}
|
<span>Tag(s): </span>
|
||||||
</div>
|
{post?.tags?.map((tag, i) => (
|
||||||
</div>
|
<Link
|
||||||
</header>
|
style={{ marginRight: "0.5rem" }}
|
||||||
|
key={i}
|
||||||
|
to={`/tags/${tag}`}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div
|
<div dangerouslySetInnerHTML={{ __html: post?.html }} />
|
||||||
className="
|
</article>
|
||||||
[&>h2]:text-2xl [&>h2]:font-bold [&>h2]:my-4 [&>h2]:text-[#ebdbb2]!
|
)}
|
||||||
[&>h3]:text-xl [&>h3]:font-bold [&>h3]:my-4 [&>h3]:text-[#ebdbb2]!
|
</div>
|
||||||
[&>h4]:text-lg [&>h4]:font-bold [&>h4]:my-4 [&>h4]:text-[#ebdbb2]!
|
</MainTemplate>
|
||||||
[&>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 }}
|
|
||||||
/>
|
|
||||||
</article>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</MainTemplate>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BlogTemplate
|
export default BlogTemplate
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,44 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import gruvboxComputer from "../images/gruvbox-computer.svg"
|
|
||||||
import { Link } from "react-router"
|
import { Link } from "react-router"
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
return (
|
return (
|
||||||
<header className="md:py-6 pb-4">
|
<header>
|
||||||
<nav className="bg-sidebar container mx-auto justify-between flex gap-1 rounded-r-2xl">
|
<nav className="">
|
||||||
<Link to="/">
|
<h1 className="site-title">
|
||||||
<div className="">
|
<Link className="title-link plain-link" to="/">
|
||||||
<img src={gruvboxComputer} className="w-12" />
|
Systems Obscure
|
||||||
</div>
|
</Link>
|
||||||
</Link>
|
</h1>
|
||||||
<ul class="flex space-x-4 px-4 py-2 text-sm">
|
<ul className="header-links">
|
||||||
<li class="flex flex-col items-center justify-center">
|
<li>
|
||||||
<Link
|
<Link to="/">Home</Link>
|
||||||
class="text-primary hover:text-[#689d6a] condensed font-semibold text-xl"
|
|
||||||
to="/posts"
|
|
||||||
>
|
|
||||||
posts
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="flex flex-col items-center justify-center">
|
|
||||||
<Link
|
<li>
|
||||||
class="text-primary hover:text-[#689d6a] condensed font-semibold text-xl"
|
<Link to="/posts">Posts</Link>
|
||||||
to="/about"
|
</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"
|
||||||
>
|
>
|
||||||
about
|
Code
|
||||||
</Link>
|
</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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -36,16 +48,16 @@ const Header = () => {
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-sidebar container mx-auto px-4 mt-10 mb-8 rounded-2xl p-2">
|
<footer className="mx-auto">
|
||||||
<nav className="">
|
<nav>
|
||||||
<ul className="flex flex-row justify-start gap-4">
|
<ul className="">
|
||||||
<li className="flex flex-col items-center justify-center">
|
<li className="flex flex-col items-center justify-center">
|
||||||
<a
|
<a
|
||||||
className="text-primary underline underline-offset-3 hover:text-[#689d6a] font-semibold"
|
className=""
|
||||||
href="https://forgejo.systemsobscure.net/thomasabishop"
|
href="https://forgejo.systemsobscure.net/thomasabishop"
|
||||||
target="blank"
|
target="blank"
|
||||||
>
|
>
|
||||||
forgejo
|
Forgejo
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -56,13 +68,14 @@ const Footer = () => {
|
||||||
|
|
||||||
const MainTemplate = ({ children }) => {
|
const MainTemplate = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="antialiased max-w-3xl mt-3 mx-auto bg-[#282828] no-scanlines wrapper">
|
<div className="">
|
||||||
<main className="flex-auto min-w-0 mt-0 flex flex-col px-2 md:px-0">
|
<main className="">
|
||||||
<Header />
|
<Header />
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
<Footer />
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default MainTemplate
|
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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue