Skip to content

Releases: kbrdn1/kbrdn.dev

v2.0.0

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](https://github.com/kbrdn1/kbrdn.dev/i...

Read more

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 21:35
v1.1.0
68a49e4

[1.1.0] - 2026-10-16

A visual release. Every page gets an animated pixel-dither background that
follows the pointer, the home page is reworked around a stack list, the pinned
GitHub repositories and the real contribution graph, the music player floats
over every page, and page titles decode on load. Two production bugs hidden
behind fallbacks are fixed on the way: GitHub activity was random data, and the
contact form most likely never emailed anything, both because their secrets
were read from a runtime config frozen empty at build time. Dependabot starts
with this release, since it only reads its config from main.

Added

  • An animated pixel-dither background, interactive with the pointer
    (#51). Two scrolling noise
    layers, thresholded through a 4×4 Bayer matrix into a few alpha levels of the
    primary orange, drawn at cell resolution and upscaled with
    image-rendering: pixelated. On every page, a fixed band over the top 85% of
    the viewport fades out downwards, and the pointer leaves a trail that lights
    up and pushes the noise. The trail is squishy: it follows the pointer on a
    spring, so it lags, overshoots and wobbles before settling, and its brush
    stretches along the motion. The pattern is random on each load. The footer and
    the blog banners (article header and list thumbnails, on top of their image)
    carry the same field, animated but not interactive. Canvas 2D, no dependency:
    the noise is generated in a Web Worker, the colour follows the theme toggle,
    the animation pauses off screen and in hidden tabs, a single frozen frame is
    drawn under prefers-reduced-motion, and touch input leaves no trail. The
    densest level stays at 18% alpha: in dark, text that met the 4.5:1 contrast
    still does over it; in light, small grey labels drop to about 4.0:1 under
    the densest cells, a trade-off kept for a visible field.
  • A floating "Listening to" player
    (#54). The music player
    leaves the home page's About section for a button in the bottom-right corner
    of every page, which opens it floating at the bottom, centred and as wide as the content
    (Escape closes it). Mounted outside
    the layouts, so the music keeps playing from the home page to the blog; the
    playlist is only fetched on first open.
  • Decoding page titles
    (#53). On load, the main title of
    every page (home, blog, articles) resolves left to right out of random glyphs,
    in up to 2.4 s, without shifting the layout; the server sends the plain
    text, screen readers get the real name, and prefers-reduced-motion keeps
    it still.
  • Weekly dependency updates with Dependabot
    (#56). Bun packages, GitHub
    Actions and the oven/bun base image, as PRs against dev, with minor and
    patch bumps grouped per ecosystem. The PR checks used to fail such PRs by
    construction (branch name, no linked issue); they now let PRs opened by
    Dependabot through, the title check still applies. Dependabot reads its
    config from main, so it starts once this version is released.

Changed

  • The home page, reworked
    (#53). The skills tabs
    give way to a stack list (a column of categories, then each one's tools with
    their icon, linking to its official site). The contact links (GitHub,
    Twitter, LinkedIn) move into the hero as icon-only ghost buttons, next to
    "Book a call", in place of the "View on GitHub" and "Read the blog" buttons; the Contact section goes.
    The "Open to opportunities" badge turns green, the avatar loses its stripes,
    border and hover zoom, and the About text loses its em dashes. Featured projects are
    now the repositories pinned on GitHub, as compact cards (logo or language
    icon, stars, forks, a language bar along the bottom edge). GitHub activity
    opens on a "Recent" tab, the rolling last 12 months as on the GitHub
    profile, and its month labels no longer overlap. Loading skeletons now have
    the shape of what they stand for, one per section.

  • A cleaner page frame
    (#51). Outside the 80rem
    column, a muted 24px grid and a border along the column, continuing the
    header's, fade out downwards; the column itself carries no grid. Drawn once
    by the layout instead of side zones repeated in every page (diagonal stripes
    on the home page, a full-height grid on the blog). The full-height borders
    around the column, the separators between sections and the corner triangles
    are gone; corners stay sharp. The dark background goes from
    #1a1a1a to #121212, and the footer gets an opaque background, with the
    blur over its content column.

Fixed

  • GitHub activity showed made-up numbers in production
    (#53). The token was read
    from a runtime config frozen at build time, empty in the Docker image, so the
    API fell back to random data on every request. The server now reads
    GITHUB_TOKEN at request time, and returns an error instead of random data
    when it cannot reach GitHub.
  • Contact form messages were most likely never emailed
    (#53). Same cause with the
    Resend key: without it, the form only logged the message and reported
    success. The key is now read at request time too.

v1.0.2

Choose a tag to compare

@github-actions github-actions released this 19 Sep 15:24
v1.0.2
958bb73

[1.0.2] - 2026-09-19

A checks-only release: the site served in production is the same as 1.0.1.
What changes is what stands between a change and production — the OG images,
which broke twice during 1.0.1 without any check noticing, are now compared
byte for byte in CI — and the release guide, which 1.0.1 proved wrong in four
places.

Added

  • CI now checks the OG images
    (#44). They are rendered at
    request time, so lint, typecheck and build never ran them, and the oxfmt
    reformat of 1.0.1 broke them twice with CI green. The build job now serves
    .output and compares the OG images of /, /blog and /blog/gwm byte for
    byte against references committed under scripts/og-smoke/ — rendering is
    identical across platforms, a reference made on macOS matches production bit
    for bit. A gradient split across lines fails it with a 500, single quotes in
    main.css fail it on the image itself. An intended change to an OG image
    goes through scripts/og-smoke.sh --update.

Fixed

  • docs/RELEASE.md described the release it was supposed to guide wrongly
    (#43). Cutting 1.0.1
    contradicted four passages: the Linked issue check applies to pull requests
    into dev too, not only main; main also requires an approval, so the
    promotion is merged with --admin — which bypasses the required checks as
    well, and must only come after they are green; /api/health cannot tell the
    tag's image from the one deploy.yml ships on the merge, so the proof of a
    release is gh release view; and re-running an old tag does not rebuild the
    image, it rewrites the release notes from the tag's tree.

v1.0.1

Choose a tag to compare

@github-actions github-actions released this 19 Sep 04:06
v1.0.1
8f65002

[1.0.1] - 2026-09-19

A maintenance release. The site itself changes in one place — the portfolio no
longer claims a job that ended — but most of this version is about the checks
around it: the typecheck had silently stopped running in CI, and linting and
formatting move to the oxc toolchain.

Changed

  • The site no longer says I work at Jewely x Flippad
    (#33). The contract ended in
    September 2026, but the hero badge, the footer status and the OG image still
    read "In contract at Jewely x Flippad", and the experience was still flagged
    current. The experience now closes on 2026-09 with the role renamed to Full
    Stack Engineer, and the status reads "Open to opportunities".

  • Linting and formatting move to oxc
    (#37). oxlint replaces
    ESLint and @nuxt/eslint, with the rules migrated from the old config. Not
    carried over: the template-only rules of eslint-plugin-vue, no-unused-vars
    and prefer-const inside .vue files (oxlint does not run them on SFCs), and
    three Nuxt-specific rules that would have kept an ESLint plugin around. Unused
    locals in .vue files are caught by the typecheck instead (noUnusedLocals).
    oxfmt formats the code — Markdown and MDX are left alone, and so are the
    satori OG components and the quotes in CSS, both of which broke the OG images
    in a way no CI step could see — and CI now fails on unformatted code. The one-off reformat commit is listed in
    .git-blame-ignore-revs.

Fixed

  • The typecheck was not running in CI
    (#35). vue-tsc was not a
    dependency, so nuxt typecheck fetched it through npx, which resolved its
    typescript peer to latest — TypeScript 7.0.2, which no longer exports
    ./lib/tsc. vue-tsc died on load, and continue-on-error: true on the step
    kept the job green: the v1.0.0 release run (2026-08-14) already failed on it. vue-tsc and
    typescript@~5.9 are now pinned devDependencies, the step fails the job
    again, and the 38 type errors it had been hiding are fixed (mostly unchecked
    index access; titles added to the pages schema, the dead i18n lazy
    option removed).

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 14 Aug 17:40
v1.0.0
1e512ce

[1.0.0] - 2026-08-14

First tagged version of the site. The site itself was already live on
kbrdn.dev — what this version adds is everything that was
missing around it: a way to say which version is deployed, a way to go back to
a previous one, and a release that a tag actually performs rather than merely
records.

Versioning starts at 1.0.0 rather than 0.x: the product is in production,
and a 0. would misdescribe it.

Added

  • A release flow, where there was none
    (#18). Deployment worked, but
    nothing tied it to a version: images were tagged :main and :dev, both
    mutable, package.json carried no version field at all, and nobody could
    answer "which version is live on pre-prod right now".

    Tags now drive deployment. vX.Y.Z-rc.N builds an immutable
    ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Z-rc.N, ships it to preprod and opens a
    GitHub pre-release; vX.Y.Z does the same for prod and publishes the
    release. Pushing to dev still redeploys preprod for day-to-day iteration —
    that path is untouched, and deploy.yml only ever wakes on branches while
    release.yml only wakes on tags.

    The routing lives in scripts/resolve-release-tag.sh rather than inline YAML,
    because getting it wrong ships an unvalidated candidate to production;
    scripts/resolve-release-tag.test.sh covers it, malformed tags included.

  • GET /api/health returning { status, version, sha, env }, all three
    baked in at build time. The container HEALTHCHECK now points at it instead
    of /: the old probe went green as soon as the home page rendered
    server-side, which stays true even when the API layer behind /api/github/*
    and /api/contact is dead. It uses a real GET, not wget --spider — that
    sends a HEAD, and h3 does not route HEAD to a .get handler.

  • Guardrails that hold the protocol up, rather than a document asking
    politely: a stable tag must be an ancestor of main and a candidate an
    ancestor of dev; package.json must match the tag; the notes file must
    exist. All four run before anything is built or deployed. The GitHub
    release is only published once /api/health has confirmed, from the outside,
    that the expected version and environment are serving traffic.

  • scripts/github-deploy.sh is versioned and hardened. The piece that
    reconciles the workflow — which configures over environment variables — with
    deploy.sh, which parses flags, turned out to be a ForceCommand wrapper
    living only on the VPS: the centre of the deployment pipeline was invisible
    from the repository. It now only exports an allowlist (ENV, IMAGE,
    GITHUB_TOKEN, RESEND_API_KEY, NUXT_STUDIO_TOKEN) and refuses anything
    else, PATH included — deploy.sh resolves docker, nginx and
    systemctl through it, as root.

  • A promo banner at the top of the README
    (#24), dark and light, built
    around the site's own monogram.

Changed

  • Rollback actually rolls back
    (#18). scripts/deploy.sh
    used to docker rm -f the outgoing colour at the end of every deploy, while
    --rollback required exactly that container — at steady state a single
    colour runs, so the option could only ever fail, and silently so until the
    day it was needed. The outgoing colour is now stopped and kept, and the
    immutable per-version images make
    --image ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Z a real return to a known version
    rather than a colour flip.

  • Deployments to one environment are serialised. The protocol itself used
    to put two in flight: merging into main wakes deploy.yml, pushing the tag
    right after wakes release.yml, and their workflow-level concurrency groups
    differ. Two deploy.sh --env prod would then target the same container names
    and the same nginx upstream. Both jobs now share a vps-deploy-<env> key.

  • Containers carry a kbrdn.version label, so docker inspect answers the same
    question as /api/health from inside the VPS.

  • Re-running a released tag no longer rebuilds its image: registry tags are
    mutable and the build is not reproducible, so republishing could quietly make
    :vX.Y.Z stop meaning what it meant — the very property version rollback
    rests on.

Docs

  • docs/RELEASE.md — the protocol, the repo's knobs, the
    real deployment chain (ForceCommand → github-deploy.sh → deploy.sh), and
    the VPS synchronisation procedure.
  • Two traps found the hard way and written down: overwriting /srv/deploy.sh
    while it runs breaks the deploy in progress (bash reads a script by offset),
    and every PR into main must close an issue — the release PR included, so
    each version needs its own.

v1.0.0-rc.2

v1.0.0-rc.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Aug 17:12
v1.0.0-rc.2
7dd0c07

[1.0.0-rc.2] - 2026-08-14

First tagged version of the site. The site itself was already live on
kbrdn.dev — what this version adds is everything that was
missing around it: a way to say which version is deployed, a way to go back to
a previous one, and a release that a tag actually performs rather than merely
records.

Versioning starts at 1.0.0 rather than 0.x: the product is in production,
and a 0. would misdescribe it.

Added

  • A release flow, where there was none
    (#18). Deployment worked, but
    nothing tied it to a version: images were tagged :main and :dev, both
    mutable, package.json carried no version field at all, and nobody could
    answer "which version is live on pre-prod right now".

    Tags now drive deployment. vX.Y.Z-rc.N builds an immutable
    ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Z-rc.N, ships it to preprod and opens a
    GitHub pre-release; vX.Y.Z does the same for prod and publishes the
    release. Pushing to dev still redeploys preprod for day-to-day iteration —
    that path is untouched, and deploy.yml only ever wakes on branches while
    release.yml only wakes on tags.

    The routing lives in scripts/resolve-release-tag.sh rather than inline YAML,
    because getting it wrong ships an unvalidated candidate to production;
    scripts/resolve-release-tag.test.sh covers it, malformed tags included.

  • GET /api/health returning { status, version, sha, env }, all three
    baked in at build time. The container HEALTHCHECK now points at it instead
    of /: the old probe went green as soon as the home page rendered
    server-side, which stays true even when the API layer behind /api/github/*
    and /api/contact is dead. It uses a real GET, not wget --spider — that
    sends a HEAD, and h3 does not route HEAD to a .get handler.

  • Guardrails that hold the protocol up, rather than a document asking
    politely: a stable tag must be an ancestor of main and a candidate an
    ancestor of dev; package.json must match the tag; the notes file must
    exist. All four run before anything is built or deployed. The GitHub
    release is only published once /api/health has confirmed, from the outside,
    that the expected version and environment are serving traffic.

  • scripts/github-deploy.sh is versioned and hardened. The piece that
    reconciles the workflow — which configures over environment variables — with
    deploy.sh, which parses flags, turned out to be a ForceCommand wrapper
    living only on the VPS: the centre of the deployment pipeline was invisible
    from the repository. It now only exports an allowlist (ENV, IMAGE,
    GITHUB_TOKEN, RESEND_API_KEY, NUXT_STUDIO_TOKEN) and refuses anything
    else, PATH included — deploy.sh resolves docker, nginx and
    systemctl through it, as root.

  • A promo banner at the top of the README
    (#24), dark and light, built
    around the site's own monogram.

Changed

  • Rollback actually rolls back
    (#18). scripts/deploy.sh
    used to docker rm -f the outgoing colour at the end of every deploy, while
    --rollback required exactly that container — at steady state a single
    colour runs, so the option could only ever fail, and silently so until the
    day it was needed. The outgoing colour is now stopped and kept, and the
    immutable per-version images make
    --image ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Z a real return to a known version
    rather than a colour flip.

  • Deployments to one environment are serialised. The protocol itself used
    to put two in flight: merging into main wakes deploy.yml, pushing the tag
    right after wakes release.yml, and their workflow-level concurrency groups
    differ. Two deploy.sh --env prod would then target the same container names
    and the same nginx upstream. Both jobs now share a vps-deploy-<env> key.

  • Containers carry a kbrdn.version label, so docker inspect answers the same
    question as /api/health from inside the VPS.

  • Re-running a released tag no longer rebuilds its image: registry tags are
    mutable and the build is not reproducible, so republishing could quietly make
    :vX.Y.Z stop meaning what it meant — the very property version rollback
    rests on.

Docs

  • docs/RELEASE.md — the protocol, the repo's knobs, the
    real deployment chain (ForceCommand → github-deploy.sh → deploy.sh), and
    the VPS synchronisation procedure.
  • Two traps found the hard way and written down: overwriting /srv/deploy.sh
    while it runs breaks the deploy in progress (bash reads a script by offset),
    and every PR into main must close an issue — the release PR included, so
    each version needs its own.