Compare commits

..

No commits in common. "91d7c965a77119281722c7c1257b8c587b68cc06" and "dbf853be9e1495af6ab68ce1687f12e74b343e21" have entirely different histories.

10 changed files with 194 additions and 271 deletions

View file

@ -1,20 +0,0 @@
---
title: "Amateur radio licence"
slug: /amateur-radio-licence/
date: 2026-03-12
tags: ["radio"]
---
In February I finally bit the bullet and booked the exam for the UK Amateur
Radio Licence (Foundation Level).
My natural learning style is both meandering and thorough. I like to work at my
own pace and pursue rabbit holes and tangents. I dislike exams and any aspect of
competition injected into the learning process. At school my grades were always
lower than my actual knowledge level because I never studied for the test, I
pursued what was interesting to me to depths not required by the exams.
I think all people who love learning for its own sake are like this but I did
actually _need_ the licence in order to be able to transmit and start gaining
practical knowledge. Having the test booked for the end of the month helped me
concentrate and stay on target.

View file

@ -1,35 +1,29 @@
import MetricBar from "./MetricBar"
const LanguagesChart = ({ chartData, error }) => {
return (
<div style={{ marginTop: "1rem" }}>
<div
className="code-stat-label"
style={{
paddingBottom: ".5rem",
marginBottom: ".5rem",
}}
>
Programming languages
</div>
return (
<div style={{marginTop:'1rem'}}>
<div className="code-stat-label" style={{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="black"
/>
))
)}
</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="dodgerblue"
/>
))
)}
</div>
)
}
export default LanguagesChart

View file

@ -1,36 +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: "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>
<div style={{ marginBottom: "12px", margin: '.5rem' }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "4px",
fontSize: "14px",
}}
>
<span style={{}}>{metric}</span>
<span style={{ color: "black" }}>
{hours}h ({percentage}%)
</span>
</div>
<div
style={{
width: "100%",
height: "8px",
backgroundColor: "lightgrey",
overflow: "hidden",
}}
>
<div
style={{
width: `${percentage}%`,
height: "100%",
backgroundColor: color,
transition: "width 0.3s ease",
}}
/>
</div>
</div>
)
export default MetricBar

View file

@ -1,38 +1,30 @@
import MetricBar from "./MetricBar"
const ProjectsChart = ({ chartData, error }) => {
return (
<div style={{ marginTop: "1rem" }}>
<div
className="code-stat-label"
style={{
paddingBottom: ".5rem",
marginBottom: ".5rem",
}}
>
Repos
</div>
return (
<div style={{marginTop: '1rem'}}>
<div className="code-stat-label">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="black"
/>
))
)}
<div className="code-stats-disclaimer">
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="dodgerblue"
/>
))
)}
<div className="code-stats-disclaimer">
Data excludes workplace repos.
</div>
</div>
)
}
export default ProjectsChart

View file

@ -1,5 +1,4 @@
import { useQuery } from "@tanstack/react-query"
import wakapiApi from "../api/wakapi-api"
import { convertDateFriendly } from "../utils/convertDate"
import Scorecard from "../components/Scorecard"
@ -33,9 +32,7 @@ const CodeStats = () => {
data &&
data?.projects.filter(
(project) =>
!project.key.includes("gp-") &&
!project.key.includes("unknown") &&
!project.key.includes("amber")
!project.key.includes("gp-") && !project.key.includes("unknown") && !project.key.includes("amber")
)
const personalProjectsSorted =
@ -70,52 +67,43 @@ const CodeStats = () => {
.slice(0, 4)
return (
<div className="code-stats-sect">
<div className="">
<div className="">
<section className="">
<div className="">
<h2 className="big-title">{`Code this month`}</h2>
{/*
<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>
*/}
<h2 className="">
{`Code stats`}
</h2>
<div className="code-stat-grid">
<span className="code-stat-label" style={{marginRight: '2rem'}}>Period:</span>
<span className="code-stat-metric">{convertDateFriendly(data?.from)} -{" "}
{convertDateFriendly(data?.to)}</span>
</div>
</div>
<div className="">
<Scorecard
title="Total time:"
metric={
error ? "Error" : isLoading ? "Loading..." : grandTotalFormatted
}
/>
<Scorecard
title="Main project"
metric={error ? "Error" : isLoading ? "Loading..." : mainProject}
/>
</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="">
<a
href="https://wakapi.dev/"
target="__blank"
className=""
>
Wakapi
</a>{" "}
instance.

View file

@ -4,56 +4,62 @@ import { Link } from "react-router"
import { convertDate } from "@/utils/convertDate"
const PostListing = ({ posts, title, showAllButton }) => {
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>
)
return (
<div className="">
<div className="">
<section className="">
<h2 className="">
{`${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}`}
key={post.slug}
className="block no-underline"
>
<Card
key={post.slug}
className="flex flex-col h-full hover:bg-primary/5 py-4 rounded border-none "
>
<CardHeader className="">
<CardTitle className="leading-snug font-bold ">
{post.title}
</CardTitle>
<CardDescription className="text-sm text-muted-foreground">
<div className="flex justify-between gap-2">
<span className="text-sm">{convertDate(post.date)}</span>
</div>
</CardDescription>
</CardHeader>
</Card>
</Link>
to={`/posts/${post.slug}`}
key={post.slug}
className="block no-underline"
>
<Card
key={post.slug}
className="flex flex-col h-full hover:bg-primary/5 py-4 rounded border-none "
>
<CardHeader className="">
<CardTitle className="leading-snug font-bold ">
{post.title}
</CardTitle>
<CardDescription className="text-sm text-muted-foreground">
<div className="flex justify-between gap-2">
<span className="text-sm">{convertDate(post.date)}</span>
</div>
</CardDescription>
</CardHeader>
</Card>
</Link>
*/
}

View file

@ -15,35 +15,20 @@
display: flex;
flex-direction: column;
}
.title-link {
letter-spacing: 0 !important;
}
}
.post-listing-item {
display: flex;
}
html {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Open Sans",
"Helvetica Neue",
sans-serif;
h1.site-title {
font-weight: 800;
}
.header-links {
display: flex;
list-style: none;
gap: 1rem;
gap: 0.5rem;
margin: 0;
padding: 0;
}
@ -86,10 +71,6 @@ figure img {
overflow: auto;
}
.code-stats-sect {
font-family: sans-serif !important;
}
.code-stat-grid {
display: flex;
flex-direction: row;
@ -98,8 +79,8 @@ figure img {
}
.code-stat-label {
font-weight: bold;
font-family: sans-serif;
font-weight: 500;
font-style: italic;
}
.code-stats-disclaimer {
@ -111,18 +92,3 @@ figure img {
.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;
}

View file

@ -7,22 +7,25 @@ import CodeStats from "../containers/CodeStats"
import { Link } from "react-router"
const HomePage = () => {
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>
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="Highlights"
posts={posts.filter((post) => post.tags.includes("highlight"))}
/>
</MainTemplate>
)
<PostListing title="Recent posts" posts={posts.slice(0, 5)} />
<PostListing
title="Highlights"
posts={posts.filter((post) => post.tags.includes("highlight"))}
/>
<CodeStats />
</MainTemplate>
)
}
export { HomePage }

View file

@ -17,19 +17,11 @@ const BlogTemplate = () => {
<div>Loading...</div>
) : (
<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",
}}
>
<header className="">
<h1 className="">
{post?.title}
</h1>
<div className="">
<time datetime={convertDate(post?.date)} className="text-sm">
{convertDateFriendly(post?.date)}
</time>
@ -37,7 +29,7 @@ const BlogTemplate = () => {
<span>Tag(s): </span>
{post?.tags?.map((tag, i) => (
<Link
style={{ marginRight: "0.5rem" }}
style={{ marginRight: '0.5rem' }}
key={i}
to={`/tags/${tag}`}
>
@ -48,7 +40,9 @@ const BlogTemplate = () => {
</div>
</header>
<div dangerouslySetInnerHTML={{ __html: post?.html }} />
<div
dangerouslySetInnerHTML={{ __html: post?.html }}
/>
</article>
)}
</div>

View file

@ -6,9 +6,9 @@ const Header = () => {
<header>
<nav className="">
<h1 className="site-title">
<Link className="title-link plain-link" to="/">
Systems Obscure
</Link>
<Link
className="plain-link"
to="/">Systems Obscure</Link>
</h1>
<ul className="header-links">
<li>
@ -27,9 +27,10 @@ const Header = () => {
href="https://forgejo.systemsobscure.net/thomasabishop"
target="blank"
>
Code
Forgejo (ext.)
</a>
</li>
</ul>
</nav>
</header>
@ -69,4 +70,3 @@ const MainTemplate = ({ children }) => {
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