---
title: Standard bearer
date: 2026-06-17T15:37:15Z
modified: 2026-07-02T11:49:20Z
permalink: "https://dgw.ltd/2026/06/17/standard-bearer/"
type: post
status: publish
excerpt: "ATmosphere auto-themes its Bluesky publication card from theme.json. It couldn't read mine, because my colours are `light-dark()` and there's no single value to pull out."
wpid: 1124
categories:
  - Syndication
featured_image: "https://dgw.ltd/wp-content/uploads/2026/06/standard-bearer-og.png"
featured_image_alt: "at://dgw.ltd/site.standard.publication Auto-themed (strikethrough text) hand-set"
---

I syndicate posts from WordPress to the AT Protocol with [ATmosphere](https://github.com/pfefferle/atmosphere), Automattic’s plugin for publishing to Bluesky and [standard.site](https://standard.site). The catch in my setup: I run my own PDS at `pds.dgw.ltd` (why?!) instead of posting through bsky.social, so every record lands in a repo I host. You can poke at the live data:

- [`site.standard.document` records](https://pdsls.dev/at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/site.standard.document) – one per post
- [the `site.standard.publication` record](https://pdsls.dev/at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/site.standard.publication) – the card I’m fixing here

The whole thing started with [Will’s writeup on implementing standard.site](https://wil.to/posts/implementing-standard-site/), which is the clearest tour of the record format I’ve found. This post is me retracing his steps from the WordPress side, and tripping over a couple of stones he’d already flagged.

A `site.standard.publication` record is what a standard.site viewer turns into the little “View publication” card under each syndicated post. Mine rendered in the viewer’s default palette: generic dark, a blue-ish button, a placeholder “D” where my icon should be.

The record is built from my WordPress settings, so in theory the card should already use my colours. It didn’t.

The plugin derives a `basicTheme` (four colours: background, foreground, accent, accentForeground) from `wp_get_global_styles()`. It reads three values:

- `color.background`
- `color.text`
- `elements.link.color.text` (the accent)

Then it resolves each. The resolver (`resolve_color()`) accepts exactly two shapes: a raw hex, or a preset reference of the form `var(--wp--preset--color--{slug})`. Anything else returns `null`. If any of the three is `null`, the whole field is dropped, because a partial record is invalid against the lexicon.

My global styles don’t hold hexes or preset references. They hold this:


```
color.background = var(--wp--custom--color--background)
color.text       = var(--wp--custom--color--text)
```

Two problems, both my own doing. First, those are `--wp--custom--` tokens, not `--wp--preset--`, so the resolver’s regex skips them. Second, each custom token expands to a `light-dark()`:


```css
--wp--custom--color--background: light-dark(var(--wp--preset--color--alt), var(--wp--preset--color--dark));
```

Even if the plugin followed one level of indirection, `light-dark(a, b)` has no single RGB answer. It’s two colours, one per OS preference.

So the plugin did the right thing and omitted the theme rather than ship a broken one.

Setting the colours by hand turned up a second issue. The plugin picks the button’s text colour automatically with a luminance > 0.5 heuristic: above, black; below, white. My brand pink `#FA008A` sits at ~0.22, so the heuristic picks white. White on `#FA008A` is **3.87:1** — under the 4.5:1 WCAG AA bar for normal text.

From Will’s code:


```bash
//Note: Bluesky enforces AA-level color contrast for buttons — if you don't meet the minimum, it fails silently.
```



| Button text on `#FA008A` | Ratio | AA 4.5:1 |
| --- | --- | --- |
| White `#ffffff` (auto-pick) | 3.87:1 | fail |
| Black `#000000` (my choice) | 5.43:1 | pass |

## The fix

There’s a filter, so I set the theme explicitly rather than fight the resolver:


```php
add_filter( 'atmosphere_transform_publication', function ( array $record ): array {
    $rgb = fn( $r, $g, $b ) => [ '$type' => 'site.standard.theme.color#rgb', 'r' => $r, 'g' => $g, 'b' => $b ];
    $record['basicTheme'] = [
        'background'       => $rgb( 13, 15, 5 ),    // #0d0f05
        'foreground'       => $rgb( 255, 255, 255 ), // #ffffff
        'accent'           => $rgb( 250, 0, 138 ),   // #FA008A
        'accentForeground' => $rgb( 0, 0, 0 ),       // #000000  5.43:1
    ];
    return $record;
} );
```

That filter lives in a must-use plugin, so it loads everywhere without me touching the theme or risking the change on a plugin update.

The icon is its own gotcha, and I walked straight into it the same way Will did:

> I had missed the option for a Publication icon the first time around, and if there’s one thing I like better than a little themed button, it’s an icon; hell yeah.

It comes from the WordPress Site Icon (`get_option('site_icon')`), uploaded to the PDS as a blob. No Site Icon, no icon on the card, and again it fails quietly. Set one in Settings → General (square, 512px) and the publication record re-syncs on save.

Anyway, this is an overly elaborate post so I can test if this all worked!

Also, lol, whilst testing this discovered Bluesky’s card preview url. 

[https://cardyb.bsky.app/v1/extract?url=https://dgw.ltd/2026/06/17/standard-bearer/](https://cardyb.bsky.app/v1/extract?url=https://dgw.ltd/2026/06/17/standard-bearer/)