# Site components

Shared pieces for the Pathwright site: query result cards and rows, bylines, and
the date formatter. Each result component reads its own Source's metadata. The
`$author` and `$edition` bindings follow the page being shown; they are exported
by `plugins/pathwright.plugin.ts`.

---

<!-- componentName: card-grid -->

# Card grid

Lays out query result cards in a responsive grid.

```html
<div class="card-grid"><slot /></div>
```

---

<!-- componentName: article-card -->

# Article card

A blog post or update with its artwork, series, title, summary and date.

```html
<a class="card article-card" href="{{ href }}">
  <span class="card-media"><img src="{{ image }}" alt="" loading="lazy" /></span>
  <span class="card-body">
    <span class="card-eyebrow">{{ series }}</span>
    <span class="card-title">{{ title }}</span>
    <span class="card-text">{{ description }}</span>
    <time class="card-date" datetime="{{ date }}">{{ date }}</time>
  </span>
</a>
```

---

<!-- componentName: edition-card -->

# Edition card

A Collider edition with its cover image and date.

```html
<a class="card edition-card" href="{{ href }}">
  <span class="card-media"><img src="{{ image }}" alt="" loading="lazy" /></span>
  <span class="card-body">
    <time class="card-date" datetime="{{ date }}">{{ date }}</time>
    <span class="card-title">{{ title }}</span>
  </span>
</a>
```

---

<!-- componentName: change-row -->

# Change row

A changelog entry in the timeline: date, title, summary, and a badge while it is a
draft.

```html
<a class="change-row" href="{{ href }}">
  <time class="change-date" datetime="{{ date }}">{{ date }}</time>
  <span class="change-body">
    <span class="change-title">{{ title }}<span class="status-badge" data-status="{{ status }}">Draft</span></span>
    <span class="change-text">{{ description }}</span>
  </span>
</a>
```

---

<!-- componentName: person-chip -->

# Person chip

An author's portrait and name, linking to their page.

```html
<a class="person-chip" href="{{ href }}"><img src="{{ image }}" alt="" width="24" height="24" /><span>{{ title }}</span></a>
```

---

<!-- componentName: person-card -->

# Person card

A team member with their portrait and focus.

```html
<a class="card person-card" href="{{ href }}">
  <img src="{{ image }}" alt="" width="56" height="56" />
  <span class="card-body"><span class="card-title">{{ title }}</span><span class="card-text">{{ description }}</span></span>
</a>
```

---

<!-- componentName: edition-chip -->

# Edition chip

The Collider edition a link appeared in, read through the `edition` binding.

```html
<x-source-read name="edition">
  <a class="edition-chip" href="{{ $edition.href }}">From Collider · <time datetime="{{ $edition.date }}">{{ $edition.date }}</time></a>
  <slot name="fallback"></slot>
</x-source-read>
```

---

<!-- componentName: byline -->

# Byline

The author of the page being shown, read through the `author` binding.

```html
<x-source-read name="author">
  <a class="person-chip" href="{{ $author.href }}"><img src="{{ $author.image }}" alt="" width="26" height="26" /><span>{{ $author.title }}</span></a>
  <slot name="fallback"></slot>
</x-source-read>
```

---

<!-- componentName: page-meta -->

# Page meta

The series, date and author above a post's title.

```html
<div class="page-meta"><span class="page-meta-series">{{ source.series }}</span><time datetime="{{ source.date }}">{{ source.date }}</time><byline /></div>
```

---

<!-- componentName: change-meta -->

# Change meta

The date and draft status above a changelog entry.

```html
<p class="page-meta"><a href="/changelog">Changelog</a><time datetime="{{ source.date }}">{{ source.date }}</time><span class="status-badge" data-status="{{ source.status }}">Draft</span></p>
```

---

<!-- componentName: embed-bar -->

# Embed bar

The title bar the Pathwright app shows above the changelog in its side panel.
It appears only inside a frame (`html[data-pmx-embedded]`), where the site's
header and footer are hidden. Close asks the app to close the panel with the
`{ action: "close" }` message it listens for. Inside a frame, links that leave
the changelog open in a new tab instead of replacing the panel.

```html
<div class="embed-bar">
  <a class="embed-bar-title" href="/changelog"><img src="/site/assets/pathwright.svg" alt="" width="24" height="24" /><span>Changelog</span></a>
  <button class="embed-bar-close" type="button" aria-label="Close changelog">
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
  </button>
</div>
```

```js
const embedded = () => document.documentElement.hasAttribute("data-pmx-embedded")
const inChangelog = (url) => url.origin === location.origin && /^\/changelog(?:\/|$)/.test(url.pathname)

on(".embed-bar-close", "click", () => window.parent.postMessage({ action: "close" }, "*"))

on(window, "click", (event) => {
  if (!embedded() || event.defaultPrevented || event.button !== 0) return
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
  const link = event.target instanceof Element ? event.target.closest("a[href]") : null
  if (!link || link.target || link.hasAttribute("download")) return
  const url = new URL(link.href, location.href)
  if (!/^https?:$/.test(url.protocol) || inChangelog(url)) return
  event.preventDefault()
  event.stopPropagation()
  window.open(url.href, "_blank", "noopener")
}, { capture: true })
```

---

<!-- componentName: site-dates -->

# Site dates

Formats every `time[datetime]` holding an ISO date as a readable date, such as
"Jun 16, 2026", including content that arrives after navigation.

```html
<span hidden></span>
```

```js
const format = new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" })
function run() {
  for (const time of document.querySelectorAll("time[datetime]")) {
    const value = time.getAttribute("datetime")
    if (!/^\d{4}-\d{2}-\d{2}/.test(value ?? "") || time.dataset.formatted === value) continue
    const date = new Date(`${value.slice(0, 10)}T00:00:00Z`)
    if (Number.isNaN(date.getTime())) continue
    time.textContent = format.format(date)
    time.dataset.formatted = value
  }
}
run()
const observer = new MutationObserver(run)
observer.observe(document.body, { childList: true, subtree: true })
cleanup(() => observer.disconnect())
```

---

<!-- componentName: person-avatar -->

# Person avatar

The portrait at the top of a person's page.

```html
<img class="person-avatar" src="{{ source.image }}" alt="" width="96" height="96" />
```
