Skip to content

v2.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 29 Oct 22:04
v2.0.0
11fc1be

[2.0.0] - 2026-10-29

A redesign, and new addresses. Every page is rebuilt as rounded panels holding
cards, with a third theme, glass, a mascot, a new K logo, a navbar whose notch
opens into menus, pixel scenes in the heroes and the sections, and four new
pages: the journey (/parcours), the apps, the activity and the contact form.
Articles can be listened to, take the site's design and open their pictures in
the site's viewer; every post and every journey entry gets its own banner. The
major version is for the addresses: up to 1.1.0 both languages shared the same
paths, English by default; French now lives at the root and English under
/en/, so the pages indexed in English answer in French. Every page gets
structured data, the site a sitemap, and an RGAA 4.1.2 pass with a button that
pauses every animation. In production, the gwm article's demo GIF stops
answering 500: it no longer goes through the image server. OG image URLs are
now signed, so production needs NUXT_APP_SECRET.

Added

  • Four pixel shapes for the dither field
    (#72). Squares, circles,
    triangles or diamonds, after codrops' Bayer dithering demo: one shape is
    picked at random on each load and shared by every field of the page
    (background, footer, blog banners). Shapes other than squares need bigger
    cells; the noise and the pointer trail are sampled accordingly, so they keep
    their size on screen. Each shape covers about half a cell and the alpha
    ceiling stays at 18%, so text contrast is unchanged. Drawn in two canvas
    calls per frame whatever the cell count. localStorage['dither-shape'] pins
    a shape.

  • Pixels that spread on hover
    (#72). After hexagoncircle's
    <pixel-canvas>: hovering or focusing a card (pinned projects, blog posts,
    the blog sidebar card) or an accordion header (experiences, studies) grows
    cells of the page's dither shape out from where the pointer came in, which
    shimmer, then shrink away on leave. Under the content, never in the way of a
    click; nothing on touch, a still frame under prefers-reduced-motion.

  • Text links retype on hover
    (#73). After effect 1 of
    codrops' Line Text Hover Animations: a block cursor runs along the label as
    each character retypes through random glyphs, on hover or keyboard focus of
    the header and blog navigation (desktop), the footer lists and the projects'
    "view all".

  • A mascot
    (#74). A monkey in a
    backwards cap, in 31 moods, shows up where something did not go as planned:
    shocked on the 404 page, thinking when no blog post matches, sad when GitHub
    does not answer, clapping or nervous after sending the contact form, and
    quietly next to the copyright. Small and decorative, always next to text that
    already says what happened; 384px webp, 616 KB for the whole set.

  • An animated 404 page
    (#74). The "404" pixels
    arrive scattered and spring back into place, now and then one of them slips
    out of line, "Reset" replays the same return, the mascot floats and gets
    dizzy while you drag a pixel, the interactive dither field replaces the
    diagonal stripes and the subtitle retypes like the titles. Still under
    prefers-reduced-motion.

  • A journey page, /parcours (/en/journey in English)
    (#78). Laid out like an
    editorial spread (after isrod.eu): an intro, a strip of every role and
    degree, then one spread each, the story on the left and the stack and
    missions on the right, with "show more". Then the open source work, every
    item linked to its pull request or issue: the projects I maintain (gwm,
    vellum, LazyCurl, herdr-plugin-gwm, claude-crosstalk, kbrdn-docs), the
    contributions (winget, aqua-registry and awesome-ratatui merged, nixpkgs in
    review) and the bugs reported upstream. On the home page, the Parcours
    accordions give way to a one-line-per-role teaser linking to it.

  • An apps page, /apps
    (#79). gwm (available),
    vellum (in development) and LazyCurl (archived), each with its features and
    licence, links to its site, repository and releases, and for gwm the install
    commands of its README. The latest release is read from GitHub instead of
    written down.

  • A third theme, glass
    (#86). The theme button now
    cycles light, dark and glass. Glass is the dark theme with translucent panels
    blurred over the dither field, which then fills the whole screen, unfaded;
    everything styled for dark applies to it. Under
    prefers-reduced-transparency the panels turn opaque again.

  • Pictures seen through a pixel layer
    (#86). Banners, app
    screenshots and the carousel's voxel scenes are redrawn as a grid of the
    page's pixel shape, the one of the dither field, each cell the picture's
    colour there. Hovering clears the layer block by block from the pointer down
    to the crisp picture, and a new picture sweeps in block by block; at once
    under prefers-reduced-motion. Banners go through IPX at a few hundred pixels
    instead of the 11 MB originals.

  • The navbar's notch opens into menus
    (#86). Hovering a page in the navbar opens the notch under it into that page's menu: a line of
    context, its sections (the latest posts for the blog) and an illustration that
    follows the link under the pointer and stays on the last one; it widens as it
    opens, and the navbar's
    pill moves to the page whose menu is open. The announcement opens gwm's, which
    now leads to gwm.kbrdn.dev. Desktop only.

  • A face on the site
    (#86). A portrait in
    /contact's details, through the pixel layer; a small round photo beside the
    hero's handle, the drawn avatar on hover; and the hero's mascot now takes turns
    with drawn portraits of me. Re-encoded without metadata.

  • A pixel on the pointer
    (#86). A pixel in the accent
    sits on the pointer's tip; now and then it grows block by block into an arcade
    invader, hops off in one of a D-pad's eight directions, marches on a block per
    frame like in the arcade, sometimes turns into another one, then hops back
    onto the tip. A click throws eight blocks along the D-pad. A triple click calls
    Ichigo Kurosaki, from his Jump Ultimate Stars sprites (21 moves, Shikai and
    Bankai): he grows out of the pixel, plays a move drawn at random (never the
    same twice in a row; three are specials whose Getsuga Tenshō flies across the
    screen), then folds back; every third click calls him again. The
    sprites load on the first triple click (123 KB). A shift click turns the
    pixel into a sight that tightens on each click, the system pointer a cross.
    Mouse and trackpad only, never under prefers-reduced-motion.

  • Illustrations full size
    (#86). A click on an
    illustration of /parcours opens it crisp and full size in a native dialog
    (Escape, a click beside it or its button close it), as an article's pictures
    already zoom.

  • Where the companies, clients and schools live
    (#86). Under each entry of
    /parcours, links to flippad.com and jewely.fr, the clients' sites
    (bijouterie-julian.com, crown-dp.com, martin-media.fr), the school's, and the
    projects' own site and article.

  • Share an article from the device
    (#86). Where the browser has
    one, a button opens the device's share sheet (Web Share), beside Copy link.

  • Featured projects, personal and professional
    (#86). Two tabs on the home:
    the repositories pinned on GitHub, and the projects built at Jewely x Flippad
    in the same cards, the project's logo, its site, its client and its years, a
    bar showing when it ran within those years; each leads to it on
    /parcours.

  • An invader on the footer's edge
    (#86). Now and then one of
    the pointer's invaders runs in on the footer's top edge, in its own colour of
    the theme, and runs back and forth, a trail of pixels behind it that follows
    its jumps. Its favourite food pops up here and there, animated (pellets for
    the squid, cherries for the crab, stars, mushrooms or cookies for the
    others), and it leaps to eat it; it vaults the obstacles that rise in its way
    (a rock, spikes, a cactus, a crate) and ducks under the birds that fly at it.
    After a while it runs off and another one comes later; a click blows it up
    and the cycle goes on. One at a time, only while the footer is in view, never
    under prefers-reduced-motion.

  • A pixel scene in the pages' hero
    (#86). Beside the hero, under
    it on a phone, a detailed loop in the theme's palette with no background, the
    sun by day, the moon and stars by night: a crab climbs the mountain's trail to
    its flag and cheers in confetti on /parcours; an invader sits on a monitor
    typing gwm's commands on /apps; another fills a contribution graph on
    /activity; on /blog, a writing nook at night after Kirokaze's and
    Waneella's rooms, rain over the city, the article typed then sent off as a
    paper plane, a ginger cat asleep on the books; a bird posts a letter on
    /contact. It plays only on screen and holds still under
    prefers-reduced-motion. The pages' OG images show the same scene.

  • What is going on right now, on /activity
    (#86). What is being learnt,
    what is being read, and what is welcome, with a link to write.

  • The blog's blue in its dither
    (#86). The dither field takes
    an optional second colour in slow patches; the blog's layout uses its sky blue.

  • A CV to download
    (#86). Between the roles and
    the degrees on /parcours, and in /contact's details, in the site's
    language. The PDFs are hosted outside this public repository: their addresses
    come from the repository variables CV_URL_FR and CV_URL_EN at build time;
    without them, nothing shows.

  • An activity page, /activity
    (#86). The open source work
    (shared with /parcours), the contribution chart, and the recent public
    GitHub activity read live and told in plain words, day by day: pushes folded
    per branch and day, comments per issue, pull requests merged or opened. Cached
    ten minutes server side; the page says when GitHub was last checked.

  • A contact page, /contact
    (#86). The form leaves the
    footer for its own page (after isrod.eu): name, email, company, the topic as
    chips, the message, with the details beside it. The footer keeps a button to
    it and signs with the logo.

  • The new theme spreads from the button
    (#86,
    #88). A dithered wave grows
    from the theme button over a still snapshot of the old theme: small square
    pixels of the new page come through in a 4×4 Bayer order, behind a front in
    the new theme's colour (yellow to light, orange to dark, frosted white to
    glass), instead of every colour fading on its own. Chromium only: recorded
    under WebKit, the mask lagged and the page flashed between both themes
    several times in half a second, so Safari and Firefox switch at once, as do
    browsers without view transitions, prefers-reduced-motion and the site's
    pause.

  • Numbers roll in
    (#86). The figures in view
    (open source counts, contribution totals, reading times, the carousel's
    position, the 404's pixels in place) spin one turn of a slot machine reel when
    they appear, blurred while fast, and roll to the new value when it changes;
    so do the table of contents' counter and step. Text rolls the same way, letter
    by letter: the hero's rotating title, the mobile table of contents' section.
    The server renders the final value; copied text and screen readers get the
    plain value.

  • Listen to an article
    (#104). The table of
    contents reads the article aloud, sidebar and mobile bar alike: a play button
    at its head, then, once started, the music player's tick bar that seeks by
    sentence, in place of the scroll progress. Its steps are the sections: the
    one being read shows the music player's equalizer, and clicking a step while
    listening reads from there. Hovering a section heading shows a button that
    reads from it too (always shown on touch screens). It runs on the browser's
    own speech synthesis (the Web Speech API, what TTSReader uses): free, no key,
    no server. The voice is the cleanest the browser has in the article's
    language (Edge's neural voices, then Chrome's Google voices, then downloaded
    system voices), so its quality depends on the browser. Code blocks, tables
    and images are not read. Starting a reading stops the music and the other
    way round; leaving the article stops it.

  • Pixel scenes in every section
    (#100). Now and then (a
    first one 5 to 11 s after a page shows, then one every 18 to 38 s, one at a
    time on the whole site), a small pixel scene grows out of a section block by
    block like Ichigo, plays and folds back: a camera beside the About figures, a
    pencil beside the post titles, gears beside the tools, a rocket in the
    projects' footer, the Octocat beside the contribution graph, a crab climbing
    steps, a mortarboard beside the degrees, a saucer in Explore's header, a sheet
    folding into a paper plane by the contact form's button, a letter beside the
    address, an hourglass, a flip calendar on the timeline, a printer on the CV, a
    git merge on Open source, a bell on the activity feed, a cannon shooting an
    invader in the apps, and on the blog a bookworm, a coffee and a megaphone. In
    a card a scene takes the rightmost stretch wide enough that no text, picture
    or control crosses, and does not play where there is none. Canvas only while one plays,
    never under prefers-reduced-motion.

  • Redrawn page heroes
    (#100). The pixel scenes of
    /parcours (a living metro map of the career), /apps (gwm's TUI on a retro
    screen), /activity (an invader filling a contribution graph), /blog (a writing
    nook, a cat asleep on the books) and /contact (a bird posting a letter at a
    front door) are redrawn at the sections' scenes' level: shaded, outlined,
    looping seamlessly, painted only while on screen, a resting frame under
    reduced motion.

  • The stack orbits the mascot
    (#100). On the home page,
    badges of the stack (TypeScript, Rust, Vue, Laravel, Nuxt, Go) orbit the
    mascot like a tilted ring, behind it on the far side and in front of it on the
    near one.

  • A glow along the column's borders
    (#100). From 1280 px wide, a
    light now and then runs up or down one of the borders that frame the 80rem
    column.

  • Languages on the Pro cards
    (#100). The client projects
    show the share of each language like the pinned repositories (measured on the
    private repositories), their frameworks, and a swatch in the framework's
    colour.

  • A banner for every post and every /parcours entry
    (#111). After two
    reference sheets: the logo in relief and the name in heavy type beside it,
    over the project's screenshot, satin ribbons in the corners on the blog,
    metro lines on /parcours (after its hero's map). Each comes light and dark
    in the site's tokens and goes through the pixel layer like every picture of
    the site; a post's OG gets that layer frozen. The studies and the Virtual
    Immersion role get an illustration too, and the article header asks IPX for
    twice its width, so the picture under the hover stays sharp on a dense
    screen. scripts/banners.ts draws them all: the next post's is one entry
    away.

  • An easter egg in the home's hero
    (#118). Five quick clicks
    on the mascot open a pixel walkout after EA Sports FC 26 in its box: a
    players' tunnel the mascot walks out of, Kylian's flag, position and club
    (his top language, as on gitfut) revealed one after the other, then his
    gitfut card. A click skips to the card, another
    folds it all back into the mascot; Escape too, Enter from the keyboard. The
    card is read by GET /api/gitfut, cached 6 hours and served stale while
    gitfut is down; its picture is made smaller once on the server with sharp
    (now a declared dependency). Nothing of it loads with the page: its code
    and the card are fetched from the third click. Under reduced motion the card
    shows at once; it is announced to screen readers.

  • Structured data and a sitemap
    (#88). Every page sets
    <html lang> to the language served, a canonical and og:url on its own
    path, og:locale with its alternate, and hreflang links between its French
    and English versions (x-default on the French one). JSON-LD describes the
    site (Person and WebSite everywhere, ProfilePage, CollectionPage,
    ContactPage, Blog and BlogPosting, BreadcrumbList). /sitemap.xml lists the
    pages and the posts in both languages, and robots.txt points to it. The
    home now serves its own translated title and description instead of the
    English fallback.

  • A button that pauses every animation
    (#88). In the header: the
    scenes, the critter, the mascots, the rotating titles and the dither field
    stop and restart from there, as under the system's reduced motion (RGAA
    13.8), without removing any of them.

  • The site's version in the footer
    (#88), as /api/health
    returns it.

Changed

  • French at the root, English under /en/
    (#88). Addresses
    change.
    Up to 1.1.0 both languages shared the same paths and a visitor
    without a preference got English; French now lives at the root and English
    under /en/, each page with its own words where it has some (/parcours,
    /en/journey). The pages indexed in English (/blog/gwm…) therefore answer
    in French; their English version moves to /en/…, and hreflang pairs the
    two. The browser's language is only read on the root, on the first visit: an
    English browser or the language cookie leads to /en, a crawler without a
    preference stays on the French root.

  • The Journey menu shows the page's banners
    (#122). The notch's menu
    for /parcours showed pictures picked before the banners existed; it now reads
    them from the career data: the latest role's on Experience and as the menu's
    picture, the latest study's on Education, in the colour mode and through IPX
    at 480px like the blog's menu (6 to 11 KB each). No open source project has
    a banner, so gwm's screenshot stays.

  • Nuxt 4.6, nuxt-og-image 7 and @nuxt/fonts 1.2
    (#90). The three
    Dependabot updates (#67, #69 and the minor-and-patch group) only work
    together: Nuxt 4.6 checks the island request hash that nuxt-og-image 6 does
    not send. OG image URLs are now signed with the app secret: production
    needs NUXT_APP_SECRET
    (at least 32 characters, a repo secret passed by
    deploy.yml and release.yml), or every /_og/* request returns 500 and
    scripts/deploy.sh warns. The VPS wrapper's allowlist accepts it: sync
    /srv/ before the first deploy that sends it (docs/RELEASE.md).

  • The navigation leads to pages
    (#80). Journey, Apps and
    Blog, on every page and on mobile, instead of the home page's anchors; one
    list shared by the header, the blog header and the footer, with the current
    page marked. On wide screens (1700px and up), the home page gets a table of
    contents in the margin, like an article's, following the section being read.

  • A new logo
    (#74). A K drawn from a
    rising bar and two orange pieces, redrawn as SVG on a 45° grid, replaces the
    bracket monogram everywhere: header, favicon (.svg that follows the system
    theme, all-orange .ico from 16 to 256 px), OG images and the README banner.
    The five colour variants are in public/images/logo/. Every corner is
    rounded with the same radius, 4 on the 96 grid
    (#101). On hover or
    keyboard focus of the logo or the button that carries it, the bar runs along
    its diagonal, leaves at the top, comes back from the bottom and cuts the
    branch again on its way through: the two orange pieces spring apart, then
    back. Still under prefers-reduced-motion and the site's pause button.

  • OG images in the site's current design
    (#74). #121212
    background, the dither field frozen at the top, a grid and column border
    fading out downwards instead of diagonal stripes, no separators, a green
    "open to opportunities" badge and the mascot as an unframed avatar. The
    blog's image now carries the same badge and avatar as the home page's.

  • The hero avatar is the GitHub one
    (#74): the mascot in its
    hoodie.

  • Pinned projects
    (#74). A "Featured
    projects" label repeated the section title right under it: gone. Each card
    now shows a GitHub mark, next to the site link when there is one; the whole
    card already opened the repository, nothing said so.

  • Titles retype on appearance and on hover
    (#73). After effect 4 of the
    same demo, in place of the left-to-right decode of 1.1.0: every character of
    a page or section title vanishes, then retypes left to right through a few
    random glyphs when the title first scrolls into view, and again on hover
    while a tinted block rises behind it. No GSAP: each glyph is drawn over its
    own transparent character, so nothing reflows; screen readers get the plain
    text, and nothing moves under prefers-reduced-motion.

  • The pointer ripples the background and leaves a trail of pixels
    (#72). After the
    CursorRipples filter of safzan.dev's hero, with its settings: a damped wave
    field splashed where the pointer moves, harder the faster it goes, displaces
    the dither pattern like water. Cells along the pointer's path light up
    gradually, filled in pixel by pixel by the Bayer matrix, and fade out in
    under a second. 60 fps while something moves, 24 at rest. Replaces 1.1.0's
    spring trail.

  • The home page in panels, after disarto
    (#86). Each section is a
    rounded panel holding cards, with disarto.co's own radii (20 px, 4 px of
    padding, concentric 16 px cards): a title in Inter, mono eyebrows over large
    Inter figures, a footer with mono meta and pill buttons. The GitHub chart's
    views are a segmented control, availability a status tag, and the pinned
    repositories' languages are cut into ticks. The hero sits open on the page,
    its mascot floating and changing expression; the carousel becomes a panel,
    its slide in a card beside its scene, moved on by a tick timer that pauses
    on hover and focus and is gone under prefers-reduced-motion. Corners are no
    longer sharp (--ui-radius), and the orange buttons get dark text: white on
    #d4825d fails AA.

  • The other pages in panels too
    (#86). /apps: one card per
    app, its features ticked, then its install commands and its demo after
    dashed rules. /parcours: a card per role and degree, company logos as
    rounded tiles, and the open source work as a card of figures (a tick bar per
    group, coloured by status) over its lists, one at a time behind a segmented
    control. /blog: a filter bar, the posts as cards, the pages in the panel's
    footer. Articles: the header is a card, the banner inset at its top.

  • A floating navbar, the announcement as a notch under it
    (#86). The navbar is
    detached from the top, a panel as wide as the home page's, its pages a
    segmented control; the announcement banner hangs under it as a notch with
    concave shoulders, on every page. The footer, the music player and the table
    of contents take the panel surfaces too.

  • The roles strip follows the reading on /parcours
    (#86). Once scrolled away,
    it comes back compact under the navbar, marks the role or degree being read
    with a sliding ink and keeps the address on its anchor; anchors scroll
    smoothly.

  • Motion, everywhere the same
    (#86). Changing page is a
    focus pull through view transitions, the navbar staying sharp and still, and
    every panel comes in as it is scrolled to (what is on screen at load stays
    put). Segmented controls get one pill that glides
    to the active segment. A new slide, track or hero title leaves on one side and
    comes in from the other, and in the player only the cover and title move.
    Collapses (a role's missions, the player's list, the mobile menu) open on
    their height, their content unblurring in step with it. Refilled lists (blog,
    contribution calendars, open source) close their removed rows from the last
    one up and open the new ones one after the other, so a list showing less
    shrinks instead of jumping.

  • /parcours as a vertical timeline
    (#86). Roles and degrees on
    a rail that fills in the accent as the page is read, the years beside it; each
    role's projects under it (Jewely E-commerce, Fiches Pédagogiques, the
    configurator, Assmat Facile, the ERP) with their client, role and period, and
    an illustration from their public site, their README or their maker's site
    seen through the pixel layer. Each entry unfolds its stack, what was done and
    its links. Figures on top (years at work, client projects, open source tools,
    degrees) roll in. On a phone, no rail: the sticky strip of roles tells where
    the reader is. The home's Parcours section shows the same timeline, one line
    per stop, and its About the same figures.

  • The error page lives in the site
    (#86). Navbar, footer and the
    three themes, an intro like the other pages, and the status code in pixels
    in a panel: drag them with a mouse, pen or finger, a counter says how many are
    back in place. Any status shows its own code.

  • OG images as the site's components, in the repos' banners' design
    (#86). One panel on the
    pixel field, two dashed construction lines along its sides and nothing else
    outside it, its height following its content: a header, then a card (name and
    title in Fenix or the article's title, buttons; the drawn portrait or the
    article's banner beside its facts; the figures on the home, the stack or the
    summary after a dashed rule), then a footer with the address in mono. Articles
    keep the field tinted with the blog's blue. The README's banner follows the
    same design, with the home as its picture.

  • Sections swing in
    (#86). Below the fold, each
    panel comes in slightly turned and off to one side, from the left and the
    right in turn.

  • Blog cards and the table of contents
    (#86). Cards after an event
    card (banner on top, reading time, one meta line), frosting the banner's edge
    in glass, and a hatched card for the next post in the empty slot of an odd
    page; the table of contents as a
    checklist of the sections read in the accent, the mobile one included. The
    music player's controls and listing in the panel style.

  • The hero's button gets in touch
    (#86). The disabled "Book a
    call" gives way to "Get in touch", a link to /contact.

  • Articles in the site's design
    (#106). Code blocks and
    tables are cards (1rem corners, the cards' border and surface, opaque in
    glass), table heads are mono labels, bullets are orange as on /parcours,
    inline code sits on the controls' surface. Hovered links no longer get a
    second line under their underline. Headings are unchanged. The :deep()
    rules of prose.css never applied (a global sheet drops them): they are
    gone, and the elements are themed through Nuxt UI's prose theme in
    app/app.config.ts.

  • Article pictures open in the site's viewer
    (#106). The same one as
    /parcours's illustrations, from a real button: Enter or Space opens it,
    Escape, the close button or a click outside closes it, and focus goes back
    to the picture. Nuxt UI's zoom, a second viewer out of the keyboard's reach,
    is gone.

  • OG images draw the redrawn heroes
    (#100). A page with a scene
    shows its new hero at its resting frame. The header's window icons and the
    footer's toolbar icons give way to the page's address (top right) and the
    site's K with its two languages, the page's one lit (bottom left).

  • Formatting moves to bun fmt, Bun pinned to 1.4.4
    (#119). Bun's built-in
    formatter replaces oxfmt, configured in bunfig.toml to print exactly what
    oxfmt printed: no file is reformatted, and the same exceptions hold
    (Markdown, MDX and the satori OG components untouched, CSS in double
    quotes). Linting stays on oxlint: Bun has no linter. CI, the Docker image and
    the Nix dev shell all run Bun 1.4.4, the first release with bun fmt;
    flake.lock is now committed.

Fixed

  • Accessibility, after an RGAA 4.1.2 audit
    (#88). Contrasts (tags and
    statuses in light, greys in dark, the archived card on /apps, article links
    and code colouring, the hero's title), a focus ring at 3:1 in light, focused
    elements no longer hidden under the navbar, the articles' promo card and the
    player's cover reachable from the keyboard, status messages and page changes
    announced, heading levels, the notch's menus closed by Escape, required
    fields flagged, article links underlined, English labels left untranslated.
    Still not compliant, and known: the GitHub calendar (colour and mouse only),
    the social links' hover card, link groups outside lists, and a single
    navigation system.

  • A missing article answered 200 with an empty page
    (#88); it now answers 404.

  • OG images render the same on every machine
    (#90). Google serves Inter
    as a variable font, which satori cannot read, and nuxt-og-image's fallback
    download varied: Inter on a Mac, Fenix on the CI runner, so in production.
    The OG templates now use Inter OG, a static Latin subset of Inter 400 and
    500 in public/fonts/inter, never preloaded by the site. The CI no longer
    caches node_modules, which bun left with stale nested packages after a
    lockfile change.

  • An OG image could come in the other language
    (#86). Both languages share
    the pages' paths, and the image rendered first was served to the other one
    from the cache; each language now has its own.

  • The contribution chart's arrow keys lost the focus
    (#86). They looked for the
    selected tab across the whole page, so focus jumped to the featured projects'
    or open source tabs; it now stays in the chart's own tabs.

  • Pictures overflowed their frame's corners
    (#86). The carousel's scene
    and the full-size illustration were rounded wider than the panel around them;
    their corners now follow it.

  • Activity sat under Projects in the home's table of contents
    (#86), and its French label
    had lost its accent.

  • The gwm card on /apps overflowed on mobile
    (#84). Its longest install
    line set the width of the whole column, and the card clipped the text and the
    video on the right; the line now scrolls inside its code block.

  • The contact form trusted what it received
    (#86). Every field went into
    the email's HTML as is: they are now escaped, capped in length and required to
    be strings (an array crashed the route), the topic limited to a closed list.

  • Glass lost its blur while things moved
    (#86). Fades on a wrapper
    (page transitions, section entrances, the compact strip, the player) turned
    the panels' blur off until they ended; and an unlayered global colour
    transition overrode every transition of the components, so the strip's ink
    jumped and focus rings snapped.

  • The kbrdn.dev card shows the site's logo
    (#100). It came from the
    default branch's favicon, still the old logo and coloured by the system's
    theme rather than the site's.

  • Animated pictures in articles arrive as committed
    (#116). GIFs went through
    the image server, which decoded and re-encoded every frame on each request,
    with no cache: the gwm demo took 16 s to reach preprod, 1.49 MB for 884 KB
    committed, and answered 500 in production. They are now served as static
    files. Other pictures still go through it: it recompresses truecolor PNGs
    smaller, though it still inflates palette ones such as the gwm article's.