diff options
| author | Kumar Damani <me@kumardamani.net> | 2026-07-24 04:29:08 +0000 |
|---|---|---|
| committer | Kumar Damani <me@kumardamani.net> | 2026-07-24 04:29:08 +0000 |
| commit | 3d615f7b4c19abf34f23ff09ea587a597f2de42a (patch) | |
| tree | 10a0e4c3e0fa2c9b0254bb1826ae89f67fbc7df0 /global | |
| parent | 62fcd12a0a1ea557334dec1fa0cfdce69d003bd8 (diff) | |
converted to typst
Diffstat (limited to 'global')
| -rw-r--r-- | global/common.typ | 228 | ||||
| -rw-r--r-- | global/extlink.svg | 25 | ||||
| -rw-r--r-- | global/favicon.ico | bin | 0 -> 15086 bytes | |||
| -rw-r--r-- | global/favicon.svg | 8 | ||||
| -rw-r--r-- | global/main.css | 371 |
5 files changed, 632 insertions, 0 deletions
diff --git a/global/common.typ b/global/common.typ new file mode 100644 index 0000000..14738a6 --- /dev/null +++ b/global/common.typ @@ -0,0 +1,228 @@ +// Site template for kumardamani.net, adapted from the example Typst blog. +// Structured for Typst's bundle export: pages are emitted as #document +// elements from the main.typ entrypoint and cross-page links use labels. + +#let _site-info = state("site-info", none) +#let _page-name = state("page-name", none) +#let _page-dir = state("page-dir", "") + +// Image paths collected from pages, emitted as bundle assets in main.typ. +#let _asset-paths = state("asset-paths", ()) + +#let srcurl = "https://gitlab.com/kdam0/kumardamani.net" + +// Deployment info, overwritten by CI per build: +// printf '{ "date": "%s", "link": "%s" }\n' \ +// "$(date '+%B %d, %Y' | sed 's/ 0/ /')" "$CI_JOB_URL" > ci.json +// date is pre-formatted for display, e.g. "July 24, 2026". +#let ci = json("/ci.json") + +// Page ids ("post/self-host") map to labels on the corresponding #document +// elements ("post-self-host"); typst resolves relative links between +// documents automatically. +#let label-for(id) = label(id.replace("/", "-")) + +#let extlink( + dest, + attrs: (:), + body +) = html.elem( + "a", + attrs: ( + class: "extlink", + rel: "external", + target: "_blank", + href: dest, + ) + attrs, + body +) + +#let navbar(pages, current) = { + html.elem("nav", html.elem("ul", { + for (pagename, prefs) in pages { + let (title,) = prefs + let hidden = prefs.at("hidden", default: false) + + if hidden and pagename != current { + continue + } + + let attrs = (:) + let class = () + + if pagename == current { + class += ("current",) + } + + if hidden { + class += ("hidden",) + } + + if class != () { + attrs += ("class": class.join(" ")) + } + + if pagename == current { + // the current page's nav item is not a link + html.elem("li", attrs: attrs, title) + } else if "url" in prefs { + html.elem("li", attrs: attrs, extlink(prefs.url, title)) + } else { + html.elem("li", attrs: attrs, link(label-for(pagename), title)) + } + } + })) +} + +// Image helpers. These emit plain <img> tags with literal src paths so that +// images stay as separate files instead of being embedded into the HTML, and +// register local sources as bundle assets (emitted in main.typ). +#let img(src, alt: "", width: none) = { + if not src.starts-with("http") { + context { + let dir = _page-dir.get() + _asset-paths.update(s => s + (dir + src,)) + } + } + + let attrs = (src: src, alt: alt) + if width != none { + attrs.insert("style", "width: " + width) + } + html.elem("img", attrs: attrs) +} + +#let fig(src, alt: "", caption: none, width: none) = html.elem("figure", { + img(src, alt: alt, width: width) + if caption != none { + html.elem("figcaption", caption) + } +}) + +#let _preferred-title(id) = { + let page = _site-info.get().at("pages").at(id) + + if "long-title" in page { + return page.at("long-title") + } else { + return page.at("title") + } +} + +// The preferred (long, if available) title of the current page. +#let preferred-title = context { + _preferred-title(_page-name.get()) +} + +// The publication date of the current page, if any. +#let page-date = context { + let info = _site-info.get().at("pages").at(_page-name.get()) + if "date" in info { + html.elem("p", attrs: (class: "date"), + html.elem("time", attrs: (datetime: info.date), info.date)) + } +} + +// Link to another page on the site; relative paths are resolved by typst. +#let pagelink(id, body) = link(label-for(id), body) + +// Link to another page, using its preferred title as the link text. +#let pageref(id) = context { + link(label-for(id), _preferred-title(id)) +} + +#let page( + pagename, + site-info, + banner: none, + stylesheets: (), + body +) = { + let site = site-info.at("title", default: "Untitled") + let pages = site-info.at("pages", default: (:)) + + if banner == none { + banner = html.elem("h1", attrs: (class: "banner"), site) + } + + // Prefix for links back to the site root from nested pages (posts). + let parts = pagename.split("/") + let depth = parts.len() - 1 + let base = "../" * depth + + html.html( + lang: "en", + { + html.head({ + html.meta(charset: "utf-8") + html.meta(name: "viewport", content: "width=device-width, initial-scale=1") + html.title(pages.at(pagename).title + " – " + site) + + html.elem("link", attrs: ("rel": "vcs-git", "title": "site source", "href": srcurl)) + html.link(rel: "icon", href: base + "favicon.svg", type: "image/svg+xml") + html.link(rel: "icon", href: base + "favicon.ico") + html.elem("link", attrs: (rel: "preload", href: base + "extlink.svg", "as": "image")) + html.link(rel: "stylesheet", href: base + "main.css") + + for ss in stylesheets { + html.link(rel: "stylesheet", href: base + ss) + } + }) + html.body({ + _site-info.update(site-info) + _page-name.update(pagename) + _page-dir.update(if depth > 0 { parts.slice(0, -1).join("/") + "/" } else { "" }) + + show html.elem: it => { + if "title" in it.attrs { + let attrs = it.attrs + let has-class = "class" in it.attrs + + if not has-class or regex("\btooltip\b") not in it.attrs.class { + if has-class { + attrs.class += " tooltip" + } else { + attrs.class = "tooltip" + } + + it = html.elem(it.tag, attrs: attrs, it.body) + } + } + + it + } + + show outline.entry: it => { + if it.prefix() != none { + html.span(class: "prefix", it.prefix()) + } + link(it.element.location(), it.body()) + } + + html.header({ + banner + navbar(pages, pagename) + }) + + html.main(body) + html.hr() + html.footer({ + html.span(class: "updated")[Updated: #ci.date.] + html.span(class: "logs")[#extlink(ci.link)[Logs] for nerds!] + }) + }) + } + ) +} + +// Scaffold for post pages: numbered headings (the heading counter is global +// to the bundle, so it is reset per document) with title and date on top. +#let post-page(pagename, site-info, body) = page(pagename, site-info, { + set heading(numbering: "1.") + counter(heading).update(0) + + title(preferred-title) + page-date + + body +}) diff --git a/global/extlink.svg b/global/extlink.svg new file mode 100644 index 0000000..3673c92 --- /dev/null +++ b/global/extlink.svg @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 24 24" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <g + id="layer1"> + <path + style="fill:#000000;stroke:none;stroke-width:10" + d="M 12,0 H 24 V 12 H 21 L 21.020819,5.1005019 11.121321,15 9,15 V 12.878679 L 18.899498,2.9791809 12,3 Z" + id="path1" /> + <path + style="fill:#000000;stroke:none;stroke-width:10" + d="M 0,6 V 24 H 18 V 12 h -3 v 9 H 3 V 9 h 9 V 6 Z" + id="path2" /> + </g> +</svg> diff --git a/global/favicon.ico b/global/favicon.ico Binary files differnew file mode 100644 index 0000000..49cf463 --- /dev/null +++ b/global/favicon.ico diff --git a/global/favicon.svg b/global/favicon.svg new file mode 100644 index 0000000..b8d5205 --- /dev/null +++ b/global/favicon.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"> + <!-- crown --> + <path d="M 22,10 L 42,10 L 47,38 L 17,38 Z" fill="#3a3f44"/> + <!-- band --> + <path d="M 17.6,31 L 46.4,31 L 47,38 L 17,38 Z" fill="#22262a"/> + <!-- brim: downward sloping skirt --> + <path d="M 17,36 L 47,36 L 60,48 Q 61.5,51.5 57,52 L 7,52 Q 2.5,51.5 4,48 Z" fill="#5c646b"/> +</svg> diff --git a/global/main.css b/global/main.css new file mode 100644 index 0000000..42951f4 --- /dev/null +++ b/global/main.css @@ -0,0 +1,371 @@ +/* SPDX-License-Identifier: MIT */ +/* SPDX-FileCopyrightText: 2024-2026 Katalin Rebhan <me@dblsaiko.net> */ +/* Adapted for kumardamani.net */ + +:root { + --button-border: color-mix(in oklab, ButtonFace, CanvasText 10%); + --extlink-icon: url('extlink.svg'); + + /* base font size is 16px, which is 12pt */ + --font-size-px-mul: calc(1rem / 16); + --font-size-pt-mul: calc(1rem / 12); + + /* fonts we use */ + --shell-ff: system-ui, ui-sans-serif, sans-serif; + --shell-fs: 1rem; + --shell-font: var(--shell-fs) var(--shell-ff); + + --body-ff: var(--shell-ff); + --body-fs: 1rem; + --body-font: var(--body-fs) var(--body-ff); + + --mono-ff: ui-monospace, monospace; + --mono-fs: calc(13 * var(--font-size-px-mul)); /* according to Safari */ + --mono-font: var(--mono-fs) var(--mono-ff); + + /* some extra lengths */ + --elem-spacing: 0.5rem; + --navbar-extra: 0.15rem; + --border-size: 0.15rem; + --text-skip: 0.2em; +} + +@media not (pointer: coarse) { + :root { + /* on displays with a non-coarse pointer (i.e. desktop computers), + shrink the shell font a bit because we can get away with it */ + --shell-fs: calc(10 * var(--font-size-pt-mul)); + } +} + +html { + max-width: 800px; + height: min(100%, 600px); + /* importantly, we never set font-size on the html element, so we can use + rem later to refer to the browser's default which might be modified by + the user. */ + font-family: var(--body-ff); + display: flex; + flex-direction: column; + color-scheme: light dark; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + text-size-adjust: none; + background: Canvas; +} + +body { + flex-grow: 1; + display: flex; + flex-direction: column; +} + +pre, xmp, plaintext, listing, tt, code, kbd, samp { + font: var(--mono-font); +} + +header { + display: grid; + grid-template-columns: auto 1fr; + align-items: baseline; + margin: 0 0 var(--elem-spacing) 0; + font: var(--shell-font); +} + +header > nav { + display: flex; + flex-wrap: nowrap; + text-wrap: nowrap; + flex-grow: 1; + overflow-x: auto; + padding: 0 calc(var(--elem-spacing) + .1rem); + border-bottom: var(--border-size) solid var(--button-border); +} + +header > nav > ul { + display: contents; +} + +header > nav a:link { + text-decoration: unset; + color: unset; +} + +header > nav a:visited { + color: unset; +} + +header > nav li { + display: inline; + padding: 0 calc(var(--elem-spacing) + var(--navbar-extra)); + margin: 0 calc(-1 * var(--navbar-extra)); +} + +header > nav li.current { + background-color: ButtonFace; + color: ButtonText; +} + +header > nav li.hidden { + font-style: italic; +} + +.banner { + font-size: 1.5em; + font-weight: unset; + margin: 0 var(--elem-spacing) 0 0; +} + +main { + flex-grow: 1; + text-wrap-style: pretty; +} + +main > :first-child { + margin-top: 0; +} + +main > :last-child { + margin-bottom: 0; +} + +hr { + /* Fixes <hr> collapsing to width 0 inside of a flex container */ + margin-left: 0; + margin-right: 0; + align-self: stretch; + + /* Same style as navbar */ + border: 0; + border-bottom: var(--border-size) solid var(--button-border); +} + +pre { + overflow: auto; +} + +blockquote { + display: block; + margin-inline-start: var(--elem-spacing); + margin-inline-end: 0; + border-inline-start: var(--border-size) solid; + padding-inline-start: var(--text-skip); +} + +/* Safari does not like font-size: 1rem + clip-path mixed units (FB22092644). + Hope we never have to combine both! */ +.biglist > li .description > a.extlink::after { + font-size: unset; +} + +a.extlink::after { + padding-right: 0.5em; + color: CanvasText; + background-color: currentcolor; + mask: var(--extlink-icon); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + clip-path: polygon( + calc(50% - 0.25em) calc(50% - 0.25em), + calc(50% + 0.25em) calc(50% - 0.25em), + calc(50% + 0.25em) calc(50% + 0.25em), + calc(50% - 0.25em) calc(50% + 0.25em) + ); + content: ""; + vertical-align: super; + line-height: 0; + font-size: 1rem; +} + +.biglist { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1em; + padding: 0; +} + +.biglist > li { + display: block; +} + +.biglist > li > h2 { + float: left; + margin: 0; + font-size: unset; +} + +.biglist > li > h2::after { + color: GrayText; + content: "—"; + font-weight: initial; + margin: 0 var(--text-skip); +} + +.biglist > li .description { + display: inline; + margin: 0; +} + +.biglist > li .source { + float: right; + margin: 0; +} + +.biglist > li .source::before { + color: GrayText; + content: "—"; + margin: 0 var(--text-skip); +} + +article { + hyphens: auto; +} + +article > p, article > blockquote { + text-align: justify; +} + +article > :first-child { + margin-top: 0; +} + +article > :last-child { + margin-bottom: 0; +} + +article > nav li { + display: contents; +} + +article > nav ol { + display: grid; + column-gap: var(--text-skip); + grid-template-columns: 0 1fr; +} + +article > nav > ol { + overflow: auto; +} + +article > nav ol { + grid-column: 2; +} + +article > nav .prefix { + user-select: none; + grid-column: 1; + justify-self: end; +} + +article > nav a { + grid-column: 2; + justify-self: start; +} + +footer { + display: grid; + /* equal flexible side tracks keep the middle track truly centered */ + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + font: var(--shell-font); +} + +footer > .updated { + grid-column: 2; + text-align: center; +} + +footer > .logs { + grid-column: 3; + justify-self: end; +} + +footer > ul { + display: contents; +} + +footer > ul > li { + display: inline; + font: var(--shell-font); +} + +footer > ul > li:not(:last-child)::after { + color: GrayText; + content: "—"; + font-weight: initial; + margin: 0 var(--text-skip); + padding: 0; +} + +.motb { + font-style: italic; + color: GrayText; + white-space: pre-wrap; +} + +.tooltip { + text-decoration: underline; + text-decoration-style: dotted; + cursor: help; +} + +table { + border-collapse: collapse; +} + +th, td { + padding: 0; + border: 0 solid transparent; + background-clip: padding-box; +} + +tr > td + td { + border-left-width: 1.5em; +} + +tr + tr > td { + border-top-width: 0.2em; +} + +/* --- additions for kumardamani.net --- */ + +/* publication date under post titles */ +.date { + color: GrayText; + font: var(--shell-font); + margin-top: 0; +} + +figure { + margin: 1em 0; + text-align: center; +} + +figcaption { + color: GrayText; + margin-top: 0.3em; +} + +img { + max-width: 100%; + height: auto; +} + +p > img:only-child { + display: block; + margin-left: auto; + margin-right: auto; +} + +pre { + padding: 0.5em; + border: 1px solid var(--button-border); +} + +details { + margin: 1em 0; +} + +summary { + cursor: pointer; +} |
