Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,18 @@ jobs:
- name: Build site
run: stack exec --system-ghc site rebuild

- name: Build CSS
# The Node/Tailwind toolchain lives in tools/tailwind/; its npm scripts
# cd back to the repo root before invoking postcss.
working-directory: tools/tailwind
run: |
npm ci
npm run build:production

# Gate every push/PR: dev.css is a checked-in snapshot of `npm run build`
# (concatenated onto tailwind.css by site.hs for Node-less previews).
# Regenerate it and fail if the committed copy has drifted.
- name: Check dev.css is in sync with Tailwind output
# assets/css/tailwind.built.css is the checked-in Tailwind output, which
# `site rebuild` above copied into _site — i.e. it is what ships. So this
# gate is what keeps a stale stylesheet from being deployed: rebuild it
# from source (the Node/Tailwind toolchain lives in tools/tailwind/, whose
# npm scripts cd back to the repo root before invoking postcss) and fail
# if the committed copy differs.
- name: Check assets/css/tailwind.built.css is in sync with Tailwind output
run: |
npm --prefix tools/tailwind run build:dev-snapshot
if ! git diff --quiet -- dev.css; then
echo "::error file=dev.css::dev.css is out of date. Regenerate it with 'cd tools/tailwind && npm run build:dev-snapshot' and commit the result."
git --no-pager diff -- dev.css
(cd tools/tailwind && npm ci && npm run build)
if ! git diff --quiet -- assets/css/tailwind.built.css; then
echo "::error file=assets/css/tailwind.built.css::The checked-in CSS is out of date. Regenerate it with 'cd tools/tailwind && npm run build' and commit the result."
git --no-pager diff -- assets/css/tailwind.built.css
exit 1
fi

Expand Down
33 changes: 18 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,36 +76,39 @@ The site will be built in the `_site` directory, and you can open the files in y

The site is styled with [Tailwind CSS](https://tailwindcss.com) (v4). Tailwind is a CSS framework whose classes are short abbreviations for inline styles (e.g. `pt-4`, `text-center`), scattered directly across the HTML templates and content. At build time the Tailwind compiler acts as a kind of "CSS tree-shaker": it scans the site for the Tailwind classes actually in use and emits only the CSS needed for them.

Two files matter:
Three files matter:

- `assets/css/tailwind.css` — the Tailwind entry point. It holds the v4 CSS-native configuration (`@theme` colors/fonts, `@plugin`, and the `@source` lines that tell the compiler which directories to scan). **New pages that use Tailwind classes must add an `@source` line here**, or those classes will be dropped from the production build.
- `assets/css/tailwind.css` — the Tailwind entry point. It holds the v4 CSS-native configuration (`@theme` colors/fonts, `@plugin`, and the `@source` lines that tell the compiler which directories to scan). **New pages that use Tailwind classes must add an `@source` line here**, or those classes will be dropped from the build. This file is compiler *input*; it is never served.
- `assets/css/tailwind.built.css` — the compiler's output, **checked into the repository** and served as `assets/css/tailwind.css`. This is the CSS the live site loads, so it must be regenerated and committed whenever it changes (see below).
- `assets/css/main.css` — hand-written CSS that is *not* processed by Tailwind. This is the place for ordinary, non-Tailwind styles.

### Do I need Node installed?

**No, if you are only editing content or Haskell code.** Hakyll concatenates a checked-in snapshot, `dev.css`, onto `assets/css/tailwind.css` at build time (see the `match "assets/css/tailwind.css"` rule in `site.hs`). `dev.css` is a pre-generated copy of the real Tailwind output, so `stack exec -- site build` on its own produces a site that looks close enough to preview — no Node toolchain required. This keeps the site accessible to contributors of all skill sets.
**No, if you are only editing content or Haskell code.** Hakyll copies the checked-in `assets/css/tailwind.built.css` into the site, so `stack exec -- site build` alone gives you the real stylesheet — no Node toolchain required. This keeps the site accessible to contributors of all skill sets.

**Yes, if you are changing the appearance.** Because `dev.css` is only a snapshot, it does *not* reflect edits to `assets/css/tailwind.css`, `assets/css/main.css`, `tools/tailwind/postcss.config.js`, or any *newly used* Tailwind class. To regenerate the real CSS you need [Node.js](https://nodejs.org). The Node/Tailwind toolchain lives in [`tools/tailwind/`](tools/tailwind/) (see its README), separate from the Hakyll project, so run npm from there:
**Yes, if you are changing the appearance** — that is, editing `assets/css/tailwind.css`, `assets/css/main.css`, `tools/tailwind/postcss.config.js`, or *using a Tailwind class the site did not use before* (Tailwind only emits the classes it finds, so a brand-new class has no CSS until the compiler runs). You will need [Node.js](https://nodejs.org). The toolchain lives in [`tools/tailwind/`](tools/tailwind/) (see its README), separate from the Hakyll project, so run npm from there:

```bash
cd tools/tailwind
npm ci # once, to install the toolchain
npm run build # compile -> _site/assets/css/tailwind.css
npm run build:production # minified build (NODE_ENV=production)
npm ci # once, to install the toolchain
npm run build # compile -> assets/css/tailwind.built.css
npm run watch # same, then recompile on source/content changes
```

If you do not want to install `npm` globally and have `nix` available, drop into the Nix development shell which provides `npm` and a basic Haskell toolchain.

If your change alters the CSS, regenerate the checked-in `dev.css` snapshot and commit it:
**Commit the regenerated `assets/css/tailwind.built.css` with your change.** CI reruns `npm run build` on every push and PR and fails if the committed copy has drifted, so a stale stylesheet cannot reach the live site.

### Live preview

Run both watchers and the two chain into each other — postcss writes `assets/css/tailwind.built.css`, which Hakyll then copies into `_site` and reloads:

```bash
cd tools/tailwind
npm run build:dev-snapshot # recompile the dev.css snapshot in place
cabal run site -- watch & # or: stack exec -- site watch
cd tools/tailwind && npm run watch
```

CI runs this same command and fails the build if the committed `dev.css` is out of date, so a stale snapshot cannot slip through review.

> **Note:** `dev.css` is only an *unminified* snapshot of `npm run build`; it still differs from the minified `npm run build:production` output shipped to production, so verify appearance-critical changes against a real production build.
> **Note:** `npm run watch` is *additive* — it picks up newly used classes, but a class you removed keeps its CSS until the next one-shot `npm run build`. So finish with `npm run build` before committing, or CI's check will complain about the leftovers.

## CI

Expand All @@ -117,8 +120,8 @@ The general steps are:
2. Install Haskell and Node.js
3. Restore the cached build artefacts
4. Build the `site` executable
5. Rebuild the site contents using the `site` executable
6. Build the production CSS (`cd tools/tailwind && npm ci && npm run build:production`)
5. Rebuild the site contents using the `site` executable (this copies the checked-in CSS into `_site`)
6. Recompile the CSS (`cd tools/tailwind && npm ci && npm run build`) and fail if the committed `assets/css/tailwind.built.css` has drifted
7. Check out the `main` branch
8. Copy the `_site` directory over the `main` branch contents
9. Commit and push the site contents to the `main` branch.
Expand Down
47 changes: 20 additions & 27 deletions assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,34 @@ textarea::placeholder {
opacity: 1;
}

a {
cursor: pointer;
--tw-text-opacity: 1;
color: rgba(109, 40, 217, var(--tw-text-opacity));
}
/*
* Element defaults for text colour live in `@layer base` so that Tailwind's
* `text-*` utilities (later `utilities` layer) can override them — e.g. the
* light paragraph/link colours on the dark panels. Everything outside this
* block stays unlayered on purpose and therefore beats utilities.
*/
@layer base {
a {
cursor: pointer;
color: #6d28d9; /* purple-700 */
}

a:hover {
text-decoration: underline;
}
a:hover {
color: #4c1d95; /* purple-900 */
text-decoration: underline;
}

a:hover,
a:visited {
--tw-text-opacity: 1;
color: rgba(139, 92, 246, var(--tw-text-opacity));
p {
color: #52525b; /* gray-600 */
}
}

.arrow-link {
--tw-border-opacity: 1;
border-color: rgba(109, 40, 217, var(--tw-border-opacity));
border-color: #6d28d9;
}

.arrow-link:hover {
--tw-border-opacity: 1;
border-color: rgba(139, 92, 246, var(--tw-border-opacity));
border-color: #4c1d95;
}

.arrow-link {
Expand Down Expand Up @@ -181,17 +185,6 @@ h6 {
* {
--tw-ring-inset: var(--tw-empty);
}

.divide-y-2 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(2px * (1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(2px * var(--tw-divide-y-reverse));
}

p {
--tw-text-opacity: 1;
color: rgba(113, 113, 122, var(--tw-text-opacity));
}
.youtube-video {
position: relative;
width: 100%;
Expand Down
72 changes: 72 additions & 0 deletions dev.css → assets/css/tailwind.built.css
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,24 @@
.z-20 {
z-index: 20;
}
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 64rem) {
max-width: 64rem;
}
@media (width >= 80rem) {
max-width: 80rem;
}
@media (width >= 96rem) {
max-width: 96rem;
}
}
.m-4 {
margin: calc(var(--spacing) * 4);
}
Expand Down Expand Up @@ -843,6 +861,13 @@
.w-48 {
width: calc(var(--spacing) * 48);
}
.w-auto {
width: auto;
}
.w-fit {
width: -moz-fit-content;
width: fit-content;
}
.w-full {
width: 100%;
}
Expand Down Expand Up @@ -876,6 +901,9 @@
.cursor-pointer {
cursor: pointer;
}
.grid-cols-\[auto_1fr\] {
grid-template-columns: auto 1fr;
}
.flex-col {
flex-direction: column;
}
Expand Down Expand Up @@ -913,6 +941,13 @@
margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-2 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-4 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
Expand All @@ -934,6 +969,10 @@
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
}
}
.gap-x-3 {
-moz-column-gap: calc(var(--spacing) * 3);
column-gap: calc(var(--spacing) * 3);
}
.space-x-4 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
Expand All @@ -955,6 +994,9 @@
margin-inline-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)));
}
}
.gap-y-1 {
row-gap: calc(var(--spacing) * 1);
}
.divide-y-2 {
:where(& > :not(:last-child)) {
--tw-divide-y-reverse: 0;
Expand All @@ -964,6 +1006,11 @@
border-bottom-width: calc(2px * calc(1 - var(--tw-divide-y-reverse)));
}
}
.divide-gray-200 {
:where(& > :not(:last-child)) {
border-color: var(--color-gray-200);
}
}
.self-start {
align-self: flex-start;
}
Expand Down Expand Up @@ -1149,6 +1196,10 @@
font-size: var(--text-4xl);
line-height: var(--tw-leading, var(--text-4xl--line-height));
}
.text-base {
font-size: var(--text-base);
line-height: var(--tw-leading, var(--text-base--line-height));
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
Expand Down Expand Up @@ -1207,6 +1258,9 @@
.text-gray-600 {
color: var(--color-gray-600);
}
.text-purple-200 {
color: var(--color-purple-200);
}
.text-purple-700 {
color: var(--color-purple-700);
}
Expand Down Expand Up @@ -1249,6 +1303,13 @@
}
}
}
.hover\:text-white {
&:hover {
@media (hover: hover) {
color: var(--color-white);
}
}
}
.focus\:outline-none {
&:focus {
--tw-outline-style: none;
Expand Down Expand Up @@ -1598,6 +1659,11 @@
margin-top: calc(var(--spacing) * 12);
}
}
.md\:mt-16 {
@media (width >= 48rem) {
margin-top: calc(var(--spacing) * 16);
}
}
.md\:mt-24 {
@media (width >= 48rem) {
margin-top: calc(var(--spacing) * 24);
Expand Down Expand Up @@ -1725,6 +1791,12 @@
line-height: var(--tw-leading, var(--text-5xl--line-height));
}
}
.md\:text-lg {
@media (width >= 48rem) {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
}
.md\:leading-tight {
@media (width >= 48rem) {
--tw-leading: var(--leading-tight);
Expand Down
31 changes: 14 additions & 17 deletions assets/css/tailwind.css
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
/**
* This is the Tailwind entry point (source). It is NOT overwritten by the
* build — its compiled *output* lands at _site/assets/css/tailwind.css (see
* steps below). Only put Tailwind config/directives here; hand-written CSS
* belongs in assets/css/main.css.
* This is the Tailwind entry point (source), and it is never served: only put
* Tailwind config/directives here, and hand-written CSS in assets/css/main.css.
*
* NOTE: The underlying implementation details of how this works are as follows
* 1. tailwindcss needs these directives in order to know where to generate the
* output
* 2. first, hakyll is run and copies this source file verbatim to
* _site/assets/css/tailwind.css
* 3. then, after hakyll is done, postcss is run on _this_ source
* assets/css/tailwind.css file. Postcss will, during execution, invoke
* tailwindcss. tailwindcss (the compiler) will scan all the files in the
* site for tailwind classes (including _this_ source file).
* 4. postcss's output file is configured to be _site/assets/css/tailwind.css,
* so postcss overwrites the copy hakyll made in step 2 with the real,
* compiled CSS. This source file is never modified.
* How it reaches the browser:
* 1. postcss runs tailwindcss over this file (`npm run build` in
* tools/tailwind), scanning the @source paths below for utility classes, and
* writes the compiled CSS to assets/css/tailwind.built.css. That output is
* checked into the repo, so building the site needs no Node toolchain.
* 2. hakyll copies assets/css/tailwind.built.css to
* _site/assets/css/tailwind.css — the URL the templates link. This source
* file is neither modified nor copied.
*
* So: regenerate and commit the built file whenever you touch this one, use a
* new utility class, or edit the postcss config. CI fails on any drift.
*/

/*
Expand All @@ -24,7 +21,7 @@
* @source paths below are scanned — mirrors the old
* topLevelFoldersWithTailwind/topLevelFilesWithTailwind purge.content list.
* New pages using Tailwind utility classes must add an @source line here,
* or their classes will be missing from the production build.
* or their classes will be missing from the compiled CSS.
*/
/*
* The Node/Tailwind toolchain lives in tools/tailwind/ (see its README), so
Expand Down
8 changes: 8 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@
pkgs.ghc
pkgs.haskell-language-server

# Screenshotting pages during design work.
(pkgs.python3.withPackages (ps: [ ps.playwright ]))

# System.
pkgs.zlib
];

env = {
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = "true";
};
};
};
flake = { };
Expand Down
Loading
Loading