#!/usr/bin/env bash # md-to-html.sh — Convert Markdown docs to styled HTML with the CyberQueer theme. # # Usage: # bash docs/md-to-html.sh # convert all docs/md/*.md # bash docs/md-to-html.sh docs/md/foo.md # convert one file # # Output lands in docs/html/ with the same base name. # Requires: python3 with the 'markdown' package (python-markdown on Arch). # sudo pacman -S python-markdown set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MD_DIR="$SCRIPT_DIR/md" HTML_DIR="$SCRIPT_DIR/html" # ── Preflight ───────────────────────────────────────────────────────────────── if ! python3 -c "import markdown" 2>/dev/null; then echo "python-markdown not found. Installing..." sudo pacman -S --noconfirm python-markdown \ || { echo "Error: please install python-markdown manually."; exit 1; } fi mkdir -p "$HTML_DIR" # ── CyberQueer inline CSS ───────────────────────────────────────────────────── # Read live palette from colors.conf if available, otherwise use defaults. _hex() { local key="$1" default="$2" if [[ -f "$SCRIPT_DIR/../colors.conf" ]]; then local v v=$(grep -m1 "^${key}=" "$SCRIPT_DIR/../colors.conf" 2>/dev/null | cut -d= -f2 | tr -d '[:space:]' | sed 's/#.*//') [[ -n "$v" ]] && { printf '#%s' "${v^^}"; return; } fi printf '#%s' "$default" } C_BG=$(_hex COLOR_BG 1A1A1A) C_TEXT=$(_hex COLOR_TEXT D6ABAB) C_HI=$(_hex COLOR_HIGHLIGHT E40046) C_VIO=$(_hex COLOR_DARK 5018DD) C_RED=$(_hex COLOR_RED F50505) # Derived C_BG2="#242424" C_BG3="#2e2e2e" C_BORDER="${C_VIO}" CSS=" /* ── CyberQueer Theme ──────────────────────────────────────────────── */ :root { --bg: ${C_BG}; --bg2: ${C_BG2}; --bg3: ${C_BG3}; --text: ${C_TEXT}; --accent: ${C_HI}; --violet: ${C_VIO}; --danger: ${C_RED}; --border: ${C_BORDER}; --radius: 10px; --radius-sm: 5px; } /* ── Reset & Base ──────────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html { scroll-behavior: smooth; } body { background-color: var(--bg); color: var(--text); font-family: 'Agave Nerd Font Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Source Code Pro', monospace; font-size: 15px; line-height: 1.75; max-width: 960px; margin: 0 auto; padding: 2.5rem 2rem 5rem; } /* ── Typography ────────────────────────────────────────────────────── */ h1, h2, h3, h4, h5, h6 { font-family: 'Agave Nerd Font Mono', monospace; font-weight: 700; line-height: 1.25; margin-top: 2.2rem; margin-bottom: 0.6rem; } h1 { color: var(--accent); font-size: 2.2rem; border-bottom: 3px solid var(--accent); padding-bottom: 0.4rem; margin-top: 0; } h2 { color: var(--accent); font-size: 1.55rem; border-bottom: 2px solid var(--violet); padding-bottom: 0.3rem; } h3 { color: var(--violet); font-size: 1.2rem; } h4, h5, h6 { color: var(--text); font-size: 1rem; } p { margin: 0.9rem 0; } strong { color: var(--accent); font-weight: 700; } em { color: var(--violet); font-style: italic; } /* ── Links ─────────────────────────────────────────────────────────── */ a { color: var(--violet); text-decoration: none; border-bottom: 1px solid transparent; transition: color 0.15s, border-color 0.15s; } a:hover { color: var(--accent); border-bottom-color: var(--accent); } /* ── Code ──────────────────────────────────────────────────────────── */ code { font-family: inherit; background: var(--bg2); color: var(--violet); border: 1px solid var(--violet); border-radius: var(--radius-sm); padding: 0.1em 0.42em; font-size: 0.9em; } pre { background: var(--bg2); border: 2px solid var(--violet); border-radius: var(--radius); padding: 1.2rem 1.4rem; overflow-x: auto; margin: 1.2rem 0; position: relative; } pre code { background: transparent; border: none; padding: 0; color: var(--text); font-size: 0.875em; line-height: 1.6; } /* Syntax-like token colouring (no JS required — structural only) */ pre code .kw { color: var(--accent); } pre code .str { color: var(--violet); } pre code .cm { color: #666; font-style: italic; } /* ── Horizontal Rule ───────────────────────────────────────────────── */ hr { border: none; border-top: 2px solid var(--violet); margin: 2rem 0; opacity: 0.45; } /* ── Blockquote ────────────────────────────────────────────────────── */ blockquote { border-left: 4px solid var(--accent); background: var(--bg2); margin: 1.2rem 0; padding: 0.8rem 1.2rem; border-radius: 0 var(--radius) var(--radius) 0; color: var(--text); opacity: 0.9; } blockquote p { margin: 0; } /* ── Tables ────────────────────────────────────────────────────────── */ table { width: 100%; border-collapse: collapse; border: 2px solid var(--border); border-radius: var(--radius); overflow: hidden; margin: 1.2rem 0; font-size: 0.9em; } th { background: var(--violet); color: var(--bg); text-align: left; padding: 0.55rem 0.9rem; font-weight: 700; letter-spacing: 0.03em; } td { padding: 0.5rem 0.9rem; border-top: 1px solid var(--bg3); vertical-align: top; } tr:nth-child(even) td { background: var(--bg2); } tr:hover td { background: var(--bg3); } /* ── Lists ─────────────────────────────────────────────────────────── */ ul, ol { padding-left: 1.6rem; margin: 0.7rem 0; } li { margin: 0.25rem 0; } ul li::marker { color: var(--accent); } ol li::marker { color: var(--violet); font-weight: 700; } /* ── Nav sidebar (index page) ──────────────────────────────────────── */ nav ul { list-style: none; padding: 0; } nav li { margin: 0.3rem 0; } /* ── Page Header Bar ───────────────────────────────────────────────── */ .page-header { border-bottom: 3px solid var(--accent); padding-bottom: 0.6rem; margin-bottom: 2rem; display: flex; align-items: baseline; gap: 1rem; } .page-header .site-title { color: var(--accent); font-size: 0.85rem; opacity: 0.75; white-space: nowrap; } /* ── Back link ─────────────────────────────────────────────────────── */ .back-link { display: inline-block; color: var(--violet); font-size: 0.85rem; margin-bottom: 1.5rem; padding: 0.25rem 0.75rem; border: 1px solid var(--violet); border-radius: 30px; transition: background 0.15s, color 0.15s; } .back-link:hover { background: var(--violet); color: var(--bg); border-bottom-color: transparent; } /* ── Footer ────────────────────────────────────────────────────────── */ footer { margin-top: 4rem; padding-top: 1rem; border-top: 1px solid var(--bg3); color: var(--text); opacity: 0.4; font-size: 0.8rem; text-align: center; } /* ── Responsive ────────────────────────────────────────────────────── */ @media (max-width: 680px) { body { padding: 1.2rem 1rem 3rem; font-size: 14px; } h1 { font-size: 1.7rem; } h2 { font-size: 1.3rem; } table { display: block; overflow-x: auto; } } /* ── Selection ─────────────────────────────────────────────────────── */ ::selection { background: var(--accent); color: var(--bg); } /* ── Scrollbar ─────────────────────────────────────────────────────── */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: var(--bg2); } ::-webkit-scrollbar-thumb { background: var(--violet); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: var(--accent); } " # ── HTML template ───────────────────────────────────────────────────────────── render_html() { local title="$1" local body_html="$2" local is_index="$3" # "true" or "false" local back_link="" if [[ "$is_index" == "false" ]]; then back_link='← Index' fi cat <