Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 770cca3259 | |||
| febb0af236 | |||
| 1a358c64dd | |||
| 91d7c965a7 | |||
| a52c80333d | |||
| dbf853be9e | |||
| f62963f5c9 |
27 changed files with 507 additions and 468 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==",
|
||||
"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"
|
||||
},
|
||||
|
|
|
|||
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({
|
||||
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()],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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}`}
|
||||
|
|
|
|||
211
src/index.css
211
src/index.css
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const PostsPage = () => {
|
|||
|
||||
return (
|
||||
<MainTemplate>
|
||||
<PostListing title="all posts" posts={posts} />
|
||||
<PostListing title="All posts" posts={posts} />
|
||||
</MainTemplate>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue