feat: Upgrade to Bootstrap 5 and bring the brand colours onto the site - #195
Merged
Conversation
npm marks 4.6.2 deprecated and Bootstrap 4 has been out of support since 2023. Bootstrap was only ever used here as CSS, because its v4 JavaScript needed jQuery. v5 dropped that dependency, so the two hand-rolled widgets that imitated Bootstrap by toggling its classes are gone, replaced by importing the collapse and carousel modules. That also removes the coupling those scripts had to v4 internals, which was about to break twice over: the carousel hardcoded carousel-item-left/-right, renamed to -start/-end in v5, and its <ol><li> indicators would have rendered invisible because v5 styles them via .carousel-indicators [data-bs-target]. Neither failure would have produced an error. The carousel deliberately omits data-bs-ride so it keeps its current click-to-advance behaviour rather than autoplaying. The rest is renamed utility classes, plus a compatibility layer in global.css for the three things v5 removes outright: .card-columns, which lays out the principles page, .text-justify, and v4's link decoration. The navbar's bleed margin now derives from the container gutter instead of hard-coding 15px, and .container is pinned to 1140px so v5's new xxl breakpoint does not widen every page by 16%. Heading utility classes are paired with their elements so the stats tables keep fixed sizes rather than opting into v5's fluid type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bg-info was never a brand choice: it is the slot Bootstrap reserves for informational messages, and v5 retuned that cyan from #17a2b8 to a much lighter #0dcaf0. The navbar cannot be light here, because the page sits on a white card and Bootstrap's default light-grey navbar would not read as a header at all, so take the other conventional option. bg-dark with data-bs-theme="dark" keeps this to stock utilities rather than a custom colour. The attribute has to sit on the .navbar element itself: Bootstrap keys the light-on-dark link colours and the white-stroke toggler icon off .navbar[data-bs-theme="dark"], so putting it on a wrapper would leave a dark hamburger on a dark bar. Contrast goes from roughly 2.3:1 to 5.7:1 for nav links and 15.4:1 for the active one, which combined with its existing fw-bold may now overstate the current page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
design/og.svg is the only place the identity has been deliberately composed, and it settles on red for the mark, near-black for the wordmark, and green for the one phrase that matters. The site used none of the red, and carried no logo at all -- Flix was recognisable everywhere except its own pages. Put the mark in the navbar, underline the current page in red, and hand the links over from Bootstrap's blue, which was the last colour here that belonged to the framework rather than to Flix. That keeps red on identity and navigation and leaves green doing content emphasis, so the two never compete for the same job. The red needs two strengths, because no single value covers both: #cf4647 is the mark's own colour and is right in shapes, but as text on white it only reaches 4.5:1, exactly the limit. Darkening it 20% gives 6.5:1, so anything made of words takes that instead. Bootstrap's primary moves with the links, since .link-primary reads --bs-primary-rgb rather than the link variables and would otherwise have left the documentation card titles blue while every other link turned red. The active underline waits for the expand breakpoint: in the collapsed menu the links stack, and a rule under one of them reads as a divider. Every link carries it transparent so the active one does not shift the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two problems, both surfaced by adding the brand. The navbar's container is justified space-between. With only the toggler on that line it sat at the start and never met the fork-me ribbon; giving space-between a second item pushed the toggler hard right, into the 149px the ribbon claims in the corner. Hide the ribbon below the breakpoint rather than move the toggler -- on a phone it was covering a good part of the screen to begin with, and above the breakpoint it stays where it has always been. The bar also collapsed later than it should. The brand and nine links need roughly 807px, against the 720px container md gets, so the row was cramped well before the toggler appeared. lg gives it 960px. That leaves three things keyed to the same breakpoint, and they have to move together: the expand class, the ribbon's visibility, and the media query around the active-page underline. Left at md, that last one would have drawn a red rule under one item of the stacked menu between 768 and 991px, where it reads as a divider rather than a marker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bootstrap's success green reads flat, and the reason is not that it is dark. It sits at hue 152, already halfway from green to teal, at 69% saturation. Replacing it with a fully saturated green at hue 145 changes how vivid it looks without changing how dark it is. It cannot get lighter. Most of the sixteen uses are large and bold and only owe 3:1, but seven are inline spans inside body paragraphs at 16px, plus a card title on /documentation/, and those hold the colour to 4.5:1. #008938 clears that by 0.03, the same margin Bootstrap left itself with #198754. Note that the value is 4.53:1 as written, not as derived: rounding a colour to the nearest of 256 steps per channel moves contrast by around 0.06, which at this threshold is the difference between passing and failing. Measure the hex, not the colour that produced it. Setting it on --bs-success means all sixteen call sites pick it up without touching any markup, the same way the links took the brand red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
og.svg still painted "effect-oriented" in Bootstrap 4's #28a745, so the card and the homepage have shown different greens since the upgrade -- and a third one once the site moved to #008938. Bring the card into line, and say in the header that this colour tracks --bs-success, since nothing previously connected the two. Also corrects a reference to InlineEditor.astro, renamed to CodeSnippet.astro in #187. public/og.png is NOT regenerated by this commit and still shows the old green. Re-rendering needs a machine with Monaco: the code line's per-token x offsets assume its 0.6001em advance, and a substituted monospace opens a visible gap before every token. On macOS: brew install librsvg brew install --cask font-open-sans rsvg-convert design/og.svg -w 1200 -h 630 -o public/og.png Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeSnippet.astro imports @shikijs/themes/light-plus to extend the Light+ theme with the magenta the Flix grammar uses for keywords, but the package was never declared. It resolved only because npm hoists it out of astro -> shiki, so the homepage's syntax highlighting was one Astro bump or hoisting change away from failing to resolve. Pinned to shiki's own 4.4.1 rather than the newer 4.4.2 that ^4.4.1 selects on its own: taking the newer one leaves two copies in the tree, which is the situation this commit exists to avoid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Only the 400 weight was imported, while Bootstrap sets every heading to 500 and the pages lean on fw-bold, <b>, and <strong> in more than fifty places. All of it was synthesised -- the browser algorithmically smearing the regular face -- which shows most at display sizes, on the h1 and the motto. Switching to the Latin subsets at the same time more than pays for the extra weight: the build drops from eighteen font files at 217 KB to eight at 126 KB, having gained a whole weight. Open Sans also ships Cyrillic, Greek, Hebrew, math, symbols and Vietnamese, none of which this site can reach -- the only characters above U+00FF anywhere in the rendered output are the trademark sign and the two dashes, all inside the Latin range. latin-ext is included even though nothing currently needs it. It carries the Polish, Czech and Turkish letterforms, and a contributor or sponsor list is exactly the sort of content that acquires one of those quietly; 30 KB is cheap next to a name silently falling back to a system font. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two rules inverted the caption and the controls so they would read against the light VSCode screenshots. Neither touched the indicators, which inherit --bs-carousel-indicator-active-bg -- white, on screenshots that are also white. The dots have not been visible. Bootstrap 5.3 has carousel-dark for precisely this, and it covers all three: black indicators, a black caption, and an inverted control icon. That retires both rules, and scopes the inversion to the icon rather than wrapping each whole control in a filter that also created a stacking context. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7cb8fdc added a comment naming the Bootstrap success custom property, which put a double hyphen inside an XML comment. XML forbids that outright, so the file has not parsed since -- rsvg-convert would have failed on the very render that commit was asking for. Nothing caught it. design/og.svg lives outside src/ and public/, so Astro never parses it, and CI only runs astro check and the build. The file has no validation at all. Name the property without its prefix, and say why in the comment so the next person does not reintroduce it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7cb8fdc brought design/og.svg's "effect-oriented" onto #008938 but could not re-render the card, so public/og.png has kept shipping Bootstrap 4's #28a745 -- the one place a visitor still saw the old green after the upgrade. Rendered with rsvg-convert. Open Sans is not a system font, so the latin 400 and 700 faces were converted from the @fontsource/open-sans .woff files to .ttf with fontTools and supplied through FONTCONFIG_FILE; Monaco came from the system. fc-match confirmed all three resolved to real files rather than substitutes, which is what the offsets in the code line depend on. Diffing against the previous PNG shows the only pixels that moved are inside (276, 230)-(538, 259), exactly the word "effect-oriented": 3784 pixels, none outside that line, and the glyph coverage is unchanged at 2313 core pixels with identical antialiasing counts. The mark, "Flix", the whole Monaco code line and "flix.dev" are byte-identical to the old render, so the substituted monospace that 7cb8fdc warned about -- a gap opening before every token -- demonstrably did not happen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upgrades Bootstrap from 4.6.2 — deprecated on npm, unsupported since 2023 — to 5.3.8, then follows through on the colour decisions the upgrade forced.
The upgrade
Bootstrap was only ever used here as CSS, because its v4 JavaScript needed jQuery. v5 dropped that dependency, so the two hand-rolled widgets that imitated Bootstrap by toggling its classes are gone, replaced by importing the
collapseandcarouselmodules.jqueryandpopper.jsfall out of the lockfile.That also retired a coupling that was about to break twice over, both times silently — no build error, no console error:
Carousel.astrohardcodedcarousel-item-left/-rightin a TypeScript ternary. v5 renamed those to-start/-endwhile leaving-next/-prevalone, which is exactly what makes it easy to miss.<ol><li>indicators would have rendered invisible, because v5 styles indicators through the attribute selector.carousel-indicators [data-bs-target].The rest is renamed utility classes, plus a small compatibility layer in
global.cssfor the three things v5 removes outright:.card-columns, which lays out four sections of the principles page,.text-justify, and v4's link decoration..containeris pinned to 1140px so v5's newxxlbreakpoint doesn't widen every page by 16%.What looks different
bg-darkwithdata-bs-theme="dark") rather thanbg-info. That class is the slot Bootstrap reserves for informational messages, and v5 retuned its cyan to a far lighter#0dcaf0on which white text reaches only about 2:1.design/og.svg. The site previously carried no logo anywhere.#a63839.$successsits at hue 152, already halfway to teal, at 69% saturation.#008938is fully saturated at hue 145 and exactly as dark — it cannot be lighter, since inline spans in body copy hold it to 4.5:1.design/og.svgand the renderedpublic/og.pnghad kept painting "effect-oriented" in v4's#28a745, which left the card as the last place the old green survived — and the one a visitor meets before the site itself.Every colour pairing was measured against WCAG; the ratios are recorded in the individual commit messages.
Fixed along the way
fw-bold,<b>and<strong>in more than fifty places. All of it was browser-synthesised. Loading the right weights and trimming to the Latin subsets took the build from eighteen font files at 217 KB down to eight at 126 KB.@shikijs/themeswas imported byCodeSnippet.astrobut never declared, resolving only because npm hoists it out ofastro→shiki.design/og.svgbriefly stopped being well-formed XML, when the comment added to name the Bootstrap success custom property put a double hyphen inside it. Nothing here catches that: the file sits outsidesrc/andpublic/, so Astro never parses it, and CI runs onlyastro checkand the build.Verification
astro checkandnpm run buildare clean. The emitted HTML was diffed page by page against a Bootstrap 4 baseline build, and both widgets were exercised against the real Bootstrap 5 JavaScript in a jsdom harness — indicator selectors, the-start/-endrename, slide/dot/aria-currentsynchronisation, and the collapse toggle'saria-expanded.The card was rendered with
rsvg-convert. Its code line carries explicit per-token x offsets that assume Monaco's 0.6001 em advance, so a substituted monospace would open a gap before every token — quietly, since the render still succeeds. Open Sans is not a system font either; the latin 400 and 700 faces were converted from the@fontsource/open-sans.wofffiles with fontTools and supplied throughFONTCONFIG_FILE, andfc-matchconfirmed all three faces resolved to real files rather than substitutes.Diffing the result against the previous PNG then settles it from the other end: the only pixels that moved lie inside
(276, 230)–(538, 259), exactly the word "effect-oriented". 3784 pixels changed, none outside that line, and the glyph coverage is identical at 2313 core pixels with matching antialiasing counts — only the fill differs. The mark, "Flix", the whole Monaco code line and "flix.dev" come out byte-identical to the old render.🤖 Generated with Claude Code