First blog post, added theming
All checks were successful
Build blog / build (push) Successful in 1s

Coffee-themed, font changes and a first post.
This commit is contained in:
2026-10-04 17:35:06 +02:00
parent 2a2afb2b6b
commit 2ee236819a
15 changed files with 335 additions and 28 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@ public/
out/
__pycache__/
*/template.md
.crossnote/
WIP

View File

@@ -64,7 +64,13 @@ def parse(path):
"date": when,
"topic": rel.parts[0] if len(rel.parts) > 1 else meta.get("topic"),
"slug": slugify(meta.get("slug") or re.sub(r"^\d{4}-\d{2}-\d{2}-", "", path.stem)),
"body": markdown.markdown(text, extensions=["fenced_code", "tables", "footnotes"]),
"body": markdown.markdown(
text,
extensions=[
"fenced_code", "tables", "footnotes", "codehilite", "abbr",
],
extension_configs={"codehilite": {"guess_lang": False}},
),
}

93
static/OFL.txt Normal file
View File

@@ -0,0 +1,93 @@
Copyright 2020 The Poltawski Nowy Project Authors (https://github.com/kosmynkab/Poltawski-Nowy)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,48 +4,158 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pascal Maas · Blog</title>
<style>
:root {
--bg: #fbfaf7; --fg: #1d1d1b; --muted: #6b6a66; --line: #e4e1da;
--accent: #2f5d8a; --code-bg: #f1eee7;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #161616; --fg: #e8e6e1; --muted: #9a978f; --line: #2c2b29;
--accent: #8ab4dc; --code-bg: #222120;
<script>
// Runs before the page is drawn, so a saved dark choice doesn't flash.
try {
if (localStorage.getItem("theme") === "dark") {
document.documentElement.dataset.theme = "dark";
}
} catch (error) {}
</script>
<style>
/* Served from static/; Georgia shows until these have loaded. */
@font-face {
font-family: "Poltawski Nowy"; font-weight: 400; font-style: normal;
src: url("static/PoltawskiNowy-Regular.ttf"); font-display: swap;
}
@font-face {
font-family: "Poltawski Nowy"; font-weight: 400; font-style: italic;
src: url("static/PoltawskiNowy-Italic.ttf"); font-display: swap;
}
@font-face {
font-family: "Poltawski Nowy"; font-weight: 700; font-style: normal;
src: url("static/PoltawskiNowy-Bold.ttf"); font-display: swap;
}
@font-face {
font-family: "Poltawski Nowy"; font-weight: 700; font-style: italic;
src: url("static/PoltawskiNowy-BoldItalic.ttf"); font-display: swap;
}
:root {
--latte: #f4f2ee; --espresso: #231c15;
--espresso-button: #4a3326; --latte-button: #e3d9cb;
--bg: var(--latte); --fg: #1d1d1b; --muted: #6b6a66; --line: #dbd6cc;
--accent: #2f5d8a; --code-bg: #eae6e0;
/* Pinned header; the side list pins just below it. */
--header-height: 3.25rem; --header-bg: #ebe8e1;
--code-block-bg: #eae6e0; --code-text: #1d1d1b;
--code-comment: #7a7a7a; --code-keyword: #a85a1e; --code-string: #4f6b3f;
--code-number: #4a6f8f; --code-function: #8a6500;
}
:root[data-theme="dark"] {
--bg: var(--espresso); --fg: #e8e6e1; --muted: #9a978f; --line: #3d3227;
--accent: #8ab4dc; --code-bg: #2f261d; --header-bg: #2c231a;
--code-block-bg: #2e2822; --code-text: #d6d4cf;
--code-comment: #808080; --code-keyword: #cc7832; --code-string: #6a8759;
--code-number: #6897bb; --code-function: #ffc66d;
}
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--fg);
font: 17px/1.65 Georgia, "Iowan Old Style", "Times New Roman", serif;
font: 17px/1.65 "Poltawski Nowy", Georgia, "Iowan Old Style",
"Times New Roman", serif;
}
.wrap { max-width: 44rem; margin: 0 auto; padding: 3rem 1rem 4rem; }
.wrap { max-width: 56rem; margin: 0 auto; padding: 0 1rem 4rem; }
a { color: var(--accent); }
h1, h2, h3, .meta, nav, footer { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
.site-header h1 { margin: 0; font-size: 1.6rem; letter-spacing: -0.01em; }
.site-header p { margin: .25rem 0 0; color: var(--muted); }
.site-header h1 {
margin: 0; font-size: 1.6rem; line-height: 1.2; letter-spacing: -0.01em;
}
.site-header p { display: none; margin: 0; color: var(--muted); }
.site-header {
display: flex; justify-content: space-between;
align-items: center; gap: 1rem;
position: sticky; top: 0; z-index: 2; background: var(--header-bg);
height: var(--header-height); padding: .5rem 0;
/* Stretches the colour and the line under it to the full window width
while the contents stay in the column. */
box-shadow: 0 0 0 100vmax var(--header-bg), 0 1px 0 100vmax var(--line);
clip-path: inset(0 -100vmax -1px);
}
.header-actions { display: flex; align-items: center; gap: .75rem; }
.site-header p, .header-actions { font-size: 15px; }
/* Same font as the tagline; buttons don't inherit it by default. */
.header-actions a, .theme-toggle { font: inherit; }
.header-actions a { color: var(--fg); text-decoration: none; }
.header-actions a:hover { text-decoration: underline; }
/* Espresso brown in light mode, latte beige in dark mode. */
.theme-toggle {
color: var(--latte); background: var(--espresso-button); cursor: pointer;
border: 1px solid var(--espresso-button); border-radius: 999px;
/* Fixed width fits "Espresso", so the Git link doesn't shift. */
width: 6em; padding: .2rem 0; text-align: center;
}
:root[data-theme="dark"] .theme-toggle {
color: var(--espresso); background: var(--latte-button);
border-color: var(--latte-button);
}
.theme-toggle:hover { opacity: .85; }
/* Wide enough for the tagline next to the name; hidden below this. */
@media (min-width: 52rem) {
.site-title { display: flex; align-items: baseline; gap: .75rem; }
.site-header p { display: block; }
}
nav { margin: 2.5rem 0 1rem; padding: 1rem 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
nav { margin: 2.5rem 0 1rem; padding: 1rem 0; border-bottom: 1px solid var(--line); }
nav h2 { margin: 0 0 .5rem; font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
nav ol { margin: 0; padding: 0; list-style: none; }
nav li { display: flex; justify-content: space-between; gap: 1rem; padding: .2rem 0; }
nav li time { color: var(--muted); font-size: .85rem; white-space: nowrap; }
nav a { text-decoration: none; }
nav a:hover { text-decoration: underline; }
nav a.current { font-weight: 700; color: var(--fg); }
/* Side column (14rem) + gap (3rem) + posts (56rem) + padding (2rem). */
@media (min-width: 75rem) {
.wrap { max-width: 75rem; }
.layout { display: grid; grid-template-columns: 14rem 56rem; gap: 3rem; }
nav {
position: sticky; top: var(--header-height); align-self: start;
max-height: calc(100vh - var(--header-height)); overflow-y: auto;
margin: 0; padding: 2.5rem 0 1rem; border: none;
}
nav li, nav li time { display: block; }
}
article { padding: 2.5rem 0; border-bottom: 1px solid var(--line); scroll-margin-top: 1rem; }
/* Keeps in-page jumps (footnotes) clear of the pinned header. */
html { scroll-padding-top: calc(var(--header-height) + 1rem); }
article header h2 { margin: 0; font-size: 1.45rem; line-height: 1.25; }
article header h2 a { color: inherit; text-decoration: none; }
article header h2 a:hover { color: var(--accent); }
.meta { margin: .35rem 0 1.25rem; font-size: .85rem; color: var(--muted); display: flex; gap: .75rem; align-items: center; }
.tag { border: 1px solid var(--line); border-radius: 999px; padding: 0 .55rem; }
img { max-width: 100%; height: auto; }
/* Defined terms (Markdown "*[term]: definition"); the script below fills
data-definition with the text for the bubble. */
abbr[data-definition] {
position: relative; cursor: help;
text-decoration: underline dotted; text-underline-offset: .2em;
}
abbr[data-definition]:is(:hover, :focus)::after {
content: attr(data-definition);
position: absolute; left: 50%; bottom: 100%; z-index: 3;
transform: translateX(-50%); margin-bottom: .35rem;
width: max-content; max-width: 16rem; padding: .35rem .6rem;
background: var(--header-bg); color: var(--fg);
border: 1px solid var(--line); border-radius: 6px;
font-size: .85rem; line-height: 1.4;
}
img { display: block; margin: 0 auto; max-width: 100%; height: auto; }
pre, code { font-family: ui-monospace, "Cascadia Code", Menlo, Consolas, monospace; font-size: .88em; }
code { background: var(--code-bg); padding: .1em .3em; border-radius: 4px; }
pre { background: var(--code-bg); padding: 1rem; border-radius: 6px; overflow-x: auto; }
pre { background: var(--code-block-bg); color: var(--code-text); padding: 1rem; border-radius: 6px; overflow-x: auto; }
pre code { background: none; padding: 0; }
/* Pygments token classes, Darcula colours: comments, keywords, strings,
numbers, function names. */
.codehilite :is(.c, .c1, .cm, .ch, .cs) {
color: var(--code-comment); font-style: italic;
}
.codehilite :is(.k, .kc, .kd, .kn, .kr, .kt) { color: var(--code-keyword); }
.codehilite :is(.s, .s1, .s2, .sa, .sd, .se, .si) {
color: var(--code-string);
}
.codehilite :is(.m, .mi, .mf, .mh, .il) { color: var(--code-number); }
.codehilite :is(.nf, .nb, .fm) { color: var(--code-function); }
blockquote { margin: 1.25rem 0; padding-left: 1rem; border-left: 3px solid var(--line); color: var(--muted); }
table { border-collapse: collapse; width: 100%; display: block; overflow-x: auto; }
th, td { border: 1px solid var(--line); padding: .4rem .6rem; text-align: left; }
@@ -56,22 +166,102 @@
<body>
<div class="wrap">
<header class="site-header">
<h1>Pascal Maas</h1>
<p>Notes and writing.</p>
<div class="site-title">
<h1>Pascal Maas</h1>
<p>A blog about bioinformatics in metabolomics</p>
</div>
<div class="header-actions">
<a href="https://git.pascal-maas.nl/pascalmaas" target="_blank"
rel="me noopener">Git</a>
<a href="mailto:contact@pascal-maas.nl">Contact</a>
<button class="theme-toggle" type="button"
title="Switch to dark mode">Espresso</button>
</div>
</header>
<nav aria-label="Posts">
<h2>Posts</h2>
<ol>
<div class="layout">
<nav aria-label="Posts">
<h2>Posts</h2>
<ol>
{{toc}}
</ol>
</nav>
</ol>
</nav>
<main>
<main>
{{posts}}
</main>
</main>
</div>
<footer>© {{year}} Pascal Maas</footer>
</div>
<script>
const root = document.documentElement;
const button = document.querySelector(".theme-toggle");
// The button names the mode it switches to, not the current one.
function applyTheme(theme) {
root.dataset.theme = theme;
if (theme === "dark") {
button.textContent = "Latte";
button.title = "Switch to light mode";
} else {
button.textContent = "Espresso";
button.title = "Switch to dark mode";
}
}
applyTheme(root.dataset.theme || "light");
button.addEventListener("click", function () {
const theme = root.dataset.theme === "dark" ? "light" : "dark";
applyTheme(theme);
try {
localStorage.setItem("theme", theme);
} catch (error) {}
});
const header = document.querySelector(".site-header");
const articles = Array.from(document.querySelectorAll("main article"));
const links = document.querySelectorAll("nav a");
// First use of each term per post keeps its definition, moved out of
// "title" so the browser's plain tooltip doesn't show beside the bubble.
for (const article of articles) {
const seen = new Set();
for (const term of article.querySelectorAll("abbr[title]")) {
if (seen.has(term.textContent)) {
term.replaceWith(term.textContent);
continue;
}
seen.add(term.textContent);
term.dataset.definition = term.title;
term.removeAttribute("title");
term.tabIndex = 0;
}
}
// The current post is the last one whose top has passed a line 3rem
// (48px) below the header; at the very bottom, it is always the last post.
function markCurrentPost() {
if (articles.length === 0) {
return;
}
const line = header.getBoundingClientRect().bottom + 48;
const viewBottom = window.innerHeight + window.scrollY;
const atBottom = viewBottom >= root.scrollHeight - 2;
let current = articles[0];
for (const article of articles) {
if (atBottom || article.getBoundingClientRect().top <= line) {
current = article;
}
}
for (const link of links) {
link.classList.toggle("current", link.hash === "#" + current.id);
}
}
markCurrentPost();
window.addEventListener("scroll", markCurrentPost, { passive: true });
window.addEventListener("resize", markCurrentPost);
</script>
</body>
</html>

16
topics/starting-a-blog.md Normal file
View File

@@ -0,0 +1,16 @@
---
title: Starting a blog
date: 2026-10-04
---
Hi there! This is my first post in hopefully many. I'm Pascal and work as a bioinformatician in the field of metabolomics. I studied bioinformatics, getting my master degree in 2020 at the University of Amsterdam and since then I work at the Metabolomics Analytical Centre, which is part of the University of Leiden. Here I develop software to support the data analysis of large volumes of samples. This however, remains a challenge for reasons that hopefully become clear through posts on this blog. Ofcourse, I will also attempt to provide solutions for practical challenges I've encounted.
I decided to start this blog to share my experience with data analysis in metabolomics as publishing articles is a slow process, and might not be the right platform for sharing everyday encounters. I've been enjoying reading other blogs like [yet another metabolomics club](https://metabolomics.blog) & [Notes from a data witch](https://blog.djnavarro.net), and it seemed the right fit to share my thoughts, ideas and code snippets. I'll post those to my [git](https://git.pascal-maas.nl/explore/repos). Except to see code mostly written in R, Python and perhaps some C++.
In the office I'm known for drinking way too much coffee (although I don't see anything wrong with my consumption..). For this reason I called the light/dark mode after coffee drinks and themed the blog as such. I'm genuinely curious how big the caffeine spike would be on our LC-MS systems, but since the assay uses plasma it will remain a mystery to this day. Perhaps DBS based metabolomics would be my best bet..
Anyways, enjoy your stay!
*[bioinformatics]: An interdisciplinary field of science that develops computational methods and software tools for understanding biological data
*[DBS]: Dried Blood Spots, a promising, minimally invasive techninique for blood sampling.