diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 3cacc4f..ee58efb 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -45,7 +45,10 @@ concurrency: env: # The Accent CMS release the site is built with. Bump deliberately, after # a local build with the new version. - ACCENT_VERSION: v0.25.1 + ACCENT_VERSION: v0.26.0 + # The installer that fetches and verifies that release (crates.io crate + # `accent`, binary `accentup`). The cache key below changes with it. + ACCENTUP_VERSION: "0.1.3" SITE_URL: https://zoosky.github.io/driller jobs: @@ -55,29 +58,40 @@ jobs: steps: - uses: actions/checkout@v4 - # The official installer downloads the release archive, checks its - # SHA-256 against the signed checksums file, and verifies that - # signature with the release key. With the require-verify flag a - # missing checksum, signature, or gpg fails the install instead of - # warning, which is what an unattended build wants. + # accentup downloads the release archive and verifies it in-process: + # the release signing key is compiled in, the signature check on the + # checksums file and the SHA-256 check of the archive run inside the + # binary, and there is no way to skip either. The job depends neither + # on the runner shipping gpg nor on an unpinned install script. It is + # built from crates.io with --locked at a pinned version and cached by + # that version, so only the first run pays for the compile. + - name: Restore accentup + id: accentup + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/accentup + key: accentup-${{ env.ACCENTUP_VERSION }}-${{ runner.os }}-${{ runner.arch }} + + - name: Build accentup from crates.io + if: steps.accentup.outputs.cache-hit != 'true' + run: cargo install accent --locked --version "$ACCENTUP_VERSION" + - name: Install Accent CMS run: | - curl -fsSL https://raw.githubusercontent.com/AccentCMS/accent/main/install.sh \ - | ACCENT_VERSION="$ACCENT_VERSION" ACCENT_INSTALL_REQUIRE_VERIFY=1 sh + accentup install --version "$ACCENT_VERSION" --dir "$HOME/.local/bin" echo "$HOME/.local/bin" >> "$GITHUB_PATH" # The path component of --base-url makes every internal URL carry the - # /driller prefix the project site is served under. --strict-links - # fails the build on any internal link whose target is missing from - # the output, which covers links into the mounted repository files as - # well as the site's own pages. + # /driller prefix the project site is served under. The build fails on + # any internal link whose target is missing from the output, which + # covers links into the mounted repository files as well as the site's + # own pages. - name: Build run: | accent --version accent build --config site/config.yaml --clean \ --output "$GITHUB_WORKSPACE/dist" \ - --base-url "$SITE_URL" \ - --strict-links + --base-url "$SITE_URL" test -f "$GITHUB_WORKSPACE/dist/index.html" test -f "$GITHUB_WORKSPACE/dist/404.html" diff --git a/site/content/default.md b/site/content/default.md index 8da3d54..9a71dd5 100644 --- a/site/content/default.md +++ b/site/content/default.md @@ -17,8 +17,8 @@ cargo install driller driller run http://localhost:9000/api/users --stats ``` -[button primary href="/docs/getting-started"]Get started[/button] -[button href="https://github.com/zoosky/driller"]Source on GitHub[/button] +{% button variant="primary" href="/docs/getting-started" %}Get started{% /button %} +{% button href="https://github.com/zoosky/driller" %}Source on GitHub{% /button %} ---section: features--- diff --git a/site/themes/driller/assets/css/_dark-mode.scss b/site/themes/driller/assets/css/_dark-mode.scss index de1f57a..2ee6559 100644 --- a/site/themes/driller/assets/css/_dark-mode.scss +++ b/site/themes/driller/assets/css/_dark-mode.scss @@ -19,26 +19,26 @@ --color-nav-pill-hover-bg: #E5E5E5; --color-nav-pill-active-bg: rgba(5, 150, 105, 0.08); - // Alert light overrides - .alert-info { + // Callout light overrides + .callout-info { border-left-color: #059669; background: #ecfdf5; color: #065f46; } - .alert-warning { + .callout-warning { border-left-color: #d97706; background: #fffbeb; color: #92400e; } - .alert-success { + .callout-success { border-left-color: #059669; background: #ecfdf5; color: #065f46; } - .alert-danger { + .callout-danger { border-left-color: #dc2626; background: #fef2f2; color: #991b1b; diff --git a/site/themes/driller/assets/css/_landing.scss b/site/themes/driller/assets/css/_landing.scss index a10eefc..ed015a2 100644 --- a/site/themes/driller/assets/css/_landing.scss +++ b/site/themes/driller/assets/css/_landing.scss @@ -106,8 +106,7 @@ border: 1px solid var(--color-border); } -.landing-hero .btn, -.landing-hero .sc-button { +.landing-hero .btn { margin: 0.25rem; } @@ -145,7 +144,7 @@ } .landing-section a, -.landing-cta a:not(.btn):not(.sc-button), +.landing-cta a:not(.btn), .landing-features a { color: var(--color-link); } diff --git a/site/themes/driller/assets/css/_layout.scss b/site/themes/driller/assets/css/_layout.scss index c0c789f..9ede0fa 100644 --- a/site/themes/driller/assets/css/_layout.scss +++ b/site/themes/driller/assets/css/_layout.scss @@ -125,6 +125,12 @@ footer { .footer-sep { color: var(--color-border-strong); } + + // Accent CMS version and commit the site was built with. + .footer-build { + font-family: var(--font-mono); + font-size: 0.72rem; + } } // Responsive: stack header nav at narrow widths diff --git a/site/themes/driller/assets/css/_shortcodes.scss b/site/themes/driller/assets/css/_shortcodes.scss index ef1fedd..49b5228 100644 --- a/site/themes/driller/assets/css/_shortcodes.scss +++ b/site/themes/driller/assets/css/_shortcodes.scss @@ -1,7 +1,7 @@ -/// Alert and button shortcode styles. +/// Callout and button component styles. -// Alert base -.alert { +// Callout base +.callout { padding: 0.75rem 1.25rem; border-left: 4px solid var(--color-border-strong); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; @@ -13,34 +13,37 @@ } } -.alert-info { +.callout-title { + font-weight: 600; + margin-bottom: 0.25rem; +} + +.callout-info { border-left-color: #34d399; background: rgba(16, 185, 129, 0.08); color: #6ee7b7; } -.alert-warning { +.callout-warning { border-left-color: #fbbf24; background: rgba(251, 191, 36, 0.08); color: #fcd34d; } -.alert-success { +.callout-success { border-left-color: #10b981; background: rgba(16, 185, 129, 0.08); color: #6ee7b7; } -.alert-danger { +.callout-danger { border-left-color: #f87171; background: rgba(248, 113, 113, 0.08); color: #fca5a5; } -// Button base. The built-in [button] shortcode emits .sc-button classes; -// the .btn aliases stay for hand-written anchors in page content. -.btn, -.sc-button { +// Button base, emitted by the button component. +.btn { display: inline-block; padding: 0.5rem 1.25rem; border-radius: var(--radius-md); @@ -52,8 +55,7 @@ transition: background 0.15s, color 0.15s, border-color 0.15s; } -.btn-default, -.sc-button--default { +.btn-default { background: var(--color-bg-subtle); color: var(--color-text); border-color: var(--color-border-strong); @@ -64,8 +66,7 @@ } } -.btn-primary, -.sc-button--primary { +.btn-primary { background: var(--color-link); color: #0A0A0A; @@ -74,8 +75,7 @@ } } -.btn-secondary, -.sc-button--secondary { +.btn-secondary { background: var(--color-bg-muted); color: var(--color-text); border-color: var(--color-border-strong); diff --git a/site/themes/driller/shortcodes/accordion.html.jinja b/site/themes/driller/shortcodes/accordion.html.jinja new file mode 100644 index 0000000..3a3fd32 --- /dev/null +++ b/site/themes/driller/shortcodes/accordion.html.jinja @@ -0,0 +1,7 @@ +{#- The accordion. The manifest declares `body: markdown`, so the body arrives + between blank lines and renders as markdown inside the element. + `data-canonical` and `data-part` are the markup contract every conforming + theme's accordion carries. -#} +
{{ attrs.summary | default("Details") }} +{{ body }} +
diff --git a/site/themes/driller/shortcodes/accordion.yaml b/site/themes/driller/shortcodes/accordion.yaml new file mode 100644 index 0000000..97125f3 --- /dev/null +++ b/site/themes/driller/shortcodes/accordion.yaml @@ -0,0 +1,23 @@ +# Component manifest for the accordion, one of the canonical components. +# +# A section that stays collapsed behind its summary line until the reader +# opens it: a plain `
`/`` pair. Every conforming theme +# implements `accordion` with the `summary` and `open` attributes. +name: accordion +description: A collapsible section behind a summary line. +params: + - name: summary + type: string + required: false + description: The always-visible line that opens and closes the section. Defaults to "Details". + - name: open + type: boolean + required: false + description: Start expanded. +body: markdown +# `[accordion "Show me"]` wrote the summary as a bare word. `[details]` was this +# theme's accordion before the canonical names, and still migrates here. +migrate: + positional: [summary] + from: + - name: details diff --git a/site/themes/driller/shortcodes/alert.html.jinja b/site/themes/driller/shortcodes/alert.html.jinja deleted file mode 100644 index d9238ee..0000000 --- a/site/themes/driller/shortcodes/alert.html.jinja +++ /dev/null @@ -1,4 +0,0 @@ -{# Theme-specific alert shortcode that overrides the built-in (Feature f074). #} -{# Usage: [alert warning]Watch out![/alert] #} -{% set variant = args.positional[0] | default("info") %} - diff --git a/site/themes/driller/shortcodes/breadcrumb.html.jinja b/site/themes/driller/shortcodes/breadcrumb.html.jinja new file mode 100644 index 0000000..b270bce --- /dev/null +++ b/site/themes/driller/shortcodes/breadcrumb.html.jinja @@ -0,0 +1,8 @@ +{#- The breadcrumb. The body is the trail as the author wrote it, in Markdown. + The manifest declares `body: markdown`, so the body arrives between blank + lines: the `nav` raw-HTML block closes before it and the links render. The + class is the layout trail's, so both look alike. `data-canonical` is the + markup contract every conforming theme's breadcrumb carries. -#} + diff --git a/site/themes/driller/shortcodes/breadcrumb.yaml b/site/themes/driller/shortcodes/breadcrumb.yaml new file mode 100644 index 0000000..105df73 --- /dev/null +++ b/site/themes/driller/shortcodes/breadcrumb.yaml @@ -0,0 +1,10 @@ +# Component manifest for the breadcrumb, one of the canonical components. +# +# A trail of links an author writes, for a page whose position the site's own +# hierarchy does not describe. The layout's trail comes from +# `breadcrumbs.html.jinja`; this renders the same markup from content. Every +# conforming theme implements `breadcrumb`, which takes no attributes. +name: breadcrumb +description: A trail of links leading to the current page, written in Markdown. +params: [] +body: markdown diff --git a/site/themes/driller/shortcodes/button.html.jinja b/site/themes/driller/shortcodes/button.html.jinja new file mode 100644 index 0000000..d8dd455 --- /dev/null +++ b/site/themes/driller/shortcodes/button.html.jinja @@ -0,0 +1,12 @@ +{#- A call to action, styled as a button rather than a link. + + `href` goes through `url()` so the link carries the deployment prefix on a + sub-path install, and so the slash stays literal: an autoescaped + `{{ attrs.href }}` emits `/`, which is a valid link but a second + spelling for the base-path rewriter to know about. + + `data-canonical` is the markup contract every conforming theme's button + carries. +-#} +{%- set variant = attrs.variant | default("default") -%} +{{ body }} diff --git a/site/themes/driller/shortcodes/button.yaml b/site/themes/driller/shortcodes/button.yaml new file mode 100644 index 0000000..07afa78 --- /dev/null +++ b/site/themes/driller/shortcodes/button.yaml @@ -0,0 +1,26 @@ +# Component manifest for the button component. +name: button +description: A call-to-action link styled as a button. +params: + - name: variant + type: enum + values: [default, primary, secondary] + required: false + description: Which button style to use. Defaults to default. + - name: href + type: reference + required: true + description: Where the button links to. +# The body is the button's label. Spliced flush, it shares the line with the +# `` that opens it, so no raw HTML block starts and inline markdown in the +# label renders; blank lines around it would turn the label into a paragraph +# inside the link. +body: inline +# A button is written in a sentence as often as on a line of its own, and +# `{% button %}Label{% /button %}` on one line is an inline placement. An +# inline component is legitimate in both positions; a block one is reported +# as misplaced when written in a line. +inline: true +# `[button primary href="/signup"]` wrote the variant as a bare word. +migrate: + positional: [variant] diff --git a/site/themes/driller/shortcodes/callout.html.jinja b/site/themes/driller/shortcodes/callout.html.jinja new file mode 100644 index 0000000..647698c --- /dev/null +++ b/site/themes/driller/shortcodes/callout.html.jinja @@ -0,0 +1,27 @@ +{#- The callout, one of the canonical components. + + `role="complementary"` and `aria-label` are the accessibility contract: the + box is an aside about the surrounding prose, and a screen reader announces + it by its title. The title is repeated as visible text because the label is + not rendered. + + `data-canonical` and `data-part` are the markup contract: the landmarks + every conforming theme's callout carries, whatever its classes. + + The manifest declares `body: markdown`, so the body arrives between blank + lines: the raw-HTML block the opening element starts closes before the + body, and the body reaches the markdown parser whole. Keep the closing tag + in column 0 -- after a blank line, an indented `` is an indented + code block. +-#} +{%- set type = attrs.type | default("info") -%} +{%- set title = attrs.title | default(type | capitalize) -%} +{%- if attrs.collapsible -%} + +{%- else -%} + +{%- endif -%} diff --git a/site/themes/driller/shortcodes/callout.yaml b/site/themes/driller/shortcodes/callout.yaml new file mode 100644 index 0000000..257639d --- /dev/null +++ b/site/themes/driller/shortcodes/callout.yaml @@ -0,0 +1,40 @@ +# Component manifest for the callout, one of the canonical components. +# +# A callout sets a passage apart as a note, a warning, or another kind of +# notice. Every conforming theme implements `callout` with the `type`, `title` +# and `collapsible` attributes, so content written with it renders under any of +# them. The type name reaches the class attribute, so a theme styles +# `callout-prereq` without this file learning what "prereq" means. +name: callout +description: A titled box that sets a passage apart as a note, a warning, or another kind of notice. +params: + - name: type + type: string + required: false + description: >- + The kind of notice. Every conforming theme styles `info`, `success`, + `warning` and `danger`. Reaches the `callout-` class and, when no + title is given, supplies the title in capitalised form. Defaults to + `info`. + - name: title + type: string + required: false + description: >- + The heading and the accessible label. Defaults to the capitalised type. + - name: collapsible + type: boolean + required: false + description: >- + Render as a `
`/`` disclosure instead of a `
`, so + the box starts closed. +body: markdown +# `[callout warning]` wrote the type as a bare word. `[alert]` and `[infobox]` +# were this theme's callouts before the canonical names; content still written +# with them migrates here, and alert's `variant` becomes `type`. +migrate: + positional: [type] + from: + - name: alert + rename: + variant: type + - name: infobox diff --git a/site/themes/driller/shortcodes/card.html.jinja b/site/themes/driller/shortcodes/card.html.jinja new file mode 100644 index 0000000..38d77d6 --- /dev/null +++ b/site/themes/driller/shortcodes/card.html.jinja @@ -0,0 +1,13 @@ +{#- The card. + + The title link and the image go through `url()` for the reason the button's + link does: a sub-path install needs the deployment prefix. The manifest + declares `body: markdown`, so the body arrives between blank lines and + renders as markdown inside the `
`. + + `data-canonical` and `data-part` are the markup contract every conforming + theme's card carries. +-#} + diff --git a/site/themes/driller/shortcodes/card.yaml b/site/themes/driller/shortcodes/card.yaml new file mode 100644 index 0000000..30716a1 --- /dev/null +++ b/site/themes/driller/shortcodes/card.yaml @@ -0,0 +1,23 @@ +# Component manifest for the card, one of the canonical components. +# +# A self-contained block of content: an optional image, a title that can link +# somewhere, and a body. Every conforming theme implements `card` with the +# `title`, `href` and `image` attributes. +name: card +description: A block of content with an optional image, a title, and a link. +params: + - name: title + type: string + required: false + description: The card's heading. + - name: href + type: reference + required: false + description: Where the title links to. + - name: image + type: media + required: false + description: >- + An image shown above the title. It is decorative and carries no alt + text, so describe anything the reader needs in the body. +body: markdown diff --git a/site/themes/driller/shortcodes/image.html.jinja b/site/themes/driller/shortcodes/image.html.jinja new file mode 100644 index 0000000..1b6e135 --- /dev/null +++ b/site/themes/driller/shortcodes/image.html.jinja @@ -0,0 +1,5 @@ +{#- The image, as a figure so a caption stays attached to it. `src` goes + through `url()` so a sub-path install serves it with the prefix. + `data-canonical` and `data-part` are the markup contract every conforming + theme's image carries. -#} +
{{ attrs.alt }}{% if attrs.caption %}
{{ attrs.caption }}
{% endif %}
diff --git a/site/themes/driller/shortcodes/image.yaml b/site/themes/driller/shortcodes/image.yaml new file mode 100644 index 0000000..870ccd1 --- /dev/null +++ b/site/themes/driller/shortcodes/image.yaml @@ -0,0 +1,23 @@ +# Component manifest for the image, one of the canonical components. +# +# An image with alt text and an optional caption, written self-closing: +# `{% image src="/media/diagram.png" alt="The request flow" /%}`. Every +# conforming theme implements `image` with the `src`, `alt` and `caption` +# attributes. +name: image +description: An image with alt text and an optional caption. +params: + - name: src + type: media + required: true + description: The image file. + - name: alt + type: string + required: true + description: >- + What the image shows, for readers who cannot see it. Use an empty string + only for a purely decorative image. + - name: caption + type: string + required: false + description: Visible text shown under the image. diff --git a/site/themes/driller/shortcodes/tab.html.jinja b/site/themes/driller/shortcodes/tab.html.jinja new file mode 100644 index 0000000..ba6f437 --- /dev/null +++ b/site/themes/driller/shortcodes/tab.html.jinja @@ -0,0 +1 @@ +{{ body }} \ No newline at end of file diff --git a/site/themes/driller/shortcodes/tab.yaml b/site/themes/driller/shortcodes/tab.yaml new file mode 100644 index 0000000..6bbda13 --- /dev/null +++ b/site/themes/driller/shortcodes/tab.yaml @@ -0,0 +1,23 @@ +# Component manifest for one tab panel. +# +# A `tab` is normally read by its `tabs` parent rather than rendered on its own: +# the parent builds the strip and the panels from `children`. This template is +# what a `tab` written outside a `tabs` renders as, and it emits the body +# unchanged -- the same thing a stray `[tab]` did. +name: tab +description: One panel of a tabbed switcher. Belongs inside `tabs`. +params: + - name: label + type: string + required: false + description: The text on the tab button. + - name: id + type: string + required: false + description: >- + The panel's identifier, used for the anchor and for remembering the + selection. Defaults to the slugified label. +body: markdown +# `[tab "macOS"]` wrote the label as a bare word. +migrate: + positional: [label] diff --git a/site/themes/driller/shortcodes/tabs.html.jinja b/site/themes/driller/shortcodes/tabs.html.jinja new file mode 100644 index 0000000..a23da56 --- /dev/null +++ b/site/themes/driller/shortcodes/tabs.html.jinja @@ -0,0 +1,29 @@ +{#- The tab container. + + Both loops read `children`, not `body`: by the time this template runs, the + body is one string of rendered markup and a label cannot be recovered from + it. `children` carries each `{% tab %}` with its attributes still resolved + and its own body separate, which is what makes the strip constructible. + + The panels are emitted flush against each other -- no blank line between a + `
` and the next `` while the panels themselves stay one HTML block. + + `tabs.js` handles switching, group synchronisation, keyboard navigation and + persistence. The first tab is marked active here so the page is correct + before any script runs. + + `data-canonical` and `data-part` are the markup contract every conforming + theme's tabs carry: the tab list, each tab and each panel. +-#} +{%- set group = attrs.group | default("default") -%} +
+{%- for c in children if c.name == "tab" %}{% set id = c.attrs.id | default(c.attrs.label | slugify) %}{% endfor -%} +
+{%- for c in children if c.name == "tab" %}{% set id = c.attrs.id | default(c.attrs.label | slugify) %}
+{{ c.body }} +
{% endfor -%} +
diff --git a/site/themes/driller/shortcodes/tabs.yaml b/site/themes/driller/shortcodes/tabs.yaml new file mode 100644 index 0000000..a274463 --- /dev/null +++ b/site/themes/driller/shortcodes/tabs.yaml @@ -0,0 +1,16 @@ +# Component manifest for the tab container. +# +# The one shipped component that composes its children rather than wrapping its +# body: the tab strip is a button per `{% tab %}` child, carrying that child's +# label, and it has to be emitted before any panel. The template reads +# `children` for exactly that. +name: tabs +description: A tabbed switcher whose panels are its `tab` children. +params: + - name: group + type: string + required: false + description: >- + Tab groups with the same name switch together, and the selection is + remembered per group. Defaults to `default`. +body: markdown diff --git a/site/themes/driller/shortcodes/tag.html.jinja b/site/themes/driller/shortcodes/tag.html.jinja new file mode 100644 index 0000000..45141b9 --- /dev/null +++ b/site/themes/driller/shortcodes/tag.html.jinja @@ -0,0 +1,5 @@ +{#- The tag. Inline, so its body is a label spliced flush into the line. The + class is the one page tags already use, so both look alike. + `data-canonical` is the markup contract every conforming theme's tag + carries. -#} +{% if attrs.href %}{{ body }}{% else %}{{ body }}{% endif %} diff --git a/site/themes/driller/shortcodes/tag.yaml b/site/themes/driller/shortcodes/tag.yaml new file mode 100644 index 0000000..7cf08e5 --- /dev/null +++ b/site/themes/driller/shortcodes/tag.yaml @@ -0,0 +1,15 @@ +# Component manifest for the tag, one of the canonical components. +# +# A small label written inside a sentence, optionally a link. It is declared +# `inline: true`, so it stays inside the paragraph, heading or list item it is +# written in. Every conforming theme implements `tag` with the `href` +# attribute. +name: tag +description: A small inline label, optionally a link. +inline: true +params: + - name: href + type: reference + required: false + description: Where the label links to. +body: inline diff --git a/site/themes/driller/templates/base.html.jinja b/site/themes/driller/templates/base.html.jinja index 8f9a675..1a94721 100644 --- a/site/themes/driller/templates/base.html.jinja +++ b/site/themes/driller/templates/base.html.jinja @@ -19,6 +19,7 @@ + @@ -61,13 +62,13 @@ Issues - Built with Accent CMS + Built with Accent CMS {{ accent.version }} ({{ accent.git_hash }}) {% for js in theme.assets.js %} - + {% endfor %} -