{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiaqmodahzbfxjb6z2g3nry2nzwbehfawf7ahgav6lll5lkcr3eabq",
    "uri": "at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/app.bsky.feed.post/3moineunfj2vy"
  },
  "content": {
    "$type": "org.wordpress.html",
    "html": "<p class=\"wp-block-paragraph\">I syndicate posts from WordPress to the AT Protocol with <a href=\"https://github.com/pfefferle/atmosphere\">ATmosphere</a>, Automattic&#8217;s plugin for publishing to Bluesky and <a href=\"https://standard.site\">standard.site</a>. The catch in my setup: I run my own PDS at <code>pds.dgw.ltd</code> (why?!) instead of posting through bsky.social, so every record lands in a repo I host. You can poke at the live data:</p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https://pdsls.dev/at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/site.standard.document\"><code>site.standard.document</code> records</a> &#8211; one per post</li>\n\n\n\n<li><a href=\"https://pdsls.dev/at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/site.standard.publication\">the <code>site.standard.publication</code> record</a> &#8211; the card I&#8217;m fixing here</li>\n</ul>\n\n\n\n<p class=\"wp-block-paragraph\">The whole thing started with <a href=\"https://wil.to/posts/implementing-standard-site/\">Will&#8217;s writeup on implementing standard.site</a>, which is the clearest tour of the record format I&#8217;ve found. This post is me retracing his steps from the WordPress side, and tripping over a couple of stones he&#8217;d already flagged.</p>\n\n\n\n<p class=\"wp-block-paragraph\">A <code>site.standard.publication</code> record is what a standard.site viewer turns into the little &#8220;View publication&#8221; card under each syndicated post. Mine rendered in the viewer&#8217;s default palette: generic dark, a blue-ish button, a placeholder &#8220;D&#8221; where my icon should be. </p>\n\n\n\n<p class=\"wp-block-paragraph\">The record is built from my WordPress settings, so in theory the card should already use my colours. It didn&#8217;t.</p>\n\n\n\n<p class=\"wp-block-paragraph\">The plugin derives a <code>basicTheme</code> (four colours: background, foreground, accent, accentForeground) from <code>wp_get_global_styles()</code>. It reads three values:</p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>color.background</code></li>\n\n\n\n<li><code>color.text</code></li>\n\n\n\n<li><code>elements.link.color.text</code> (the accent)</li>\n</ul>\n\n\n\n<p class=\"wp-block-paragraph\">Then it resolves each. The resolver (<code>resolve_color()</code>) accepts exactly two shapes: a raw hex, or a preset reference of the form <code>var(--wp--preset--color--{slug})</code>. Anything else returns <code>null</code>. If any of the three is <code>null</code>, the whole field is dropped, because a partial record is invalid against the lexicon.</p>\n\n\n\n<p class=\"wp-block-paragraph\">My global styles don&#8217;t hold hexes or preset references. They hold this:</p>\n\n\n\n<pre class=\"wp-block-code\"><code>color.background = var(--wp--custom--color--background)\ncolor.text       = var(--wp--custom--color--text)</code></pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two problems, both my own doing. First, those are <code>--wp--custom--</code> tokens, not <code>--wp--preset--</code>, so the resolver&#8217;s regex skips them. Second, each custom token expands to a <code>light-dark()</code>:</p>\n\n\n\n<pre class=\"wp-block-code is-style-css\"><code class=\"language-css\">--wp--custom--color--background: light-dark(var(--wp--preset--color--alt), var(--wp--preset--color--dark));</code></pre>\n\n\n\n<p class=\"wp-block-paragraph\">Even if the plugin followed one level of indirection, <code>light-dark(a, b)</code> has no single RGB answer. It&#8217;s two colours, one per OS preference. </p>\n\n\n\n<p class=\"wp-block-paragraph\">So the plugin did the right thing and omitted the theme rather than ship a broken one. </p>\n\n\n\n<p class=\"wp-block-paragraph\">Setting the colours by hand turned up a second issue. The plugin picks the button&#8217;s text colour automatically with a luminance &gt; 0.5 heuristic: above, black; below, white. My brand pink <code>#FA008A</code> sits at ~0.22, so the heuristic picks white. White on <code>#FA008A</code> is <strong>3.87:1</strong> — under the 4.5:1 WCAG AA bar for normal text.</p>\n\n\n\n<p class=\"wp-block-paragraph\">From Will&#8217;s code:</p>\n\n\n\n<pre class=\"wp-block-code is-style-bash\"><code class=\"language-bash\">//Note: Bluesky enforces AA-level color contrast for buttons — if you don't meet the minimum, it fails silently.</code></pre>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Button text on <code>#FA008A</code></th><th>Ratio</th><th>AA 4.5:1</th></tr></thead><tbody><tr><td>White <code>#ffffff</code> (auto-pick)</td><td>3.87:1</td><td>fail</td></tr><tr><td>Black <code>#000000</code> (my choice)</td><td>5.43:1</td><td>pass</td></tr></tbody></table></figure>\n\n\n\n<h2 class=\"wp-block-heading\">The fix</h2>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s a filter, so I set the theme explicitly rather than fight the resolver:</p>\n\n\n\n<pre class=\"wp-block-code is-style-php\"><code class=\"language-php\">add_filter( 'atmosphere_transform_publication', function ( array $record ): array {\n    $rgb = fn( $r, $g, $b ) =&gt; &#91; '$type' =&gt; 'site.standard.theme.color#rgb', 'r' =&gt; $r, 'g' =&gt; $g, 'b' =&gt; $b ];\n    $record&#91;'basicTheme'] = &#91;\n        'background'       =&gt; $rgb( 13, 15, 5 ),    // #0d0f05\n        'foreground'       =&gt; $rgb( 255, 255, 255 ), // #ffffff\n        'accent'           =&gt; $rgb( 250, 0, 138 ),   // #FA008A\n        'accentForeground' =&gt; $rgb( 0, 0, 0 ),       // #000000  5.43:1\n    ];\n    return $record;\n} );</code></pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.</p>\n\n\n\n<p class=\"wp-block-paragraph\">The icon is its own gotcha, and I walked straight into it the same way Will did:</p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">I had missed the option for a Publication icon the first time around, and if there&#8217;s one thing I like better than a little themed button, it&#8217;s an icon; hell yeah.</p>\n</blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">It comes from the WordPress Site Icon (<code>get_option('site_icon')</code>), 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.</p>\n\n\n\n<p class=\"wp-block-paragraph\">Anyway, this is an overly elaborate post so I can test if this all worked!</p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, lol, whilst testing this discovered Bluesky&#8217;s card preview url. <br><br><a href=\"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/</a></p>\n\n\n\n<p class=\"wp-block-paragraph\"></p>"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreic2nj4nrqtxddf2gbmjwgz53t6zrpjp36wuiu3p3xaxr5fsxuuitq"
    },
    "mimeType": "image/png",
    "size": 19947
  },
  "description": "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.",
  "path": "/2026/06/17/standard-bearer/",
  "publishedAt": "2026-06-17T15:37:15.000Z",
  "site": "at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/site.standard.publication/3mhpwfentz6lr",
  "tags": [
    "Syndication"
  ],
  "textContent": "I syndicate posts from WordPress to the AT Protocol with ATmosphere, Automattic’s plugin for publishing to Bluesky and 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 – one per post the site.standard.publication record – the card I’m fixing here The whole thing started with Will’s writeup on 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(): --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: //Note: Bluesky enforces AA-level color contrast for buttons — if you don't meet the minimum, it fails silently. Button text on #FA008ARatioAA 4.5:1White #ffffff (auto-pick)3.87:1failBlack #000000 (my choice)5.43:1pass The fix There’s a filter, so I set the theme explicitly rather than fight the resolver: 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/",
  "title": "Standard bearer",
  "updatedAt": "2026-07-02T11:49:19.000Z"
}