pandoc-ed!

This commit is contained in:
tkucar
2025-02-25 18:07:32 +01:00
parent c0d05c9f4d
commit a54823cb5b
21 changed files with 1111 additions and 812 deletions

1
.envrc
View File

@@ -1 +0,0 @@
use flake .

1
.gitignore vendored
View File

@@ -0,0 +1 @@
build/

View File

@@ -1,3 +1,9 @@
The Pandoc Monospace is a Static Site Generator based on The Monospace Web project by Oskar Wickström.
All changes to the original project are unlicensed (C0).
The Monospace Web is licensed under the MIT license.
Copyright 2024 Oskar Wickström
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -1,12 +0,0 @@
VERSION=$(shell jq -r .version package.json)
DATE=$(shell date +%F)
all: index.html
clean:
rm -f index.html
index.html: demo/index.md demo/template.html Makefile
pandoc --toc -s --css src/reset.css --css src/index.css -Vversion=v$(VERSION) -Vdate=$(DATE) -i $< -o $@ --template=demo/template.html
.PHONY: all clean

View File

@@ -1,16 +0,0 @@
# The Monospace Web
Monospace fonts are dear to many of us. Some find them more readable, consistent, and beautiful, than their proportional alternatives. Maybe were just brainwashed from spending years in terminals? Or are we hopelessly nostalgic? Im not sure. But I like them, and thats why I started experimenting with all-monospace Web.
https://owickstrom.github.io/the-monospace-web/
## Build
```
nix develop # or `direnv allow .`
make
```
## License
[MIT](LICENSE.md)

308
convert.sh Executable file
View File

@@ -0,0 +1,308 @@
#!/bin/bash
# Configuration
VERSION="1.0.1"
SOURCE_DIR="src"
OUTPUT_DIR="build"
TEMPLATE="template.html"
CSS_FILES=(
"files/css/reset.css"
"files/css/index.css"
)
# Add supported document formats
SUPPORTED_FORMATS=(
"*.md" # Markdown
"*.markdown"
"*.mdx"
"*.org" # Org mode
"*.rst" # reStructuredText
"*.txt" # Plain text
"*.tex" # LaTeX
"*.wiki" # MediaWiki markup
"*.dokuwiki" # DokuWiki markup
"*.textile" # Textile
"*.asciidoc" # AsciiDoc
)
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Add after VERSION declaration
PDF_OUTPUT=false
# Error handling function
error() {
echo -e "${RED}Error: $1${NC}" >&2
exit 1
}
# Warning function
warning() {
echo -e "${YELLOW}Warning: $1${NC}" >&2
}
# Success function
success() {
echo -e "${GREEN}$1${NC}"
}
# Info function
info() {
[ "$VERBOSE" = true ] && echo -e "${BLUE}$1${NC}"
}
# Help function
show_help() {
cat << EOF
$(basename "$0") v$VERSION
Convert documents to HTML or PDF using pandoc.
Usage: $(basename "$0") [OPTIONS]
Options:
-h, --help Show this help message
-v, --version Show version information
-s, --source DIR Source directory (default: $SOURCE_DIR)
-o, --output DIR Output directory (default: $OUTPUT_DIR)
-t, --template FILE Template file (default: $TEMPLATE)
-l, --list-formats List supported file formats
--verbose Enable verbose output
--clean Remove output directory before starting
--pdf Generate PDF instead of HTML output
Examples:
$(basename "$0") # Use default settings (HTML output)
$(basename "$0") --pdf # Generate PDF output
$(basename "$0") -s content -o dist # Use custom directories
$(basename "$0") --clean # Clean build
Supported formats:
${SUPPORTED_FORMATS[@]/#/ }
For more information, visit: https://pandoc.org/MANUAL.html
EOF
}
# Version function
show_version() {
echo "$(basename "$0") version $VERSION"
}
# List supported formats
list_formats() {
echo "Supported input formats:"
for format in "${SUPPORTED_FORMATS[@]}"; do
echo " $format"
done
}
# Check if pandoc is installed before continuing
if ! command -v pandoc >/dev/null 2>&1; then
error "Pandoc is required but not installed. Please install pandoc."
fi
# Function to create CSS arguments array
create_css_args() {
local source_file="$1"
local source_dir="$2"
# Calculate relative path from the HTML file to the root
local rel_path="${source_file#"$source_dir"/}"
local dir_depth
dir_depth=$(echo "$rel_path" | tr -cd '/' | wc -c)
local path_prefix=""
# Add "../" for each directory level
for ((i=0; i<dir_depth; i++)); do
path_prefix="../$path_prefix"
done
# Return array of CSS arguments
local css_args=()
for css in "${CSS_FILES[@]}"; do
css_args+=("--css" "$path_prefix$css")
done
printf "%s\n" "${css_args[@]}"
}
# Function to process a single file
process_file() {
local source_file="$1"
local source_dir="$2"
local target_dir="$3"
local extension="${source_file##*.}"
info "Processing: $source_file"
# Get relative path from source directory
local rel_path="${source_file#"$source_dir"/}"
local dir_path
dir_path=$(dirname "$rel_path")
local filename
filename=$(basename "$source_file")
local output_filename
output_filename="${filename%.*}.$([ "$PDF_OUTPUT" = true ] && echo "pdf" || echo "html")"
# Create target directory if it doesn't exist
mkdir -p "$target_dir/$dir_path"
# Format-specific options
local format_opts=""
case "$extension" in
"tex")
format_opts="--mathjax"
;;
"org")
format_opts="--shift-heading-level-by=1"
;;
"rst")
format_opts="--section-divs"
;;
*)
format_opts="--toc"
;;
esac
if [ "$PDF_OUTPUT" = true ]; then
# Generate PDF file
if ! pandoc --toc -s \
$format_opts \
--pdf-engine=xelatex \
-Vversion="v$(date +%s)" \
-Vdate="$(date +%F)" \
-Vyear="$(date +%Y)" \
-i "$source_file" \
-o "$target_dir/$dir_path/$output_filename"; then
error "Failed to convert $source_file to PDF"
return 1
fi
else
# Generate HTML file
if ! pandoc --toc -s \
$format_opts \
$(create_css_args "$source_file" "$source_dir") \
-Vversion="v$(date +%s)" \
-Vdate="$(date +%F)" \
-Vyear="$(date +%Y)" \
-i "$source_file" \
-o "$target_dir/$dir_path/$output_filename" \
--template="$TEMPLATE"; then
error "Failed to convert $source_file to HTML"
return 1
fi
fi
success "Converted: $source_file -> $target_dir/$dir_path/$output_filename"
return 0
}
# Parse command line arguments
VERBOSE=false
CLEAN=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-v|--version)
show_version
exit 0
;;
-s|--source)
SOURCE_DIR="$2"
shift 2
;;
-o|--output)
OUTPUT_DIR="$2"
shift 2
;;
-t|--template)
TEMPLATE="$2"
shift 2
;;
-l|--list-formats)
list_formats
exit 0
;;
--verbose)
VERBOSE=true
shift
;;
--clean)
CLEAN=true
shift
;;
--pdf)
PDF_OUTPUT=true
shift
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Add after parsing arguments
if [ "$PDF_OUTPUT" = true ]; then
if ! command -v xelatex >/dev/null 2>&1; then
error "PDF generation requires xelatex. Please install texlive-xelatex package."
fi
fi
# Create find command pattern for supported formats
FIND_PATTERN=()
for format in "${SUPPORTED_FORMATS[@]}"; do
FIND_PATTERN+=(-o -name "$format")
done
# Remove the first -o since we don't need it for the first pattern
FIND_PATTERN=("${FIND_PATTERN[@]:1}")
# Update error checks
if [ "$PDF_OUTPUT" = false ] && [ ! -f "$TEMPLATE" ]; then
error "Template file $TEMPLATE not found!"
fi
if [ ! -d "$SOURCE_DIR" ]; then
error "Source directory $SOURCE_DIR not found!"
fi
# Clean up old build if requested
if [ "$CLEAN" = true ]; then
echo "Cleaning up old build directory..."
rm -rf "$OUTPUT_DIR"
fi
# Create output directory
mkdir -p "$OUTPUT_DIR"
# Copy all files from source directory if it exists
[ "$VERBOSE" = true ] && info "Copying assets..."
if [ -d "$SOURCE_DIR/files" ]; then
cp -r "$SOURCE_DIR/files" "$OUTPUT_DIR/"
info "Copied files directory and its contents"
else
warning "files directory not found in source"
fi
# Find and process all supported files
[ "$VERBOSE" = true ] && info "Processing files..."
if find "$SOURCE_DIR" -type f \( "${FIND_PATTERN[@]}" \) | while read -r file; do
process_file "$file" "$SOURCE_DIR" "$OUTPUT_DIR" || exit 1
done; then
# Remove any hidden files (except .htaccess)
find "$OUTPUT_DIR/" -type f -name '.*' ! -name ".htaccess" -delete
success "Conversion complete! Output in $OUTPUT_DIR/"
exit 0
else
error "Conversion failed!"
fi

View File

@@ -1,76 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$>
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$
$if(date-meta)$
<meta name="dcterms.date" content="$date-meta$" />
$endif$
$if(keywords)$
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
$endif$
$if(description-meta)$
<meta name="description" content="$description-meta$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ $endif$$pagetitle$</title>
$for(css)$
<link rel="stylesheet" href="$css$" />
$endfor$
$for(header-includes)$
$header-includes$
$endfor$
$if(math)$
$math$
$endif$
</head>
<body>
$for(include-before)$
$include-before$
$endfor$
<table class="header">
<tr>
<td colspan="2" rowspan="2" class="width-auto">
<h1 class="title">$title$</h1>
<span class="subtitle">$subtitle$</span>
</td>
<th>Version</th>
<td class="width-min">$version$</td>
</tr>
<tr>
<th>Updated</th>
<td class="width-min"><time style="white-space: pre;">$date$</time></td>
</tr>
<tr>
<th class="width-min">Author</th>
<td class="width-auto"><a href="$author-url$"><cite>$author$</cite></a></td>
<th class="width-min">License</th>
<td>MIT</td>
</tr>
</table>
<label class="debug-toggle-label"><input type="checkbox" class="debug-toggle" /> Debug mode</label>
$if(abstract)$
<div class="abstract">
<div class="abstract-title">$abstract-title$</div>
$abstract$
</div>
$endif$
$if(toc)$
<nav id="$idprefix$TOC" role="doc-toc">
$if(toc-title)$
<h2 id="$idprefix$toc-title">$toc-title$</h2>
$endif$
$table-of-contents$
</nav>
$endif$
$body$
$for(include-after)$
$include-after$
$endfor$
<div class="debug-grid"></div>
<script src="src/index.js"></script>
</body>
</html>

43
flake.lock generated
View File

@@ -1,43 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1723891200,
"narHash": "sha256-uljX21+D/DZgb9uEFFG2dkkQbPZN+ig4Z6+UCLWFVAk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a0d6390cb3e82062a35d0288979c45756e481f60",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,36 +0,0 @@
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
systems.url = "github:nix-systems/default";
};
outputs =
{
self,
systems,
nixpkgs,
}:
let
eachSystem = nixpkgs.lib.genAttrs (import systems);
in
{
devShells = eachSystem (
system:
let
pkgs = (import nixpkgs { inherit system; });
in
{
default = pkgs.mkShell {
packages = with pkgs; [
live-server
pandoc
jq
gnumake
];
};
}
);
};
}

View File

@@ -1,324 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="Oskar Wickström" />
<title>The Monospace Web</title>
<link rel="stylesheet" href="src/reset.css" />
<link rel="stylesheet" href="src/index.css" />
</head>
<body>
<table class="header">
<tr>
<td colspan="2" rowspan="2" class="width-auto">
<h1 class="title">The Monospace Web</h1>
<span class="subtitle">A minimalist design exploration</span>
</td>
<th>Version</th>
<td class="width-min">v0.1.5</td>
</tr>
<tr>
<th>Updated</th>
<td class="width-min"><time style="white-space: pre;">2025-01-25</time></td>
</tr>
<tr>
<th class="width-min">Author</th>
<td class="width-auto"><a href="https://wickstrom.tech"><cite>Oskar
Wickström</cite></a></td>
<th class="width-min">License</th>
<td>MIT</td>
</tr>
</table>
<label class="debug-toggle-label"><input type="checkbox" class="debug-toggle" /> Debug mode</label>
<nav id="TOC" role="doc-toc">
<h2 id="toc-title">Contents</h2>
<ul class="incremental">
<li><a href="#introduction" id="toc-introduction">Introduction</a></li>
<li><a href="#the-basics" id="toc-the-basics">The Basics</a></li>
<li><a href="#lists" id="toc-lists">Lists</a></li>
<li><a href="#tables" id="toc-tables">Tables</a></li>
<li><a href="#forms" id="toc-forms">Forms</a></li>
<li><a href="#grids" id="toc-grids">Grids</a></li>
<li><a href="#ascii-drawings" id="toc-ascii-drawings">ASCII
Drawings</a></li>
<li><a href="#media" id="toc-media">Media</a></li>
<li><a href="#discussion" id="toc-discussion">Discussion</a></li>
</ul>
</nav>
<h2 id="introduction">Introduction</h2>
<p>Monospace fonts are dear to many of us. Some find them more readable,
consistent, and beautiful, than their proportional alternatives. Maybe
were just brainwashed from spending years in terminals? Or are we
hopelessly nostalgic? Im not sure. But I like them, and thats why I
started experimenting with all-monospace Web.</p>
<p>On this page, I use a monospace grid to align text and draw diagrams.
Its generated from a simple Markdown document (using Pandoc), and the
CSS and a tiny bit of Javascript renders it on the grid. The page is
responsive, shrinking in character-sized steps. Standard elements should
<em>just work</em>, at least thats the goal. Its semantic HTML,
rendered as if we were back in the 70s.</p>
<p>All right, but is this even a good idea? Its a technical and
creative challenge and I like the aestethic. If youd like to use it,
feel free to fork or copy the bits you need, respecting the license. I
might update it over time with improvements and support for more
standard elements.</p>
<h2 id="the-basics">The Basics</h2>
<p>This document uses a few extra classes here and there, but mostly
its just markup. This, for instance, is a regular paragraph.</p>
<p>Look at this horizontal break:</p>
<hr>
<p>Lovely. We can hide stuff in the <code>&lt;details</code>&gt;
element:</p>
<details>
<summary>
A short summary of the contents
</summary>
<p>
Hidden gems.
</p>
</details>
<h2 id="lists">Lists</h2>
<p>This is a plain old bulleted list:</p>
<ul class="incremental">
<li>Banana</li>
<li>Paper boat</li>
<li>Cucumber</li>
<li>Rocket</li>
</ul>
<p>Ordered lists look pretty much as youd expect:</p>
<ol class="incremental" type="1">
<li>Goals</li>
<li>Motivations
<ol class="incremental" type="1">
<li>Intrinsic</li>
<li>Extrinsic</li>
</ol></li>
<li>Second-order effects</li>
</ol>
<p>Its nice to visualize trees. This is a regular unordered list with a
<code>tree</code> class:</p>
<ul class="tree">
<li>
<p style="margin: 0;">
<strong>/dev/nvme0n1p2</strong>
</p>
<ul class="incremental">
<li>usr
<ul class="incremental">
<li>local<br />
</li>
<li>share<br />
</li>
<li>libexec<br />
</li>
<li>include<br />
</li>
<li>sbin<br />
</li>
<li>src<br />
</li>
<li>lib64<br />
</li>
<li>lib<br />
</li>
<li>bin<br />
</li>
<li>games
<ul class="incremental">
<li>solitaire</li>
<li>snake</li>
<li>tic-tac-toe</li>
</ul></li>
<li>media<br />
</li>
</ul></li>
<li>media<br />
</li>
<li>run<br />
</li>
<li>tmp</li>
</ul>
</li>
</ul>
<h2 id="tables">Tables</h2>
<p>We can use regular tables that automatically adjust to the monospace
grid. Theyre responsive.</p>
<table>
<thead>
<tr>
<th class="width-min">
Name
</th>
<th class="width-auto">
Dimensions
</th>
<th class="width-min">
Position
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Boboli Obelisk
</td>
<td>
1.41m × 1.41m × 4.87m
</td>
<td>
43°4550.78”N 11°153.34”E
</td>
</tr>
<tr>
<td>
Pyramid of Khafre
</td>
<td>
215.25m × 215.25m × 136.4m
</td>
<td>
29°5834”N 31°0751”E
</td>
</tr>
</tbody>
</table>
<p>Note that only one column is allowed to grow.</p>
<h2 id="forms">Forms</h2>
<p>Here are some buttons:</p>
<nav>
<button>
Reset
</button>
<button>
Save
</button>
</nav>
<p>And inputs:</p>
<form class="grid">
<label>First name
<input type="text" placeholder="Placeholder..." /></label> <label>Last
name <input type="text" placeholder="Text goes here..." /></label>
<label>Age <input type="text" value="30" /></label>
</form>
<p>And radio buttons:</p>
<form class="grid">
<label><input name="radio" type="radio" /> Option #1</label>
<label><input name="radio" type="radio" /> Option #2</label>
<label><input name="radio" type="radio" /> Option #3</label>
</form>
<h2 id="grids">Grids</h2>
<p>Add the <code>grid</code> class to a container to divide up the
horizontal space evenly for the cells. Note that it maintains the
monospace, so the total width might not be 100%. Here are six grids with
increasing cell count:</p>
<div class="grid">
<input readonly value="1" />
</div>
<div class="grid">
<input readonly value="1" /><input readonly value="2" />
</div>
<div class="grid">
<input readonly value="1" /><input readonly value="2" /><input readonly value="3" />
</div>
<div class="grid">
<input readonly value="1" /><input readonly value="2" /><input readonly value="3" /><input readonly value="4" />
</div>
<div class="grid">
<input readonly value="1" /><input readonly value="2" /><input readonly value="3" /><input readonly value="4" /><input readonly value="5" />
</div>
<div class="grid">
<input readonly value="1" /><input readonly value="2" /><input readonly value="3" /><input readonly value="4" /><input readonly value="5" /><input readonly value="6" />
</div>
<p>If we want one cell to fill the remainder, we set
<code>flex-grow: 1;</code> for that particular cell.</p>
<div class="grid">
<input readonly value="1" /><input readonly value="2" /><input readonly value="3!" style="flex-grow: 1;" /><input readonly value="4" /><input readonly value="5" /><input readonly value="6" />
</div>
<h2 id="ascii-drawings">ASCII Drawings</h2>
<p>We can draw in <code>&lt;pre&gt;</code> tags using <a
href="https://en.wikipedia.org/wiki/Box-drawing_characters">box-drawing
characters</a>:</p>
<pre><code>╭─────────────────╮
│ MONOSPACE ROCKS │
╰─────────────────╯</code></pre>
<p>To have it stand out a bit more, we can wrap it in a
<code>&lt;figure&gt;</code> tag, and why not also add a
<code>&lt;figcaption&gt;</code>.</p>
<figure>
<pre>
┌───────┐ ┌───────┐ ┌───────┐
│Actor 1│ │Actor 2│ │Actor 3│
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
│ │ msg 1 │
│ │────────►│
│ │ │
│ msg 2 │ │
│────────►│ │
┌───┴───┐ ┌───┴───┐ ┌───┴───┐
│Actor 1│ │Actor 2│ │Actor 3│
└───────┘ └───────┘ └───────┘</pre>
<figcaption>
Example: Message passing.
</figcaption>
</figure>
<p>Lets go wild and draw a chart!</p>
<figure>
<pre>
Things I Have
│ ████ Usable
15 │
│ ░░░░ Broken
12 │ ░
│ ░
│ ░ ░
9 │ ░ ░
│ ░ ░
│ ░ ░ ░
6 │ █ ░ ░ ░
│ █ ░ ░ ░
│ █ ░ █ ░
3 │ █ █ █ ░
│ █ █ █ ░
│ █ █ █ ░
0 └───▀─────────▀─────────▀──────────▀─────────────
Socks Jeans Shirts USB Drives
</pre>
</figure>
<h2 id="media">Media</h2>
<p>Media objects are supported, like images and video:</p>
<figure>
<img src="castle.jpg" alt="A room in an old French castle (2024)" />
<figcaption aria-hidden="true">A room in an old French castle
(2024)</figcaption>
</figure>
<figure>
<video
src="https://upload.wikimedia.org/wikipedia/commons/e/e0/The_Center_of_the_Web_%281914%29.webm"
controls=""><a
href="https://upload.wikimedia.org/wikipedia/commons/e/e0/The_Center_of_the_Web_%281914%29.webm">The
Center of the Web (1914), Wikimedia</a></video>
<figcaption aria-hidden="true"><a
href="https://en.wikisource.org/wiki/Page:The_Center_of_the_Web_(1914).webm/11">The
Center of the Web (1914), Wikimedia</a></figcaption>
</figure>
<p>They extend to the width of the page, and add appropriate padding in
the bottom to maintain the monospace grid.</p>
<h2 id="discussion">Discussion</h2>
<p>Thats it for now. Ive very much enjoyed making this, pushing my CSS
chops and having a lot of fun with the design. If you like it or even
decide to use it, please <a href="https://x.com/owickstrom">let me
know</a>.</p>
<p>The full source code is here: <a
href="https://github.com/owickstrom/the-monospace-web">github.com/owickstrom/the-monospace-web</a></p>
<p>Finally, a massive shout-out to <a
href="https://x.com/usgraphics">U.S. Graphics Company</a> for all the
inspiration.</p>
<div class="debug-grid"></div>
<script src="src/index.js"></script>
</body>
</html>

View File

@@ -1,30 +0,0 @@
{
"name": "@owickstrom/the-monospace-web",
"version": "0.1.5",
"description": " A minimalist design exploration",
"main": "src/index.css",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/owickstrom/the-monospace-web.git"
},
"keywords": [
"css"
],
"author": "Oskar Wickström",
"license": "MIT",
"bugs": {
"url": "https://github.com/owickstrom/the-monospace-web/issues"
},
"homepage": "https://github.com/owickstrom/the-monospace-web#readme",
"files": [
"src/index.css",
"src/reset.css",
"src/index.js",
"LICENSE",
"README.md"
]
}

22
readme.txt Normal file
View File

@@ -0,0 +1,22 @@
▄▄▄· ▄▄▄· ▐ ▄ ·▄▄▄▄ ▄▄· • ▌ ▄ ·. ▐ ▄
▐█ ▄█▐█ ▀█ •█▌▐███▪ ██ ▪ ▐█ ▌▪·██ ▐███▪▪ •█▌▐█▪
██▀·▄█▀▀█ ▐█▐▐▌▐█· ▐█▌ ▄█▀▄ ██ ▄▄▐█ ▌▐▌▐█· ▄█▀▄ ▐█▐▐▌ ▄█▀▄
▐█▪·•▐█ ▪▐▌██▐█▌██. ██ ▐█▌.▐▌▐███▌██ ██▌▐█▌▐█▌.▐▌██▐█▌▐█▌.▐▌
.▀ ▀ ▀ ▀▀ █▪▀▀▀▀▀• ▀█▄▀▪·▀▀▀ ▀▀ █▪▀▀▀ ▀█▄▀▪▀▀ █▪ ▀█▄▀▪
This is a simple Pandoc based Static Site Generator
based on the Monospace Web project by Oskar Wickström.
1. Install pandoc
2. Drop Markdown files in the src directory
3. Execute the convert.sh script
4. Deploy the build directory to your static site host
Given that the site generator consits of a single bash script,
single html template and a css file, it's easy to customize.
All changes to the original project are unlicensed (C0).
The Monospace Web is licensed under the MIT license.
- ff 2025

16
src/blog/index.md Normal file
View File

@@ -0,0 +1,16 @@
---
title: Blog
subtitle: Random musings.
author: John Doe
lang: en
toc: false
---
## Highligted
None.
## Other
- [Lorem ipsum](lorem-ipsum.html)

20
src/blog/lorem-ipsum.md Normal file
View File

@@ -0,0 +1,20 @@
---
title: Lorem ipsum
subtitle: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
author: John Doe
lang: en
toc: false
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque quis sagittis diam. Nunc nec nisi venenatis, varius ipsum sed, interdum ex. Nullam lacinia ex at sapien maximus mattis. Donec felis leo, viverra sit amet augue at, convallis laoreet ex. Cras luctus lacinia mi vitae tincidunt. Duis pellentesque consequat sapien a gravida. Donec tincidunt diam turpis, nec imperdiet est faucibus at. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
Phasellus posuere, magna ut vulputate tincidunt, ipsum libero euismod mi, sit amet tincidunt risus sem in tellus. Quisque vestibulum sem sed orci rutrum euismod euismod sit amet velit. Phasellus pharetra dui eget metus pellentesque, vitae blandit leo tempus. Pellentesque commodo a magna vitae maximus. Aliquam hendrerit justo quis metus sollicitudin, ut tempus sapien tincidunt. Nam vitae mollis nunc, non iaculis neque. Cras ornare velit nec eros faucibus rhoncus. Praesent porttitor lectus lectus, nec luctus lectus sagittis sit amet. Fusce ultrices tincidunt commodo.
Fusce pulvinar sit amet neque sit amet ultrices. Sed facilisis non dui a consectetur. Cras vitae sem nisi. Nunc at tortor eu leo facilisis ultrices et eget lorem. Sed et odio nunc. Quisque mattis congue porta. Integer aliquet eleifend urna, eget molestie justo fermentum a. Phasellus ut efficitur tellus. Nulla posuere nisi vitae nisl dapibus mollis.
Integer condimentum libero ut ligula tempus dictum. Pellentesque dignissim mauris eget sapien consequat bibendum. Integer interdum risus nec quam egestas, id consequat arcu rutrum. Quisque viverra arcu ex, ut varius neque volutpat ut. Quisque et mattis nulla. Suspendisse congue orci id velit tincidunt rhoncus. Sed luctus odio nec augue vulputate, sit amet hendrerit lacus commodo. Duis auctor nisi orci, ut suscipit ipsum aliquet id. Etiam elementum orci sed augue iaculis, vitae condimentum justo pellentesque. Morbi eget tortor ullamcorper, eleifend tellus non, iaculis sapien. Aenean pellentesque laoreet ante, eu suscipit nisi imperdiet sit amet. Ut non dapibus tellus, posuere dignissim lacus. Aliquam aliquet malesuada porttitor. Maecenas tincidunt id ante at ultricies.
Fusce id libero vel diam faucibus congue. Cras sed molestie leo. Cras lobortis blandit orci dignissim pulvinar. Morbi ut fermentum erat. Suspendisse varius est lacinia mauris lacinia pellentesque. Duis non purus suscipit, dapibus ante ut, finibus metus. Nam dictum placerat suscipit. Suspendisse sapien ex, rutrum ut efficitur sed, suscipit ut justo. Donec venenatis libero vitae lobortis tristique. Aliquam ultricies mi consequat lectus rhoncus vestibulum. Curabitur consectetur in sem id tempor. Fusce eget ex mauris. Nunc porta porta arcu, vitae viverra ante gravida ut. Maecenas luctus condimentum nisi a cursus. Aenean blandit dolor vel velit rhoncus, vitae consectetur sem tincidunt. In ac tortor sit amet justo eleifend tincidunt eget quis justo.

View File

@@ -1,7 +1,12 @@
/* ==========================================================================
Base & Variables
========================================================================== */
@import url('https://fonts.cdnfonts.com/css/jetbrains-mono-2');
:root {
--font-family: "JetBrains Mono", monospace;
--font-family: "JetBrains Mono", "Cascadia Code", "Fira Code", "Source Code Pro",
Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--line-height: 1.20rem;
--border-thickness: 2px;
--text-color: #000;
@@ -25,20 +30,57 @@
:root {
--text-color: #fff;
--text-color-alt: #aaa;
--background-color: #000;
--background-color-alt: #111;
--background-color: #111;
--background-color-alt: #222;
}
}
[data-theme="dark"] {
--text-color: #fff;
--text-color-alt: #aaa;
--background-color: #111;
--background-color-alt: #222;
}
[data-theme="light"] {
--text-color: #000;
--text-color-alt: #666;
--background-color: #fff;
--background-color-alt: #eee;
}
* {
box-sizing: border-box;
}
*+* {
margin-top: var(--line-height);
}
.theme-toggle {
cursor: pointer;
display: inline-flex;
align-items: center;
vertical-align: middle;
width: 1.5ch;
height: 1.5ch;
margin: 0 0 0 0;
}
.theme-toggle svg {
width: 100%;
height: 100%;
fill: var(--text-color);
}
.theme-toggle:hover svg {
fill: var(--text-color-alt);
}
/* ==========================================================================
Layout & Grid
========================================================================== */
html {
display: flex;
width: 100%;
@@ -64,12 +106,85 @@ body {
:root {
font-size: 14px;
}
body {
padding: var(--line-height) 1ch;
}
}
h1, h2, h3, h4, h5, h6 {
@media print {
.page, .page-break { break-after: page; }
}
/* Override universal margins for grid */
.grid {
margin-top: 0;
}
.grid>* {
margin-top: 0;
}
/* Fix grid spacing */
.grid {
--grid-cells: 0;
display: flex;
gap: 1ch;
width: calc(round(down, 100%, (1ch * var(--grid-cells)) - (1ch * var(--grid-cells) - 1)));
margin-bottom: var(--line-height);
}
.grid>*,
.grid>input {
flex: 0 0 calc(round(down, (100% - (1ch * (var(--grid-cells) - 1))) / var(--grid-cells), 1ch));
}
.grid:has(> :last-child:nth-child(1)) {
--grid-cells: 1;
}
.grid:has(> :last-child:nth-child(2)) {
--grid-cells: 2;
}
.grid:has(> :last-child:nth-child(3)) {
--grid-cells: 3;
}
.grid:has(> :last-child:nth-child(4)) {
--grid-cells: 4;
}
.grid:has(> :last-child:nth-child(5)) {
--grid-cells: 5;
}
.grid:has(> :last-child:nth-child(6)) {
--grid-cells: 6;
}
.grid:has(> :last-child:nth-child(7)) {
--grid-cells: 7;
}
.grid:has(> :last-child:nth-child(8)) {
--grid-cells: 8;
}
.grid:has(> :last-child:nth-child(9)) {
--grid-cells: 9;
}
/* ==========================================================================
Typography
========================================================================== */
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: var(--font-weight-bold);
margin: calc(var(--line-height) * 2) 0 var(--line-height);
line-height: var(--line-height);
@@ -81,11 +196,28 @@ h1 {
margin-bottom: calc(var(--line-height) * 2);
text-transform: uppercase;
}
h2 {
font-size: 1rem;
text-transform: uppercase;
}
.header-anchor {
opacity: 0;
text-decoration: none;
margin-left: 1ch;
font-weight: normal;
}
h1:hover .header-anchor,
h2:hover .header-anchor,
h3:hover .header-anchor,
h4:hover .header-anchor,
h5:hover .header-anchor,
h6:hover .header-anchor {
opacity: 1;
}
hr {
position: relative;
display: block;
@@ -94,6 +226,7 @@ hr {
border: none;
color: var(--text-color);
}
hr:after {
display: block;
content: "";
@@ -105,25 +238,47 @@ hr:after {
height: 0;
}
a {
text-decoration-thickness: var(--border-thickness);
hr.thin {
position: relative;
height: var(--line-height);
margin: var(--line-height) 0;
}
a:link, a:visited {
color: var(--text-color);
hr.thin:after {
display: block;
content: "";
position: absolute;
top: calc(var(--line-height) / 2);
left: 0;
width: 100%;
border-top: var(--border-thickness) solid var(--text-color-alt);
height: 0;
}
p {
word-break: break-word;
word-wrap: break-word;
hyphens: auto;
margin-bottom: var(--line-height);
}
strong {
font-weight: var(--font-weight-bold);
}
em {
font-style: italic;
}
a {
text-decoration-thickness: var(--border-thickness);
}
a:link,
a:visited {
color: var(--text-color);
}
sub {
position: relative;
display: inline-block;
@@ -134,6 +289,12 @@ sub {
font-size: .75rem;
}
/* ==========================================================================
Components
========================================================================== */
/* Tables */
table {
position: relative;
top: calc(var(--line-height) / 2);
@@ -142,161 +303,30 @@ table {
margin: 0 0 calc(var(--line-height) * 2);
}
th, td {
th,
td {
border: var(--border-thickness) solid var(--text-color);
padding:
calc((var(--line-height) / 2))
calc(1ch - var(--border-thickness) / 2)
calc((var(--line-height) / 2) - (var(--border-thickness)))
;
calc((var(--line-height) / 2)) calc(1ch - var(--border-thickness) / 2) calc((var(--line-height) / 2) - (var(--border-thickness)));
line-height: var(--line-height);
vertical-align: top;
text-align: left;
}
table tbody tr:first-child>* {
padding-top: calc((var(--line-height) / 2) - var(--border-thickness));
}
th {
font-weight: 700;
}
.width-min {
width: 0%;
}
.width-auto {
width: 100%;
font-weight: var(--font-weight-bold);
}
.header {
margin-bottom: calc(var(--line-height) * 2);
}
.header h1 {
margin: 0;
}
.header tr td:last-child {
text-align: right;
}
p {
word-break: break-word;
word-wrap: break-word;
hyphens: auto;
}
img, video {
display: block;
width: 100%;
object-fit: contain;
overflow: hidden;
}
img {
font-style: italic;
color: var(--text-color-alt);
}
details {
/* Forms */
input,
button,
textarea {
border: var(--border-thickness) solid var(--text-color);
padding: calc(var(--line-height) - var(--border-thickness)) 1ch;
margin-bottom: var(--line-height);
}
summary {
font-weight: var(--font-weight-medium);
cursor: pointer;
}
details[open] summary {
margin-bottom: var(--line-height);
}
details ::marker {
display: inline-block;
content: '▶';
margin: 0;
}
details[open] ::marker {
content: '▼';
}
details :last-child {
margin-bottom: 0;
}
pre {
white-space: pre;
overflow-x: auto;
margin: var(--line-height) 0;
overflow-y: hidden;
}
figure pre {
margin: 0;
}
pre, code {
font-family: var(--font-family);
}
code {
font-weight: var(--font-weight-medium);
}
figure {
margin: calc(var(--line-height) * 2) 3ch;
overflow-x: auto;
overflow-y: hidden;
}
figcaption {
display: block;
font-style: italic;
margin-top: var(--line-height);
}
ul, ol {
padding: 0;
margin: 0 0 var(--line-height);
}
ul {
list-style-type: square;
padding: 0 0 0 2ch;
}
ol {
list-style-type: none;
counter-reset: item;
padding: 0;
}
ol ul,
ol ol,
ul ol,
ul ul {
padding: 0 0 0 3ch;
margin: 0;
}
ol li:before {
content: counters(item, ".") ". ";
counter-increment: item;
font-weight: var(--font-weight-medium);
}
li {
margin: 0;
padding: 0;
}
li::marker {
line-height: 0;
}
::-webkit-scrollbar {
height: var(--line-height);
}
input, button, textarea {
border: var(--border-thickness) solid var(--text-color);
padding:
calc(var(--line-height) / 2 - var(--border-thickness))
calc(1ch - var(--border-thickness));
padding: calc(var(--line-height) / 2 - var(--border-thickness)) calc(1ch - var(--border-thickness));
margin: 0;
font: inherit;
font-weight: inherit;
@@ -320,6 +350,7 @@ input[type=radio] {
height: var(--line-height);
cursor: pointer;
}
input[type=checkbox]:checked:before,
input[type=radio]:checked:before {
content: "";
@@ -327,12 +358,14 @@ input[type=radio]:checked:before {
height: calc(var(--line-height) / 2);
background: var(--text-color);
}
input[type=radio],
input[type=radio]:before {
border-radius: 100%;
}
button:focus, input:focus {
button:focus,
input:focus {
--border-thickness: 3px;
outline: none;
}
@@ -340,16 +373,19 @@ button:focus, input:focus {
input {
width: calc(round(down, 100%, 1ch));
}
::placeholder {
color: var(--text-color-alt);
opacity: 1;
}
::-ms-input-placeholder {
color: var(--text-color-alt);
}
button::-moz-focus-inner {
padding: 0;
border: 0
border: 0;
}
button {
@@ -361,83 +397,200 @@ button {
button:hover {
background: var(--background-color-alt);
}
button:active {
transform: translate(2px, 2px);
}
label {
display: block;
width: calc(round(down, 100%, 1ch));
height: auto;
line-height: var(--line-height);
/* Code Blocks */
pre {
white-space: pre;
overflow-x: auto;
margin: var(--line-height) 0;
overflow-y: hidden;
}
pre,
code {
font-family: var(--font-family);
}
code {
font-weight: var(--font-weight-medium);
margin: 0;
}
label input {
width: 100%;
code:not(pre code) {
background: var(--background-color-alt);
padding: 0 0.5ch;
border-radius: 2px;
font-size: 0.95em;
white-space: nowrap;
}
.tree, .tree ul {
position: relative;
padding-left: 0;
list-style-type: none;
pre code {
display: block;
padding: calc(var(--line-height) / 2) 2ch;
background: var(--background-color-alt);
border-radius: 2px;
overflow-x: auto;
line-height: var(--line-height);
}
.tree ul {
margin: 0;
}
.tree ul li {
position: relative;
padding-left: 1.5ch;
margin-left: 1.5ch;
border-left: var(--border-thickness) solid var(--text-color);
}
.tree ul li:before {
position: absolute;
display: block;
top: calc(var(--line-height) / 2);
left: 0;
content: "";
width: 1ch;
border-bottom: var(--border-thickness) solid var(--text-color);
}
.tree ul li:last-child {
border-left: none;
}
.tree ul li:last-child:after {
position: absolute;
display: block;
top: 0;
left: 0;
content: "";
pre code::-webkit-scrollbar {
height: calc(var(--line-height) / 2);
border-left: var(--border-thickness) solid var(--text-color);
}
.grid {
--grid-cells: 0;
display: flex;
gap: 1ch;
width: calc(round(down, 100%, (1ch * var(--grid-cells)) - (1ch * var(--grid-cells) - 1)));
pre code::-webkit-scrollbar-thumb {
background: var(--text-color-alt);
border-radius: 2px;
}
pre code::-webkit-scrollbar-track {
background: transparent;
}
/* Lists */
ul,
ol {
padding: 0;
margin: 0 0 var(--line-height);
}
ul {
list-style-type: square;
padding: 0 0 0 2ch;
}
ol {
list-style-type: none;
counter-reset: item;
padding: 0;
}
ol ul,
ol ol,
ul ol,
ul ul {
padding: 0 0 0 3ch;
margin: 0;
}
ol li:before {
content: counters(item, ".") ". ";
counter-increment: item;
font-weight: var(--font-weight-medium);
}
li {
margin: 0;
padding: 0;
}
li::marker {
line-height: 0;
}
/* Blockquotes */
blockquote {
margin: var(--line-height) 3ch;
padding-left: 2ch;
border-left: var(--border-thickness) solid var(--text-color);
color: var(--text-color-alt);
}
blockquote p {
margin: 0;
}
blockquote+blockquote {
margin-top: 0;
}
/* Details */
details {
border: var(--border-thickness) solid var(--text-color);
padding: calc(var(--line-height) - var(--border-thickness)) 1ch;
margin-bottom: var(--line-height);
}
.grid > *,
.grid > input {
flex: 0 0 calc(round(down, (100% - (1ch * (var(--grid-cells) - 1))) / var(--grid-cells), 1ch));
summary {
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.grid:has(> :last-child:nth-child(1)) { --grid-cells: 1; }
.grid:has(> :last-child:nth-child(2)) { --grid-cells: 2; }
.grid:has(> :last-child:nth-child(3)) { --grid-cells: 3; }
.grid:has(> :last-child:nth-child(4)) { --grid-cells: 4; }
.grid:has(> :last-child:nth-child(5)) { --grid-cells: 5; }
.grid:has(> :last-child:nth-child(6)) { --grid-cells: 6; }
.grid:has(> :last-child:nth-child(7)) { --grid-cells: 7; }
.grid:has(> :last-child:nth-child(8)) { --grid-cells: 8; }
.grid:has(> :last-child:nth-child(9)) { --grid-cells: 9; }
/* DEBUG UTILITIES */
details[open] summary {
margin-bottom: var(--line-height);
}
details ::marker {
display: inline-block;
content: '▶';
margin: 0;
}
details[open] ::marker {
content: '▼';
}
details :last-child {
margin-bottom: 0;
}
/* Media */
img,
video {
display: block;
width: 100%;
object-fit: contain;
overflow: hidden;
}
img {
font-style: italic;
color: var(--text-color-alt);
}
figure {
margin: calc(var(--line-height) * 2) 3ch;
overflow-x: auto;
overflow-y: hidden;
}
figcaption {
display: block;
font-style: italic;
margin-top: var(--line-height);
}
/* ==========================================================================
Utilities
========================================================================== */
.width-min {
width: 0%;
}
.width-auto {
width: 100%;
}
.header {
margin-bottom: calc(var(--line-height) * 2);
}
.header h1 {
margin: 0;
}
.header tr td:last-child {
text-align: right;
}
/* ==========================================================================
Debug
========================================================================== */
.debug .debug-grid {
--color: color-mix(in srgb, var(--text-color) 10%, var(--background-color) 90%);
@@ -461,3 +614,115 @@ label input {
.debug-toggle-label {
text-align: right;
}
/* ==========================================================================
Miscellaneous
========================================================================== */
/* Product Cards */
.product-card {
display: flex;
flex-direction: column;
}
.product-card > *:not(nav) {
flex: 1 0 auto;
}
.product-card nav {
display: flex;
gap: 1ch;
align-items: stretch;
margin-top: auto;
}
.product-card nav a {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: calc(var(--line-height) / 4) 0;
background: var(--background-color-alt);
text-decoration: none;
min-height: calc(var(--line-height) * 1.5);
margin-top: 0;
}
.product-card nav a:hover {
background: var(--text-color);
color: var(--background-color);
}
.product-card {
border: var(--border-thickness) solid var(--text-color);
padding: calc(var(--line-height) - var(--border-thickness)) 1ch;
width: 100%;
margin-top: var(--line-height);
}
.product-card img, .product-card video {
margin: 0 -1ch;
width: calc(100% + 2ch);
max-height: calc(var(--line-height) * 8);
object-fit: cover;
}
.product-card h3 {
margin-top: var(--line-height);
margin-bottom: 0;
font-weight: var(--font-weight-bold);
}
.product-card p {
color: var(--text-color-alt);
margin-bottom: var(--line-height);
}
.product-card table {
font-size: 0.9em;
margin-bottom: var(--line-height);
}
/* Override the universal margin for nav elements and their children */
nav,
nav + *,
nav > *,
nav > * + * {
margin-top: 0;
}
/* Also override for nav when it's inside a product-card */
.product-card nav {
margin-top: auto; /* This pushes the nav to the bottom while removing the default margin */
}
/* Ensure nav children (links/buttons) don't have top margin */
.product-card nav > * {
margin-top: 0;
}
/* Transformations */
.rotate-90 {
transform: rotate(90deg);
transform-origin: center center;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/* theme-aware images */
.theme-aware-image {
filter: none;
}
[data-theme="dark"] .theme-aware-image:not(.no-invert) {
filter: invert(1) hue-rotate(180deg);
}
/* Optional: Add specific adjustments for certain images if needed */
[data-theme="dark"] .theme-aware-image.adjust-contrast {
filter: invert(1) hue-rotate(180deg) contrast(0.8);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,67 +1,85 @@
---
title: The Monospace Web
subtitle: A minimalist design exploration
author: Oskar Wickström
author-url: "https://wickstrom.tech"
title: CSS Features Demo
subtitle: Showcasing all available styles
author: Demo Author
author-url: "https://example.com"
lang: en
toc-title: Contents
---
## Introduction
Monospace fonts are dear to many of us.
Some find them more readable, consistent, and beautiful, than their proportional alternatives.
Maybe we're just brainwashed from spending years in terminals?
Or are we hopelessly nostalgic?
I'm not sure.
But I like them, and that's why I started experimenting with all-monospace Web.
<br>
On this page, I use a monospace grid to align text and draw diagrams.
It's generated from a simple Markdown document (using Pandoc), and the CSS and a tiny bit of Javascript renders it on the grid.
The page is responsive, shrinking in character-sized steps.
Standard elements should _just work_, at least that's the goal.
It's semantic HTML, rendered as if we were back in the 70s.
# Pandoc Monospace Web
All right, but is this even a good idea?
It's a technical and creative challenge and I like the aestethic.
If you'd like to use it, feel free to fork or copy the bits you need, respecting the license.
I might update it over time with improvements and support for more standard elements.
This is a simple Pandoc based Static Site Generator
based on the Monospace Web project by Oskar Wickström.
## The Basics
1. Install pandoc
2. Drop Markdown files in the src directory
3. Execute the convert.sh script
4. Deploy the build directory to your static site host
This document uses a few extra classes here and there, but mostly it's just markup.
This, for instance, is a regular paragraph.
Given that the site generator consits of a single bash script,
single html template and a css file, it's easy to customize.
Look at this horizontal break:
All changes to the original project are unlicensed (C0).
The Monospace Web is licensed under the MIT license.
<hr class="thin">
This is a demo page showcasing all the available styles for the Pandoc Monospace Web.
All standard Pandoc markdown features are supported [^1].
See the footer for "blog" implementation idea.
- Github repository: [frainfreeze/the-monospace-web-pandoc](https://github.com/frainfreeze/the-monospace-web-pandoc)
- Original author GitHub: [owickstrom/the-monospace-web](https://github.com/owickstrom/the-monospace-web)
<hr class="thin">
## Typography
# H1 Heading (Uppercase)
## H2 Heading (Uppercase)
### H3 Heading
#### H4 Heading
##### H5 Heading
###### H6 Heading
Regular paragraph with **bold text**, *italic text*, and `inline code`. Here's a [link to somewhere](https://example.com).
Text with a sub<sub>script</sub>.
## Horizontal Rules
Default horizontal rule:
<hr>
Lovely. We can hide stuff in the `<details`> element:
Thin variant:
<details>
<summary>A short summary of the contents</summary>
<p>Hidden gems.</p>
</details>
<hr class="thin">
## Lists
This is a plain old bulleted list:
Unordered list:
* Banana
* Paper boat
* Cucumber
* Rocket
* Item one
* Item two
* Item three
* Nested item
* Another nested item
Ordered lists look pretty much as you'd expect:
Ordered list:
1. Goals
1. Motivations
1. Intrinsic
1. Extrinsic
1. Second-order effects
1. First item
2. Second item
1. Nested numbered
2. Another nested
3. Third item
It's nice to visualize trees.
This is a regular unordered list with a `tree` class:
Tree view:
<ul class="tree"><li><p style="margin: 0;"><strong>/dev/nvme0n1p2</strong></p>
@@ -86,6 +104,7 @@ This is a regular unordered list with a `tree` class:
</li></ul>
## Tables
We can use regular tables that automatically adjust to the monospace grid.
@@ -126,18 +145,17 @@ Here are some buttons:
And inputs:
<form class="grid">
<form>
<label>First name <input type="text" placeholder="Placeholder..." /></label>
<label>Last name <input type="text" placeholder="Text goes here..." /></label>
<label>Age <input type="text" value="30" /></label>
<label>Disabled Input <input type="text" disabled value="Can't edit" /></label>
</form>
And radio buttons:
### Radio & Checkboxes
<form class="grid">
<label><input name="radio" type="radio" /> Option #1</label>
<label><input name="radio" type="radio" /> Option #2</label>
<label><input name="radio" type="radio" /> Option #3</label>
<label><input type="checkbox" /> Checkbox Option</label>
<label><input type="radio" name="group1" /> Radio 1</label>
<label><input type="radio" name="group1" /> Radio 2</label>
</form>
## Grids
@@ -157,7 +175,42 @@ If we want one cell to fill the remainder, we set `flex-grow: 1;` for that parti
<div class="grid"><input readonly value="1" /><input readonly value="2" /><input readonly value="3!" style="flex-grow: 1;" /><input readonly value="4" /><input readonly value="5" /><input readonly value="6" /></div>
## ASCII Drawings
## Code Blocks
Inline code: `const example = "hello world";`
Fenced code block:
```javascript
function demo() {
return {
hello: "world",
number: 42
};
}
```
## Blockquotes
> This is a blockquote
> It can span multiple lines
> And can contain *formatted* **text**
> Nested blockquotes
>> Are also possible
>>> And can go deeper
## Details/Summary
<details>
<summary>Click to expand</summary>
<p>Hidden content goes here</p>
<p>Can contain any other elements</p>
</details>
## ASCII Art & Diagrams
We can draw in `<pre>` tags using [box-drawing characters](https://en.wikipedia.org/wiki/Box-drawing_characters):
@@ -211,22 +264,54 @@ Let's go wild and draw a chart!
Socks Jeans Shirts USB Drives
</pre></figure>
## Media
Media objects are supported, like images and video:
## Product Cards
![A room in an old French castle (2024)](castle.jpg)
Product cards are useful for displaying items with images, descriptions, and actions:
![[The Center of the Web (1914), Wikimedia](https://en.wikisource.org/wiki/Page:The_Center_of_the_Web_(1914).webm/11)](https://upload.wikimedia.org/wikipedia/commons/e/e0/The_Center_of_the_Web_%281914%29.webm)
<div class="product-card">
<img src="./files/images/example.jpg" alt="Product image">
<h3>Product Title</h3>
<p>This is a description of the product with all its amazing features.</p>
<table>
<tr>
<th>Price</th>
<td>$99.99</td>
</tr>
<tr>
<th>Rating</th>
<td>★★★★☆</td>
</tr>
</table>
<nav>
<a href="#">Details</a>
<a href="#">Buy Now</a>
</nav>
</div>
## Media with Captions
Media objects are supported, like images and video with captions:
![A sample image with caption (2024)](./files/img/castle.jpg)
They extend to the width of the page, and add appropriate padding in the bottom to maintain the monospace grid.
## Discussion
![Example image alt text]()
That's it for now.
I've very much enjoyed making this, pushing my CSS chops and having a lot of fun with the design.
If you like it or even decide to use it, please [let me know](https://x.com/owickstrom).
<video src="https://upload.wikimedia.org/wikipedia/commons/e/e0/The_Center_of_the_Web_%281914%29.webm" controls></video>
The full source code is here: [github.com/owickstrom/the-monospace-web](https://github.com/owickstrom/the-monospace-web)
### Theme-Aware Images
Finally, a massive shout-out to [U.S. Graphics Company](https://x.com/usgraphics) for all the inspiration.
Images can adapt to light and dark themes (convert theme using lightbulb icon in the footer or system settings):
<div class="grid">
<img height="150px" class="theme-aware-image" src="./files/img/Nicaragua1_1913.jpg" alt="This image inverts in dark mode">
<img height="150px" class="theme-aware-image no-invert" src="./files/img/Nicaragua1_1913.jpg" alt="This image stays the same in both modes">
<img height="150px" class="theme-aware-image adjust-contrast" src="./files/img/Nicaragua1_1913.jpg" alt="This image adjusts contrast in dark mode">
</div>
[^1]: Pandoc understands an extended and slightly revised version of John Grubers Markdown syntax. See [Pandocs Markdown](https://pandoc.org/chunkedhtml-demo/8-pandocs-markdown.html) for details.

114
template.html Normal file
View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$>
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc-monospace-v1.0.1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$
$if(date-meta)$
<meta name="dcterms.date" content="$date-meta$" />
$endif$
$if(keywords)$
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
$endif$
$if(description-meta)$
<meta name="description" content="$description-meta$" />
$endif$
<link rel="shortcut icon" href="/files/img/favicon.ico">
<title>$if(title-prefix)$$title-prefix$ $endif$$pagetitle$</title>
$for(css)$
<link rel="stylesheet" href="$css$" />
$endfor$
$for(header-includes)$
$header-includes$
$endfor$
$if(math)$
$math$
$endif$
</head>
<body>
$for(include-before)$
$include-before$
$endfor$
<table class="header">
<tr>
<td colspan="2" rowspan="2" class="width-auto">
<h1 class="title">$title$</h1>
<span class="subtitle">$subtitle$</span>
</td>
<th>Version</th>
<td class="width-min">$version$</td>
</tr>
<tr>
<th>Updated</th>
<td class="width-min"><time style="white-space: pre;">$date$</time></td>
</tr>
</table>
$if(abstract)$
<div class="abstract">
<div class="abstract-title">$abstract-title$</div>
$abstract$
</div>
$endif$
$if(toc)$
<nav id="$idprefix$TOC" role="doc-toc">
$if(toc-title)$
<h2 id="$idprefix$toc-title">$toc-title$</h2>
$endif$
$table-of-contents$
</nav>
$endif$
$body$
$for(include-after)$
$include-after$
$endfor$
<table>
<tr>
<td class="width-auto">
<a style="text-decoration: none;" href="">Home</a>
<a style="text-decoration: none;" href="/blog/index.html">Blog</a>
<a style="text-decoration: none;" href="">©Copyright $year$</a>
<span onclick="toggleTheme()" class="theme-toggle" title="Toggle theme">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sun" viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708"/>
</svg>
</span>
</td>
</tr>
</table>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check for saved theme preference, otherwise use system preference
const theme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
// Apply theme
document.documentElement.setAttribute('data-theme', theme);
// Add anchor links to headers
document.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
if (heading.id) {
const anchor = document.createElement('a');
anchor.href = '#' + heading.id;
anchor.className = 'header-anchor';
anchor.textContent = '#';
const headingText = heading.textContent || '';
anchor.setAttribute('aria-label', 'Link to header' + headingText);
heading.appendChild(anchor);
}
});
});
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
</script>
</body>
</html>