# Content components

> Local, reusable components added by OINK

---

LLMS index: [llms.txt](/llms.txt)

---

OINK promotes the content components that proved reusable across PGSTY sites
into the theme. Each component has a stable authoring API, unique instance IDs,
local assets, and a defined safety boundary. Site-specific data widgets remain
outside the theme.

## Loading model

Interactive shortcodes mark the features used by a page. OINK then adds each
required stylesheet or runtime once, even if the page has several component
instances. A plain page does not download component code it never uses.

Relative asset and link parameters pass through Hugo's URL handling, so they
remain correct under a subpath `baseURL`. Component markup also has print,
dark-mode, mobile, keyboard, and reduced-motion behavior where applicable.

## Asciinema

Use `asciinema` for a terminal recording stored as a local `.cast` file:

```go-html-template
{{< asciinema
  file="oink/demo.cast"
  speed="1.5"
  markers="0:Start,1:Done"
>}}
```

<div id="td-asciinema-83eb67c27b51b12cf1f28e2794ffb439-0" class="td-asciinema td-max-width-on-larger-screens" data-td-asciinema
  data-timer-label="Playback time">
  <div data-td-asciinema-player></div>
  <script type="application/json" data-td-asciinema-config>{"options":{"autoPlay":false,"fit":"width","loop":false,"markers":[0,"Start",1,"Done"],"preload":false,"speed":1.5,"startAt":0},"src":"/oink/demo.cast","theme":"auto"}</script>
</div>


`file` is required and can also be the first positional argument. Supported
options are `theme`, `fit` (`width`, `height`, `both`, or `none`), `autoplay`,
`loop`, `preload`, `speed`, `startAt`, `poster`, `cols`, `rows`,
`idleTimeLimit`, `pauseOnMarkers`, and comma-separated `markers`.

Keep cast files local for offline use. A remote URL is accepted only when the
author explicitly supplies it.

## ECharts

The safe default accepts JSON or YAML and serializes the parsed value into an
`application/json` element:

```go-html-template
{{< echarts height="280px" >}}
xAxis: { type: category, data: [Source, Build, Publish] }
yAxis: { type: value }
series: [{ type: bar, data: [1, 2, 3] }]
{{< /echarts >}}
```

<!-- prettier-ignore-start -->


<div id="td-echarts-83eb67c27b51b12cf1f28e2794ffb439-1" class="td-echarts td-max-width-on-larger-screens"
  data-td-echarts>
  <div data-td-echarts-canvas style="height: 280px"></div>
  <script type="application/json" data-td-echarts-options>{"series":[{"data":[1,2,3],"type":"bar"}],"xAxis":{"data":["Source","Build","Publish"],"type":"category"},"yAxis":{"type":"value"}}</script>
</div>


<!-- prettier-ignore-end -->

`height` defaults to `400px` and must use a safe CSS length unit. `theme`
selects an ECharts theme, and `full=true` removes the normal content-width
constraint.

Legacy pages may contain a fenced JavaScript block and `$fn:name` references.
OINK rejects that executable form unless the shortcode sets `unsafe=true` or the
site temporarily enables:

```yaml
params:
  content:
    echarts_unsafe: true
```

Use the switch only during a reviewed migration. New charts should remain in the
structured JSON/YAML mode.

## Infographic

`infographic` renders the AntV Infographic DSL locally:

```go-html-template
{{< infographic >}}
infographic list-row-simple-horizontal-arrow
data
  items
    - label Source
      desc Markdown and configuration
    - label Build
      desc Hugo Extended
    - label Publish
      desc Static files
{{< /infographic >}}
```

<!-- prettier-ignore-start -->

<div id="td-infographic-83eb67c27b51b12cf1f28e2794ffb439-2" class="td-infographic td-max-width-on-larger-screens"
  data-td-infographic data-height="auto">
  <div id="td-infographic-83eb67c27b51b12cf1f28e2794ffb439-2-canvas" data-td-infographic-canvas></div>
  <script type="application/json" data-td-infographic-syntax>"infographic list-row-simple-horizontal-arrow\ndata\n  items\n    - label Source\n      desc Markdown and configuration\n    - label Build\n      desc Hugo Extended\n    - label Publish\n      desc Static files"</script>
</div>


<!-- prettier-ignore-end -->

`height` accepts `auto` or a safe CSS length; `full=true` removes the normal
content-width constraint. The DSL is serialized as data rather than inserted as
an executable script.

## Cards and carousel

`doc-card` and `nav-card` share one card implementation. `doc-cards` and
`nav-cards` create responsive groups of one to four columns. The aliases let an
existing site's content keep its most descriptive name without duplicating
markup or styles.

```go-html-template
{{< nav-cards cols="3" >}}
  {{< nav-card
    title="Architecture"
    link="/docs/oink/architecture/"
    icon="fa-solid fa-diagram-project"
    desc="Understand the build and runtime boundaries."
  >}}
  {{< nav-card
    title="Deployment"
    link="/docs/oink/deployment/"
    badge="Hugo-only"
  >}}Publish the static output.{{< /nav-card >}}
{{< /nav-cards >}}
```

<!-- prettier-ignore-start -->

<div id="td-nav-cards-83eb67c27b51b12cf1f28e2794ffb439-3" class="td-content-cards" style="--td-card-columns: 3">
<article id="td-nav-card-83eb67c27b51b12cf1f28e2794ffb439-nav-cards-3-0" class="td-content-card">
  <div class="td-content-card__body">
    <div class="td-content-card__head"><i class="fa-solid fa-diagram-project td-content-card__icon" aria-hidden="true"></i><a class="td-content-card__title" href="/docs/oink/architecture/">Architecture</a></div><p class="td-content-card__description">Understand the build and runtime boundaries.</p>
  </div>
</article>

<article id="td-nav-card-83eb67c27b51b12cf1f28e2794ffb439-nav-cards-3-1" class="td-content-card">
  <div class="td-content-card__body">
    <div class="td-content-card__head"><a class="td-content-card__title" href="/docs/oink/deployment/">Deployment</a><span class="td-content-card__badge">Hugo-only</span></div>
    <div class="td-content-card__links">Publish the static output.</div>
  </div>
</article>

</div>


<!-- prettier-ignore-end -->

A card accepts `title`, `link`, `image`, `alt`, `icon`, `desc`, `accent`, and
`badge`. Its body can contain Markdown links. Tokens such as `{version}` in
`desc` resolve from site parameters when a matching value exists.

Wrap document cards in `doc-carousel` to create an accessible horizontal
carousel:

```go-html-template
{{< doc-carousel label="OINK workflow" >}}
  {{< doc-card title="Write" >}}Create paired content.{{< /doc-card >}}
  {{< doc-card title="Build" >}}Run Hugo Extended.{{< /doc-card >}}
  {{< doc-card title="Verify" >}}Inspect the static site.{{< /doc-card >}}
{{< /doc-carousel >}}
```

<!-- prettier-ignore-start -->

<section id="td-carousel-83eb67c27b51b12cf1f28e2794ffb439-4" class="td-doc-carousel" data-td-carousel role="region"
  aria-roledescription="carousel" aria-label="OINK workflow">
  <button class="td-doc-carousel__button" type="button" data-td-carousel-action="previous"
    aria-controls="td-carousel-83eb67c27b51b12cf1f28e2794ffb439-4-track" aria-label="Previous card">‹</button>
  <div id="td-carousel-83eb67c27b51b12cf1f28e2794ffb439-4-track" class="td-doc-carousel__track" data-td-carousel-track tabindex="0">
<article id="td-doc-card-83eb67c27b51b12cf1f28e2794ffb439-doc-carousel-4-0" class="td-content-card">
  <div class="td-content-card__body">
    <div class="td-content-card__head"><strong class="td-content-card__title">Write</strong></div>
    <div class="td-content-card__links">Create paired content.</div>
  </div>
</article>

<article id="td-doc-card-83eb67c27b51b12cf1f28e2794ffb439-doc-carousel-4-1" class="td-content-card">
  <div class="td-content-card__body">
    <div class="td-content-card__head"><strong class="td-content-card__title">Build</strong></div>
    <div class="td-content-card__links">Run Hugo Extended.</div>
  </div>
</article>

<article id="td-doc-card-83eb67c27b51b12cf1f28e2794ffb439-doc-carousel-4-2" class="td-content-card">
  <div class="td-content-card__body">
    <div class="td-content-card__head"><strong class="td-content-card__title">Verify</strong></div>
    <div class="td-content-card__links">Inspect the static site.</div>
  </div>
</article>

</div>
  <button class="td-doc-carousel__button" type="button" data-td-carousel-action="next"
    aria-controls="td-carousel-83eb67c27b51b12cf1f28e2794ffb439-4-track" aria-label="Next card">›</button>
</section>


<!-- prettier-ignore-end -->

`label` supplies the carousel's accessible name. Arrow keys and visible
previous/next controls navigate the track; reduced-motion preferences disable
unnecessary animation.

## Details

`details` emits native `details` and `summary` elements:

```go-html-template
{{% details title="Why Hugo-only?" closed="false" %}}
Committed browser assets keep the consuming build reproducible.
{{% /details %}}
```

<!-- prettier-ignore-start -->

<details id="td-details-83eb67c27b51b12cf1f28e2794ffb439-5" class="td-details" open>
  <summary>Why Hugo-only?</summary>
  <div class="td-details__body">
Committed browser assets keep the consuming build reproducible.
</div>
</details>


<!-- prettier-ignore-end -->

`title` sets the summary. The block is closed by default; set `closed=false` to
render it open.

## Tabs

OINK keeps Docsy's `tabpane` and `tab` authoring model while preserving
`selected=true` and whitespace behavior used by imported sites:

```go-html-template
{{< tabpane text=true >}}
  {{< tab header="Local" selected=true >}}
  Build with the complete local theme.
  {{< /tab >}}
  {{< tab header="Cloudflare" >}}
  Run the same Hugo command from the source branch.
  {{< /tab >}}
{{< /tabpane >}}
```

<!-- prettier-ignore-start -->




<ul class="nav nav-tabs" id="tabs-6" role="tablist"><li class="nav-item"><button class="nav-link active" id="tabs-06-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-06-00" role="tab" data-td-tp-persist="local" aria-controls="tabs-06-00" aria-selected="true">Local</button></li><li class="nav-item"><button class="nav-link" id="tabs-06-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-06-01" role="tab" data-td-tp-persist="cloudflare" aria-controls="tabs-06-01" aria-selected="false">Cloudflare</button></li></ul>

<div class="tab-content" id="tabs-6-content"><div class="tab-body tab-pane fade show active" id="tabs-06-00" role="tabpanel" aria-labelledby="tabs-06-00-tab" tabindex="0">
Build with the complete local theme.
</div><div class="tab-body tab-pane fade" id="tabs-06-01" role="tabpanel" aria-labelledby="tabs-06-01-tab" tabindex="0">
Run the same Hugo command from the source branch.
</div>
</div>


<!-- prettier-ignore-end -->

Use `text=true` for Markdown content; otherwise tabs are syntax-highlighted
code. Tab panes also support language-aware persistence, disabled tabs, and
right-aligned entries. Generated tab and panel IDs have matching ARIA
relationships.

## Parameters

`param` prints a page parameter, falling back to the site parameter of the same
name:

```go-html-template
Current version: {{< param version >}}
```

Current version: `v0.16.0`

The shortcode fails the build when the named parameter does not exist. This is
intentional: a missing release or repository value should not silently produce
misleading documentation.

## Existing rich content

OINK also ships local runtimes for inherited content features:

- fenced `mermaid`, `math`, and `markmap` code blocks;
- `swaggerui` and `redoc` API documentation shortcodes;
- Docsy blocks, alerts, image, include, readfile, cards, and other established
  shortcodes.

See [Shortcodes](/docs/content/shortcodes/) and
[Diagrams and formulae](/docs/content/diagrams-and-formulae/) for the complete
authoring reference.

## Authoring rules

- Prefer structured data over executable content.
- Give images useful `alt` text and carousels a meaningful `label`.
- Do not enable autoplay unless the content genuinely requires it.
- Test several identical instances on one page when creating a new wrapper.
- Verify keyboard navigation, focus visibility, dark and light themes, mobile
  layout, print output, and reduced-motion behavior.
- Keep business-specific data components in the consuming site.
