fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
feat(docs): new brand mark, CrowdSec indigo, four diagrams, and the i18n gate on the MDX AST (#95)
## Description
Phases 5 to 7 of the documentation plan, plus the technical debt that
had accumulated in the i18n gate, plus a new brand mark chosen from
twenty-nine candidates.
## Type of change
- [x] ✨ New feature (non-breaking change that adds functionality)
- [x] 📖 Documentation update
- [x] 🧪 Tests / tooling / CI
- [x] 🧹 Refactoring / code cleanup
## Changes
### The i18n parity gate is rewritten onto the MDX AST
It had produced **three defects in one area** — an HTML-comment
terminator that missed `--!>`, a chained-replace sanitisation that left
a bare `<!--` behind, and a one-character backtick delimiter that leaked
double-backtick spans. Two were found by CodeQL, one in review. Three
findings in one file say the approach is wrong, not that the patches
were bad; parsing makes those classes unreachable.
All four parser packages were **already resolved in the lockfile**, so
declaring them added nothing to install.
Verified by a **differential harness over all 56 corpus files across six
dimensions**, which caught a regression the rewrite introduced and
surfaced a genuine improvement: three multi-path `ConfigOption`
invocations that used to collapse into one indistinguishable bucket are
now keyed individually. Demonstrated: a Spanish page pointing at a
non-existent config path **passed** the old gate and fails the new one.
It also corrected a claim the old file asserted: an HTML comment closed
with `--!>` is **not** closed for the renderer. CommonMark ends an HTML
block only at `-->`, so the earlier "fix" was wrong in the other
direction.
### A new mark
The shield is gone. Rendered side by side at the sizes that matter it
was, unmistakably, **the WiFi glyph** — a shield containing concentric
arcs radiating from a dot is the standard signal icon at 104px and at
23px. Nobody noticed until the candidates were laid out and looked at.
For a network tool that is worse than generic; it is misleading.
What replaces it: four lanes meeting a rail, three crossing and
continuing, one stopping dead at it. The proportion is deliberate — a
firewall does not block everything, it blocks the exception. **Three
primitives, 282 bytes of geometry, no ids, no defs, no mask**, against
the 3.8 KB the shield needed.
Chosen from 29 candidates across two waves. The first wave's five
directions all came from the data side — list, chain, loop, flow — and
its winner drew a generic list; the siblings anchor on a git ref and an
open book, objects that only exist in their domains. The second wave
entered through the domain and beat it on all four judging lenses.
### The accent moves to CrowdSec's indigo
`#4d4a98` light, `#807dc0` dark — the same hue and saturation raised
until it clears 4.5:1 against the **surface** rather than merely the
page. The page-only value failed twice, on the terminal prompt glyph and
the sidebar pill, and the contrast gate caught both.
This is ecosystem alignment, not a return to the co-branding retired
earlier: that palette paired CrowdSec indigo *with* MikroTik teal,
implying endorsement by two vendors including the hardware one. This is
a CrowdSec bouncer, listed on their Hub. **Worth checking their brand
guidelines before a release goes out.**
The rail takes the accent because the boundary is what the product is.
Pass and stop are carried by **length before colour**: the accent and
the muted tone measure 1.19:1 apart, so a mark relying on that pair to
tell them apart would come very close to collapsing into one tone in
greyscale.
CrowdSec's amber moves to `--rb-status-warn`, where it does semantic
work rather than decorating.
### Four architecture diagrams, under one convention
The rule-placement ladder, the decision funnel in three swimlanes, the
reconciliation diff and the shutdown order. Solid is a write, dashed is
a read, carried on three channels — line pattern, then colour, then a
legend inside each diagram — so they survive greyscale, colour vision
deficiency and a printout.
Six accuracy defects in them were corrected against the Go source. One
claim was rewritten rather than deleted: entries whose comment does not
match the prefix were described as "never listed", but `ListAddresses`
queries on the list name alone and filters client-side, so they **are**
fetched every pass and only then dropped — which costs transfer on a
list an operator may be sharing.
### Navigation
Sidebar depth hierarchy, pagination that names its destination (two
entries were both labelled "Overview"), scroll-into-view, and a **mobile
theme toggle** — there was none, the header hid the control below 50rem.
It never writes `data-theme` or `localStorage` itself: it drives
Starlight's own select and reads state back via a MutationObserver,
because anything else desynchronises from the desktop control and the
before-paint script.
## Testing
- [x] `pnpm analyze` exits 0 — config schema check, brand raster check,
i18n parity, contrast, `astro check`, ESLint, Prettier, build,
`html-validate`
- [x] `go build ./...`, `go test -race ./...`, golangci-lint, gosec,
staticcheck, govulncheck all clean; markdownlint clean
- [x] Every gate mutation-tested in both directions, including the
parity gate's twelve behaviours and the new brand-raster check
- [x] Rendered SVGs inspected in `dist/` — all 62 diagram edges parse,
dashed edges carry a real `stroke-dasharray`, and the mermaid CSS
ordering was checked so the dotted class actually wins
- [ ] Tested against a real MikroTik router (not applicable — no
behavioural change ships here)
## Notes for review
Four claims **of my own** were wrong and are corrected in the last
commit: a stale contrast figure in all three copies of the mark, the
`prefers-contrast` block still pointing at the retired teal, a comment
describing lanes "cut by the rail" that are unbroken rectangles, and a
false provenance claim about the sibling status fills' saturation.
An intermittent `analyze` failure reported by three agents did not
reproduce — 8 clean builds out of 8 in isolation. The cause was
concurrent agents building into the same `dist/`.
https://claude.ai/code/session_01ENguejGi5gZcxoMbCKWFBy
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/95?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Introduced refreshed CrowdSec Indigo branding across the documentation
site, including responsive wordmark, icons, and social images.
* Added light/dark theme support for diagrams and documentation images.
* Added mobile theme switching and improved sidebar, header, pagination,
and navigation behavior.
* Added clearer architecture diagrams, flow explanations, and Grafana
dashboard imagery.
* **Documentation**
* Expanded English and Spanish guidance for decision filtering,
firewall-rule placement, reconciliation, shutdown behavior, and decision
lifecycles.
* Clarified read/write diagram conventions and operational edge cases.
* **Bug Fixes**
* Improved validation for localized documentation structure and
generated brand assets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
docs: on-site GEO improvements from the 2026-07-07 audit (#64)
## Description
Applies every on-site action from the fresh GEO audit (overall 72/100,
up from 62 on Jul 2 — see `GEO-AUDIT-REPORT.md`, updated in this PR).
Off-site actions (awesome-crowdsec PR, community threads, Wikidata
enrichment, YouTube) are intentionally out of scope. Also adds the
language selector to the mobile header per review feedback.
## Type of change
- [x] 📖 Documentation update
- [x] ✨ New feature (non-breaking change that adds functionality)
## Changes
- **Visible authorship (E-E-A-T high)**: "Maintained by José Manuel
Requena Plens" footer byline + new About page (en/es) with maintainer
links, third-party listings (CrowdSec Hub, Wikidata, pkg.go.dev, GHCR),
and a tested-with matrix (RB5009UG+S+, RouterOS 7.22.1, CrowdSec 1.5+,
Go 1.26.4)
- **Schema**: TechArticle `image` + git-derived `datePublished`;
`APIReference` typing for the 7 reference pages; `Dataset` for the
RB5009 benchmarks (localized); FAQPage `@id`/`inLanguage`/`isPartOf`;
SoftwareApplication `softwareHelp`, `softwareRequirements`, real
dashboard screenshot; Person `description`; WebSite `inLanguage:
["en","es"]` (a fix lost during the i18n round, spotted live by the
audit)
- **Citability**: Quick Start direct-answer lead + prerequisites block +
question-form headings; Installation decision-rule intro; quantified
"defaults work well"; methodology anchors for the 97× and 200–500x
claims; self-contained API-key note
- **Outbound authority links**: CrowdSec bouncers docs, RouterOS
firewall docs
- **Meta/technical**: 46 descriptions expanded to ~120–165 chars (they
were breaking YAML until quoted); `twitter:title/description`;
`og:image` width/height/alt; `x-default` in sitemap hreflang alternates;
IndexNow HTTP result surfaced in the CI job summary
- **Mobile UX**: language selector now visible in the header below the
50rem breakpoint (was buried at the bottom of the menu drawer); other
right-group items stay hidden
- **Infra**: `latest-release.mjs` resolves the docs root via
`process.cwd()` — `import.meta.url` pointed into `dist/chunks/` under
Vite SSR bundling, which silently broke git lookups for bundled
consumers
## Testing
- [x] Documentation checks pass (`pnpm analyze`: astro check 0 errors,
eslint, prettier, build 57 pages, html-validate)
- [x] 102 internal links + anchors verified against the built HTML in
both locales; en/es structural parity holds on all 28 pages
- [x] JSON-LD verified in dist: APIReference, Dataset, datePublished
(real first-commit dates; correctly omitted for untracked pages), FAQ
@id, softwareHelp, inLanguage ["en","es"], 56 x-default sitemap
alternates
- [x] Mobile header verified with Playwright at 390×844 in both locales
(language select visible, no overlap with the hamburger)
- [ ] Unit tests (not applicable — docs/CI only)
- [ ] Tested against a real MikroTik router (not applicable)
## RouterOS compatibility
Not applicable — no runtime behavior changes.
## Checklist
- [x] My code follows the project style guidelines
- [ ] I have added tests for my changes (not applicable)
- [x] I have updated the documentation (if applicable)
- [ ] I have updated CHANGELOG.md (docs/CI-only change)
- [x] My changes do not introduce new warnings
https://claude.ai/code/session_01DqSrSogARwnqPjVcJWYAXb
## Summary by Sourcery
Apply on-site GEO audit improvements to the docs site and mobile header.
Enhancements:
- Add visible maintainer attribution via footer byline and new bilingual
About page with external listings and tested-with matrix.
- Upgrade structured data by typing reference pages as APIReference,
adding TechArticle images and git-derived datePublished, and introducing
a Dataset node for RB5009 benchmarks.
- Enrich global JSON-LD (Person description, WebSite inLanguage,
SoftwareApplication screenshot, softwareHelp, and softwareRequirements)
and FAQPage IDs and linkage.
- Improve AI- and search-facing metadata, including extended page
descriptions, Twitter meta tags, OG image dimensions/alt text, sitemap
x-default hreflang, and more precise configuration/architecture copy.
- Refine quickstart and installation docs with prerequisite blocks,
direct-answer intros, clarified deployment guidance, and CrowdSec docs
citations, plus stronger outbound authority links to RouterOS
documentation.
- Expose the language selector in the mobile header while keeping other
right-group items hidden on small screens.
- Fix git-based utilities by resolving the docs root from process.cwd()
and adding first-commit date lookup for datePublished, and surface
IndexNow results in GitHub Actions job summaries.
Documentation:
- Update GEO audit report with the July 7 re-audit, new scores, and
revised action plan.
fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
feat(docs): new brand mark, CrowdSec indigo, four diagrams, and the i18n gate on the MDX AST (#95)
## Description
Phases 5 to 7 of the documentation plan, plus the technical debt that
had accumulated in the i18n gate, plus a new brand mark chosen from
twenty-nine candidates.
## Type of change
- [x] ✨ New feature (non-breaking change that adds functionality)
- [x] 📖 Documentation update
- [x] 🧪 Tests / tooling / CI
- [x] 🧹 Refactoring / code cleanup
## Changes
### The i18n parity gate is rewritten onto the MDX AST
It had produced **three defects in one area** — an HTML-comment
terminator that missed `--!>`, a chained-replace sanitisation that left
a bare `<!--` behind, and a one-character backtick delimiter that leaked
double-backtick spans. Two were found by CodeQL, one in review. Three
findings in one file say the approach is wrong, not that the patches
were bad; parsing makes those classes unreachable.
All four parser packages were **already resolved in the lockfile**, so
declaring them added nothing to install.
Verified by a **differential harness over all 56 corpus files across six
dimensions**, which caught a regression the rewrite introduced and
surfaced a genuine improvement: three multi-path `ConfigOption`
invocations that used to collapse into one indistinguishable bucket are
now keyed individually. Demonstrated: a Spanish page pointing at a
non-existent config path **passed** the old gate and fails the new one.
It also corrected a claim the old file asserted: an HTML comment closed
with `--!>` is **not** closed for the renderer. CommonMark ends an HTML
block only at `-->`, so the earlier "fix" was wrong in the other
direction.
### A new mark
The shield is gone. Rendered side by side at the sizes that matter it
was, unmistakably, **the WiFi glyph** — a shield containing concentric
arcs radiating from a dot is the standard signal icon at 104px and at
23px. Nobody noticed until the candidates were laid out and looked at.
For a network tool that is worse than generic; it is misleading.
What replaces it: four lanes meeting a rail, three crossing and
continuing, one stopping dead at it. The proportion is deliberate — a
firewall does not block everything, it blocks the exception. **Three
primitives, 282 bytes of geometry, no ids, no defs, no mask**, against
the 3.8 KB the shield needed.
Chosen from 29 candidates across two waves. The first wave's five
directions all came from the data side — list, chain, loop, flow — and
its winner drew a generic list; the siblings anchor on a git ref and an
open book, objects that only exist in their domains. The second wave
entered through the domain and beat it on all four judging lenses.
### The accent moves to CrowdSec's indigo
`#4d4a98` light, `#807dc0` dark — the same hue and saturation raised
until it clears 4.5:1 against the **surface** rather than merely the
page. The page-only value failed twice, on the terminal prompt glyph and
the sidebar pill, and the contrast gate caught both.
This is ecosystem alignment, not a return to the co-branding retired
earlier: that palette paired CrowdSec indigo *with* MikroTik teal,
implying endorsement by two vendors including the hardware one. This is
a CrowdSec bouncer, listed on their Hub. **Worth checking their brand
guidelines before a release goes out.**
The rail takes the accent because the boundary is what the product is.
Pass and stop are carried by **length before colour**: the accent and
the muted tone measure 1.19:1 apart, so a mark relying on that pair to
tell them apart would come very close to collapsing into one tone in
greyscale.
CrowdSec's amber moves to `--rb-status-warn`, where it does semantic
work rather than decorating.
### Four architecture diagrams, under one convention
The rule-placement ladder, the decision funnel in three swimlanes, the
reconciliation diff and the shutdown order. Solid is a write, dashed is
a read, carried on three channels — line pattern, then colour, then a
legend inside each diagram — so they survive greyscale, colour vision
deficiency and a printout.
Six accuracy defects in them were corrected against the Go source. One
claim was rewritten rather than deleted: entries whose comment does not
match the prefix were described as "never listed", but `ListAddresses`
queries on the list name alone and filters client-side, so they **are**
fetched every pass and only then dropped — which costs transfer on a
list an operator may be sharing.
### Navigation
Sidebar depth hierarchy, pagination that names its destination (two
entries were both labelled "Overview"), scroll-into-view, and a **mobile
theme toggle** — there was none, the header hid the control below 50rem.
It never writes `data-theme` or `localStorage` itself: it drives
Starlight's own select and reads state back via a MutationObserver,
because anything else desynchronises from the desktop control and the
before-paint script.
## Testing
- [x] `pnpm analyze` exits 0 — config schema check, brand raster check,
i18n parity, contrast, `astro check`, ESLint, Prettier, build,
`html-validate`
- [x] `go build ./...`, `go test -race ./...`, golangci-lint, gosec,
staticcheck, govulncheck all clean; markdownlint clean
- [x] Every gate mutation-tested in both directions, including the
parity gate's twelve behaviours and the new brand-raster check
- [x] Rendered SVGs inspected in `dist/` — all 62 diagram edges parse,
dashed edges carry a real `stroke-dasharray`, and the mermaid CSS
ordering was checked so the dotted class actually wins
- [ ] Tested against a real MikroTik router (not applicable — no
behavioural change ships here)
## Notes for review
Four claims **of my own** were wrong and are corrected in the last
commit: a stale contrast figure in all three copies of the mark, the
`prefers-contrast` block still pointing at the retired teal, a comment
describing lanes "cut by the rail" that are unbroken rectangles, and a
false provenance claim about the sibling status fills' saturation.
An intermittent `analyze` failure reported by three agents did not
reproduce — 8 clean builds out of 8 in isolation. The cause was
concurrent agents building into the same `dist/`.
https://claude.ai/code/session_01ENguejGi5gZcxoMbCKWFBy
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/95?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Introduced refreshed CrowdSec Indigo branding across the documentation
site, including responsive wordmark, icons, and social images.
* Added light/dark theme support for diagrams and documentation images.
* Added mobile theme switching and improved sidebar, header, pagination,
and navigation behavior.
* Added clearer architecture diagrams, flow explanations, and Grafana
dashboard imagery.
* **Documentation**
* Expanded English and Spanish guidance for decision filtering,
firewall-rule placement, reconciliation, shutdown behavior, and decision
lifecycles.
* Clarified read/write diagram conventions and operational edge cases.
* **Bug Fixes**
* Improved validation for localized documentation structure and
generated brand assets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->