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
95 changes: 94 additions & 1 deletion assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,108 @@ main { padding-bottom: 6rem; }
.post__body h3 { margin-top: 2rem; font-size: 1.1rem; }
.post__body img { max-width: 100%; height: auto; }

/* ---- repo-card shortcodes: github, gitlab, huggingface ---- */
.post__body .repo-card { text-decoration: none; }
.repo-card {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.9rem 1rem;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--fg);
}
.repo-card:hover { border-color: var(--link); }
.repo-card:hover .repo-card__name { color: var(--link); }
.repo-card__icon { flex: none; margin-top: 0.15rem; color: var(--muted); font-size: 1.1rem; line-height: 1; }
.repo-card__main { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.repo-card__name { font-family: var(--mono); font-weight: 600; }
.repo-card__desc { color: var(--muted); font-size: 0.9rem; }
.repo-card__meta { margin-left: auto; flex: none; display: flex; gap: 0.75rem; font-family: var(--mono); font-size: 0.8rem; color: var(--muted); white-space: nowrap; }

/* figures (markdown images) */
.post-figure { margin: 2rem 0; }
.post-figure img { display: block; width: 100%; border: 1px solid var(--border); border-radius: 4px; }
.post-figure img { display: block; width: 100%; border-radius: 4px; }
.post-figure figcaption {
margin-top: 0.5rem;
font-family: var(--mono);
font-size: 0.75rem;
color: var(--muted);
text-align: center;
}
.post-figure__zoom { display: block; cursor: zoom-in; }

/* click-to-zoom lightbox: pure CSS via :target, no JS */
.lightbox {
display: none;
position: fixed;
inset: 0;
z-index: 100;
padding: 2rem;
align-items: center;
justify-content: center;
}
.lightbox:target { display: flex; }
.lightbox__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.85); cursor: zoom-out; }
.lightbox img { position: relative; max-width: 100%; max-height: 100%; border-radius: 4px; }

/* code shortcode: imported file/URL, source path shown above the block */
.code-import figcaption { margin-bottom: 0.4rem; font-family: var(--mono); font-size: 0.75rem; color: var(--muted); }
.code-import figcaption a:hover { color: var(--link); }

/* swatches shortcode */
.swatches { display: flex; flex-wrap: wrap; gap: 1rem; margin: 1.5rem 0; }
.swatches__item { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
.swatches__chip { display: block; width: 3rem; height: 3rem; border-radius: 6px; border: 1px solid var(--border); }
.swatches__label { font-family: var(--mono); font-size: 0.75rem; color: var(--muted); }

/* admonition shortcode */
.admonition {
margin: 1.5rem 0;
padding: 0.9rem 1.1rem;
border-left: 3px solid var(--admonition-color, var(--c-blue));
background: color-mix(in srgb, var(--admonition-color, var(--c-blue)) 10%, transparent);
border-radius: 4px;
}
.admonition--tip { --admonition-color: var(--c-green); }
.admonition--warning { --admonition-color: var(--c-orange); }
.admonition--danger { --admonition-color: var(--c-red); }
.admonition__title { margin: 0 0 0.4rem; font-weight: 600; color: var(--admonition-color, var(--c-blue)); }
.admonition__body > :first-child { margin-top: 0; }
.admonition__body > :last-child { margin-bottom: 0; }

/* details shortcode — native <details>/<summary>, themed */
.details { margin: 1.5rem 0; border: 1px solid var(--border); border-radius: 6px; padding: 0.75rem 1rem; }
.details summary { cursor: pointer; font-weight: 600; }
.details[open] summary { margin-bottom: 0.5rem; }
.details__body > :first-child { margin-top: 0; }
.details__body > :last-child { margin-bottom: 0; }

/* badge shortcode */
.badge {
display: inline-block;
padding: 0.15em 0.55em;
border: 1px solid var(--border);
border-radius: 999px;
font-family: var(--mono);
font-size: 0.75rem;
color: var(--muted);
}
.badge--red { border-color: var(--c-red); color: var(--c-red); }
.badge--orange { border-color: var(--c-orange); color: var(--c-orange); }
.badge--yellow { border-color: var(--c-yellow); color: var(--c-yellow); }
.badge--green { border-color: var(--c-green); color: var(--c-green); }
.badge--cyan { border-color: var(--c-cyan); color: var(--c-cyan); }
.badge--blue { border-color: var(--c-blue); color: var(--c-blue); }
.badge--purple { border-color: var(--c-purple); color: var(--c-purple); }
.badge--pink { border-color: var(--c-pink); color: var(--c-pink); }

/* footnotes (goldmark markup, no shortcode needed) */
.footnote-ref { text-decoration: none; }
.footnotes { margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--border); font-size: 0.9rem; color: var(--muted); }
.footnotes hr { display: none; }
.footnotes ol { padding-left: 1.25rem; }
.footnote-backref { text-decoration: none; margin-left: 0.3rem; }

/* math (MathML) */
.post__body math { font-size: 1.05em; }
Expand Down Expand Up @@ -476,6 +568,7 @@ main { padding-bottom: 6rem; }
font-family: var(--mono);
font-size: 0.85rem;
}
.post__body iframe { border-radius: 6px; }
.post__body code { font-family: var(--mono); font-size: 0.9em; }
.post__body :not(pre) > code {
padding: 0.1em 0.35em;
Expand Down
68 changes: 68 additions & 0 deletions exampleSite/content/posts/hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ pop of colour. Inline `code` looks like this.

> A blockquote sits quietly to one side.

## A repo

{{< github "gohugoio/hugo" >}}

{{< gitlab "gitlab-org/gitlab" >}}

{{< huggingface "bert-base-uncased" >}}

## Swatches

{{< swatches "#64748b" "#3b82f6" "#06b6d4" >}}

## A diagram

```mermaid
Expand All @@ -41,3 +53,59 @@ Inline, like $E = mc^2$, sits in the sentence. Display math gets its own line:
$$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$

That's it.

## Imported code

{{< code file="assets/js/console.js" lines="1-8" >}}

## A gist

{{< gist "octocat" "6cad326836d38bd3a7ae" >}}

## A video

{{< youtube dQw4w9WgXcQ >}}

## Contact

Questions? {{< mailto "hello@example.org" "Hello from the blog" "say hi" >}}.

Default text: {{< mailto "hello@example.org" >}}

## Admonitions

{{< admonition >}}A plain note, no title override.{{< /admonition >}}

{{< admonition type="tip" title="Pro tip" >}}Use `pnpm dev` for live reload while writing.{{< /admonition >}}

{{< admonition type="warning" title="Careful" >}}This regenerates the fingerprinted bundle.{{< /admonition >}}

{{< admonition type="danger" title="Don't" >}}Never commit `.env` files.{{< /admonition >}}

## Details

{{< details summary="Click to expand" >}}
Hidden content with **markdown** support, revealed via the native `<details>` element.
{{< /details >}}

## Badges

{{< badge "MIT" >}} {{< badge "v1.2.0" "blue" >}} {{< badge "beta" "orange" >}}

## Package cards

{{< npm "left-pad" >}}

{{< crates "serde" >}}

{{< pypi "requests" >}}

## A Bluesky post

{{< bluesky "https://bsky.app/profile/bsky.app/post/3mqcp5qjdfs26" >}}

## A footnote

Here's a claim that needs backing up.[^1]

[^1]: This is the footnote content, rendered by goldmark natively.
12 changes: 10 additions & 2 deletions layouts/_default/_markup/render-image.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{{/* Markdown images become <figure>s; alt text doubles as caption. */}}
{{/* Markdown images become <figure>s; alt text doubles as caption.
Click to zoom, a pure-CSS lightbox via :target — no JS. */}}
{{ $id := printf "img-%d" .Ordinal }}
<figure class="post-figure">
<img src="{{ .Destination | safeURL }}" alt="{{ .PlainText }}" loading="lazy" decoding="async"{{ with .Title }} title="{{ . }}"{{ end }} />
<a href="#{{ $id }}" class="post-figure__zoom" aria-label="Zoom image">
<img src="{{ .Destination | safeURL }}" alt="{{ .PlainText }}" loading="lazy" decoding="async"{{ with .Title }} title="{{ . }}"{{ end }} />
</a>
{{ with .PlainText }}<figcaption>{{ . }}</figcaption>{{ end }}
</figure>
<div class="lightbox" id="{{ $id }}">
<a href="{{ .Page.RelPermalink }}" class="lightbox__backdrop" aria-label="Close"></a>
<img src="{{ .Destination | safeURL }}" alt="{{ .PlainText }}" />
</div>
8 changes: 8 additions & 0 deletions layouts/shortcodes/admonition.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{- /* {{< admonition type="warning" title="Heads up" >}}markdown content{{< /admonition >}}
type: note (default) | tip | warning | danger. title is optional. */ -}}
{{- $type := .Get "type" | default "note" -}}
{{- $title := .Get "title" | default (title $type) -}}
<div class="admonition admonition--{{ $type }}">
<p class="admonition__title">{{ $title }}</p>
<div class="admonition__body">{{ .Inner | $.Page.RenderString }}</div>
</div>
7 changes: 7 additions & 0 deletions layouts/shortcodes/badge.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{- /* {{< badge "MIT" >}} or {{< badge "beta" color="orange" >}}
color: one of the pop palette names (red/orange/yellow/green/cyan/blue/purple/pink), defaults to accent. */ -}}
{{- $text := .Get 0 -}}
{{- if not $text }}{{ errorf "badge shortcode: text required, e.g. {{</* badge \"MIT\" */>}} (%s)" .Position }}{{ end -}}
{{- $color := .Get 1 -}}
{{- with .Get "color" }}{{ $color = . }}{{ end -}}
<span class="badge{{ with $color }} badge--{{ . }}{{ end }}">{{ $text }}</span>
47 changes: 47 additions & 0 deletions layouts/shortcodes/bluesky.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{{- /* {{< bluesky "https://bsky.app/profile/handle.bsky.social/post/postid" >}}
Fetches the post via Bluesky's public AppView API at build time and renders
it as a card — no embed script, no auth needed (unlike X/Twitter, which
removed unauthenticated read access, so there's no equivalent shortcode for
it without a developer API key). */ -}}
{{- $url := .Get 0 -}}
{{- if not $url }}{{ errorf "bluesky shortcode: post URL required, e.g. {{</* bluesky \"https://bsky.app/profile/handle/post/id\" */>}} (%s)" .Position }}{{ end -}}
{{- $parts := strings.Split $url "/" -}}
{{- if lt (len $parts) 7 }}{{ errorf "bluesky shortcode: expected https://bsky.app/profile/<handle>/post/<id> (%s)" .Position }}{{ end -}}
{{- $handle := index $parts 4 -}}
{{- $postID := index $parts 6 -}}

{{- $atURI := printf "at://%s/app.bsky.feed.post/%s" $handle $postID -}}
{{- $encoded := replace $atURI ":" "%3A" -}}
{{- $encoded = replace $encoded "/" "%2F" -}}

{{- $author := $handle -}}
{{- $text := "" -}}
{{- $likes := "" -}}
{{- $reposts := "" -}}
{{- $try := try (resources.GetRemote (printf "https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=%s" $encoded)) -}}
{{- with $try -}}
{{- if .Err }}{{ errorf "bluesky shortcode: failed to fetch %s: %s (%s)" $url .Err $.Position }}
{{- else -}}
{{- with .Value -}}
{{- $post := (transform.Unmarshal .Content).thread.post -}}
{{- $text = $post.record.text -}}
{{- $likes = int $post.likeCount -}}
{{- $reposts = int $post.repostCount -}}
{{- if $post.author.displayName }}{{ $author = printf "%s (@%s)" $post.author.displayName $post.author.handle }}{{ else }}{{ $author = $post.author.handle }}{{ end -}}
{{- end -}}
{{- end -}}
{{- end -}}

<a class="repo-card" href="{{ $url }}" target="_blank" rel="noopener noreferrer">
<span class="repo-card__icon" aria-hidden="true">🦋</span>
<span class="repo-card__main">
<span class="repo-card__name">{{ $author }}</span>
{{- with $text }}<span class="repo-card__desc">{{ . }}</span>{{ end }}
</span>
{{- if or $likes $reposts }}
<span class="repo-card__meta">
{{- with $reposts }}<span class="repo-card__stat">⟲ {{ . }}</span>{{ end }}
{{- with $likes }}<span class="repo-card__stat">♥ {{ . }}</span>{{ end }}
</span>
{{- end }}
</a>
40 changes: 40 additions & 0 deletions layouts/shortcodes/code.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{{- /* {{< code file="assets/js/console.js" >}} or {{< code url="https://raw.githubusercontent.com/…" >}}
Imports code from a local file or remote URL instead of pasting it inline.
Optional: lang="go" (else guessed from the extension), lines="12-34" (1-indexed, inclusive). */ -}}
{{- $file := .Get "file" -}}
{{- $url := .Get "url" -}}
{{- if not (or $file $url) }}{{ errorf "code shortcode: file or url required, e.g. {{</* code file=\"path/to/file.go\" */>}} (%s)" .Position }}{{ end -}}
{{- if and $file $url }}{{ errorf "code shortcode: pass file or url, not both (%s)" .Position }}{{ end -}}

{{- $lang := .Get "lang" -}}
{{- $label := "" -}}
{{- $content := "" -}}
{{- if $file -}}
{{- $content = os.ReadFile $file -}}
{{- $label = $file -}}
{{- if not $lang }}{{ $lang = path.Ext $file | strings.TrimPrefix "." }}{{ end -}}
{{- else -}}
{{- $label = $url -}}
{{- $try := try (resources.GetRemote $url) -}}
{{- with $try -}}
{{- if .Err }}{{ errorf "code shortcode: failed to fetch %s: %s (%s)" $url .Err $.Position }}
{{- else -}}
{{- $content = .Value.Content -}}
{{- if not $lang }}{{ $lang = path.Ext $url | strings.TrimPrefix "." }}{{ end -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{- with .Get "lines" -}}
{{- $parts := strings.Split . "-" -}}
{{- $start := int (index $parts 0) -}}
{{- $end := $start -}}
{{- if gt (len $parts) 1 }}{{ $end = int (index $parts 1) }}{{ end -}}
{{- $all := strings.Split $content "\n" -}}
{{- $content = delimit (after (sub $start 1) (first $end $all)) "\n" -}}
{{- end -}}

<figure class="code-import">
<figcaption>{{ if $url }}<a href="{{ $url }}" target="_blank" rel="noopener noreferrer">{{ $label }}</a>{{ else }}{{ $label }}{{ end }}</figcaption>
{{ transform.Highlight $content $lang }}
</figure>
38 changes: 38 additions & 0 deletions layouts/shortcodes/crates.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{{- /* {{< crates "serde" >}} — card linking to a crates.io package.
Auto-fetches description/version/downloads at build time; pass desc="…"
version="…" downloads="…" to override (or skip the network call). */ -}}
{{- $pkg := .Get 0 -}}
{{- with .Get "pkg" }}{{ $pkg = . }}{{ end -}}
{{- if not $pkg }}{{ errorf "crates shortcode: crate name required, e.g. {{</* crates \"serde\" */>}} (%s)" .Position }}{{ end -}}

{{- $desc := .Get "desc" -}}
{{- $version := .Get "version" -}}
{{- $downloads := .Get "downloads" -}}
{{- if not (and $desc $version $downloads) -}}
{{- $opts := dict "headers" (dict "User-Agent" "basic-hugo-theme (https://github.com/jjsnack/basic)") -}}
{{- $try := try (resources.GetRemote (printf "https://crates.io/api/v1/crates/%s" $pkg) $opts) -}}
{{- with $try -}}
{{- if not .Err -}}
{{- with .Value -}}
{{- $data := (transform.Unmarshal .Content).crate -}}
{{- if not $desc }}{{ $desc = $data.description }}{{ end -}}
{{- if not $version }}{{ $version = $data.max_stable_version }}{{ end -}}
{{- if not $downloads }}{{ $downloads = int $data.downloads }}{{ end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}

<a class="repo-card" href="https://crates.io/crates/{{ $pkg }}" target="_blank" rel="noopener noreferrer">
<span class="repo-card__icon" aria-hidden="true">📦</span>
<span class="repo-card__main">
<span class="repo-card__name">{{ $pkg }}</span>
{{- with $desc }}<span class="repo-card__desc">{{ . }}</span>{{ end }}
</span>
{{- if or $version $downloads }}
<span class="repo-card__meta">
{{- with $version }}<span class="repo-card__stat">v{{ . }}</span>{{ end }}
{{- with $downloads }}<span class="repo-card__stat">⬇ {{ . }}</span>{{ end }}
</span>
{{- end }}
</a>
7 changes: 7 additions & 0 deletions layouts/shortcodes/details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{- /* {{< details summary="Click to expand" >}}markdown content{{< /details >}}
Wraps the native <details>/<summary> disclosure widget — no JS. */ -}}
{{- $summary := .Get "summary" | default "Details" -}}
<details class="details">
<summary>{{ $summary }}</summary>
<div class="details__body">{{ .Inner | $.Page.RenderString }}</div>
</details>
21 changes: 21 additions & 0 deletions layouts/shortcodes/gist.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{- /* {{< gist "user" "gist-id" >}} — embeds a GitHub gist.
Hugo's built-in gist shortcode was removed in v0.156; this fetches the
gist via the GitHub API at build time and renders it with the site's own
Chroma highlighting instead of GitHub's unstyled <script> embed. */ -}}
{{- $user := .Get 0 -}}
{{- $id := .Get 1 -}}
{{- if or (not $user) (not $id) }}{{ errorf "gist shortcode: usage {{</* gist \"user\" \"gist-id\" */>}} (%s)" .Position }}{{ end -}}

{{- $try := try (resources.GetRemote (printf "https://api.github.com/gists/%s" $id)) -}}
{{- with $try -}}
{{- if .Err }}{{ errorf "gist shortcode: failed to fetch gist %s: %s (%s)" $id .Err $.Position }}
{{- else -}}
{{- $data := transform.Unmarshal .Value.Content -}}
{{- range $name, $f := $data.files }}
<figure class="code-import">
<figcaption><a href="https://gist.github.com/{{ $user }}/{{ $id }}" target="_blank" rel="noopener noreferrer">{{ $user }}/{{ $name }}</a></figcaption>
{{ transform.Highlight $f.content (lower $f.language) }}
</figure>
{{- end -}}
{{- end -}}
{{- end -}}
Loading