root@nijat:~/MIT/HTMAA26F$ cat week_00.md

week 00 · 09/09

Introduction, Computer-Aided Design

To start base of my website I started structuring my website:

  • dark only, monochrome, no accent color
  • monospace font (IBM Plex Mono), terminal feeling
  • left sidebar with all weeks, content column in the middle
  • one folder per week, one .md file per page

Folder structure

index.md                  home
content/
  about/  final/
  week_00 … week_13/
    index.md  →  index.html
    img/  00_Name.jpg
    vid/  00_Name.mp4
assets/  css/  js/  img/
_dev/    build.py  serve.py  template.html

Media is numbered in the order it was made (07_KerfTestV1.jpg). Bigger weeks get a subfolder per topic (img/printing/, img/scanning/). _dev/ stays local and never goes to the repo.

Progression

09/16 · starting point

<body>
Nijat Mahamaliyev's fab class home page
</body>

Base design

Colors and font live in one place, everything else uses them.

:root {
  --bg: #0b0b0b;
  --fg: #d2d2d2;
  --fg-hi: #f4f4f4;
  --fg-dim: #8a8a8a;
  --mono: "IBM Plex Mono", "Courier New", monospace;
}
.shell { display: grid; grid-template-columns: 300px minmax(0, 1fr); }
.main  { max-width: 920px; margin: 0 auto; }

Every page starts with a front matter. draft weeks show up greyed out in the sidebar and aren't linked yet.

---
week: 03
date: 09/30
title: 3D Scanning and Printing
status: done
---

Each page gets a small terminal prompt on top.

PS1 = "root@nijat:~/MIT/HTMAA26F$"
prompt = f'<p class="prompt"><span class="ps1">{PS1}</span> cat {page["file"]}</p>'

On the home page the commands type themselves out, once per visit.

var tick = function () {
  if (j < texts[i].length) {
    cmds[i].textContent += texts[i].charAt(j++);
    setTimeout(tick, 50 + Math.random() * 70);
  } else {
    i++; j = 0;
    setTimeout(tick, 450);
  }
};

Images and layout

Images on the same line = one row, a new line = a new row. All images in a row get the same height and the row fills the column.

![](img/01.jpg) ![](img/02.jpg)
![](img/03.jpg)

The build reads each image's ratio and the CSS does the rest.

w, h = Image.open(path).size
fig = etree.Element("figure", {"style": f"--r:{w / h:.4f}"})
.row        { display: flex; gap: 12px; }
.row figure { flex: var(--r) 1 0; }
.row img    { width: 100%; aspect-ratio: var(--r); }

The page also follows the .md layout: line breaks, extra blank lines and indents show up the same way I type them.

Videos

YouTube links become embeds.

![](https://www.youtube.com/watch?v=G94FDMGLwCc)

Safari wouldn't play my videos locally because the preview server didn't answer byte-range requests.

if self.headers.get("Range", "").startswith("bytes="):
    return self.send_range(path)

Videos are compressed before they go in.

ffmpeg -i in.mov -c:v libx264 -crf 26 -preset slow -vf scale=-2:720 -an -movflags +faststart out.mp4

Stale pages + favicon

After a push, browsers kept showing old pages (the server sends no cache headers). Now every build writes an id, and each page checks it and reloads once if it's outdated.

fetch(new URL("../version.txt", script.src), { cache: "no-store" })
  .then(function (r) { return r.text(); })
  .then(function (live) { if (live.trim() !== build) location.reload(); });

The favicon is the stone-arrow from my vinyl cut.

Vimeo + arrow keys

return f"https://player.vimeo.com/video/{id}?dnt=1"

In full-screen images, ← → go to the previous/next image.

if (ev.key === "ArrowRight") show(at + 1);
if (ev.key === "ArrowLeft") show(at - 1);

md → html

_dev/build.py reads every index.md, converts it and drops it into one shared template.

meta, body = parse_front_matter(src.read_text())
md = markdown.Markdown(extensions=["toc", "fenced_code", "tables", "nl2br", MediaExtension(src.parent)])
values = {"sidebar": sidebar(page, site), "header": header(page), "content": md.convert(body)}
html = re.sub(r"\{\{(\w+)\}\}", lambda m: values[m[1]], template)
<aside class="side">{{sidebar}}</aside>
<main class="main">
  <header class="page-head">{{header}}</header>
  <article class="content">{{content}}</article>
</main>

The ## headings become the small index under the current week in the sidebar.

Live preview

python3 _dev/serve.py

It serves the site at localhost:8000, rebuilds on every save and reloads the open tab. I keep it open next to the .md in VS Code (Simple Browser).

while True:
    time.sleep(0.4)
    if snapshot() != last:
        last = snapshot()
        build.build()
        version += 1
setInterval(function () {
  fetch("/__version").then(function (r) { return r.text(); })
    .then(function (t) { if (v === null) v = t; else if (t !== v) location.reload(); });
}, 600);

AI usage

Claude Code was used to help writing and expanding the website code (build script, preview server, CSS, JS) from my sketches, decisions and screenshot feedback, and to compress media.