diff --git a/apps/blog/public/sponsors/anthropic-dark.svg b/apps/blog/public/sponsors/anthropic-dark.svg
new file mode 100644
index 0000000..cc8b904
--- /dev/null
+++ b/apps/blog/public/sponsors/anthropic-dark.svg
@@ -0,0 +1 @@
+
diff --git a/apps/blog/public/sponsors/anthropic-light.svg b/apps/blog/public/sponsors/anthropic-light.svg
new file mode 100644
index 0000000..983bfc4
--- /dev/null
+++ b/apps/blog/public/sponsors/anthropic-light.svg
@@ -0,0 +1 @@
+
diff --git a/apps/blog/public/sponsors/kong.svg b/apps/blog/public/sponsors/kong.svg
new file mode 100644
index 0000000..6247a00
--- /dev/null
+++ b/apps/blog/public/sponsors/kong.svg
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/apps/blog/public/sponsors/mestier.svg b/apps/blog/public/sponsors/mestier.svg
new file mode 100644
index 0000000..176211b
--- /dev/null
+++ b/apps/blog/public/sponsors/mestier.svg
@@ -0,0 +1,9 @@
+
diff --git a/apps/blog/src/content/posts/en/why-rust-for-an-iam.mdx b/apps/blog/src/content/posts/en/why-rust-for-an-iam.mdx
new file mode 100644
index 0000000..c7cd210
--- /dev/null
+++ b/apps/blog/src/content/posts/en/why-rust-for-an-iam.mdx
@@ -0,0 +1,97 @@
+---
+title: "Fast wasn't the point"
+description: "Everyone assumes we picked Rust for FerrisKey because it's fast. It is, but that's a side effect, not the argument. The real reason is what the compiler refuses to let us ship, why our tests look nothing like a typical Java or Node test suite, and why that same determinism is what makes simulation testing and formal proofs a realistic next step instead of a pipe dream."
+short_description: "We didn't choose Rust for speed. We chose it because the compiler refuses to let entire categories of bugs exist."
+date: 2026-07-26
+tags: [rust, architecture, security, engineering, testing, ferriskey, open-source]
+status: published
+author: nathaelb
+---
+
+Every time we mention that FerrisKey is written in Rust, the first reaction is the same: "oh, so it's fast." It is. Our comparison numbers are public: a few megabytes of memory at idle, a cold start under a second, next to a JVM-based IAM that needs hundreds of megabytes and half a minute to be ready to answer a single request.
+
+But if speed were the whole argument, we'd have a marketing page, not this post. Speed is what you notice from the outside. It's not why we bet a security-critical piece of infrastructure on this language.
+
+## The bug class an IAM cannot afford
+
+An IAM sits where attackers hit first, and where latency matters most. Every authenticated request in your infrastructure passes through it. It parses tokens it didn't generate, headers it doesn't control, payloads sent by clients it has no reason to trust.
+
+Historically, that exact kind of code (parsers, crypto, session handling, written in C or C++) is where our industry's worst vulnerabilities have come from. Heartbleed wasn't a design flaw in TLS. It was a buffer over-read in OpenSSL's memory handling: a bug class, not a business logic mistake.
+
+Rust's ownership model removes that bug class at compile time, not through discipline, code review, or a fuzzer that happened to find it first. A FerrisKey binary that compiles cannot have a data race or a use-after-free. That's not a testing result. It's a mathematical property of the code that made it past `cargo build`.
+
+And it's not just the application code. The TLS stack is [rustls](https://github.com/rustls/rustls), not OpenSSL: memory-safe, with no C dependency, in the exact layer that talks to the network first. Password hashing runs on `argon2`. Passkeys and WebAuthn run on `webauthn-rs`. The code that touches what an attacker sends you is written in the same memory-safe language as the rest of the system, not bolted on across an FFI boundary into C.
+
+## The compiler as a wall, not a suggestion
+
+This is the part that doesn't show up in a benchmark: Rust lets us push rules from "documented in a wiki page, hopefully respected" to "the code does not compile otherwise."
+
+Take the login flow. A single authentication attempt in FerrisKey can only end one of four ways:
+
+```rust title="ferriskey-domain: the login flow can only end one of four ways"
+#[derive(Debug, Clone, PartialEq)]
+pub enum AuthenticationStepStatus {
+ Success,
+ RequiresActions,
+ RequiresOtpChallenge,
+ Failed,
+}
+```
+
+Nothing exotic. But the handler that turns this into an HTTP response has to match on it, and Rust refuses to compile a `match` that forgets a variant. Add a fifth outcome tomorrow (say, a passkey challenge step) and every response mapper that needs to know about it either handles it explicitly or the build breaks. Nobody has to remember to update it. Nobody can forget, because forgetting isn't a code path anymore, it's a compiler error.
+
+That's the shift: a rule stops being something a senior engineer has to remember to check in review, and becomes something the type system enforces on every commit, from every contributor, forever.
+
+## A testing strategy that looks nothing like the usual one
+
+If invalid states can't be represented, and forgotten cases can't compile, your tests stop needing to defend against them. That changes what you actually test, and how.
+
+FerrisKey follows a hexagonal architecture: the domain defines *ports* (traits) for everything that talks to the outside world: the database, the mailer, the webhook dispatcher. Several of those crates ship a `mock` feature:
+
+```toml title="core/Cargo.toml (trimmed)"
+ferriskey-security = { path = "../libs/ferriskey-security", features = ["mock"] }
+ferriskey-seawatch = { path = "../libs/ferriskey-seawatch", features = ["mock"] }
+ferriskey-webhook = { path = "../libs/ferriskey-webhook", features = ["mock"] }
+```
+
+That mock isn't a runtime stand-in generated by reflection, the way Mockito or `jest.mock()` work. It's a real, separate implementation of the exact same trait, checked by the compiler like any other. If the port's contract changes, the mock either gets updated to match or the whole workspace stops building. It cannot silently drift out of sync with what production actually calls.
+
+The result is a test suite that spends its effort on business rules (is this transition allowed, does this policy apply, is this token actually expired) instead of re-proving, in every single test file, that the mock doesn't crash or return the wrong shape. The compiler already proved that part.
+
+## Capitalizing without trading away security
+
+Every module we ship (Trident for MFA and WebAuthn, SeaWatch for audit trails, our webhook system) is its own crate, talking to the domain core only through those same ports. That's not a folder convention that a rushed PR can quietly break. A module cannot reach around the security core to touch something it has no port for; there's no path in the compiled binary for it to do so.
+
+That's what lets us build faster without spending that speed against our own security posture. Growing the feature surface and keeping the trust boundary intact aren't in tension here. The second is what the compiler is already enforcing while we do the first.
+
+## Fewer surprises, in theory
+
+An IAM isn't a peripheral service. It's the piece every other system in your information system quietly depends on staying correct. When it's wrong, it's not a degraded feature. It's every downstream login, every downstream authorization check, at once.
+
+For the SREs who get paged when it isn't: no garbage collector means no GC pause showing up as a latency spike under load. Exhaustive matches on realms, roles, and session states mean an "impossible" case that Java or Python would happily let you construct at runtime simply doesn't exist as a value here. None of this makes incidents impossible. Bad deploys, bad configs, and bad assumptions about the world are still entirely possible. But a whole category of "how did this even happen" surprises is closed off before the binary ever ships. Fewer unknown unknowns, in theory. And in a system this central, that theory is worth having on your side.
+
+## What determinism buys us next
+
+Memory safety gets the headlines, but it's not the only property this design gives us for free. Ownership and the absence of a garbage collector also make the runtime deterministic: no GC pause, no JIT warm-up curve, no allocator quietly deciding to compact the heap under load. Feed the same input to the same binary twice and it takes the same path both times, with the same timing characteristics. That's not just a safety property, it's a performance one too: the same determinism that rules out data races is what keeps tail latency boring under load.
+
+That determinism is also a foundation, not just an outcome, and it points at two things we want FerrisKey to grow into.
+
+The first is simulation-driven development. Systems like FoundationDB and TigerBeetle run their entire test suite against a simulated clock and network, replaying byte-for-byte identical event sequences while injecting failures (dropped connections, delayed disk writes, clock skew) and reproducing a rare bug from a single seed instead of chasing a flaky CI run for a week. That kind of harness only works if the program doesn't have hidden non-determinism to fight against. We don't have this built for FerrisKey today, but it's a direction Rust actually makes reachable, in a way a language where GC timing is one more source of noise to simulate around does not.
+
+The second is formal proofs on the invariants that matter most. Not proving the whole system correct, that's not a realistic bar for a project this size, but specific, narrow claims worth actually proving instead of only testing: a token that's been revoked can never be accepted again, a realm boundary can never be crossed by a code path that forgot to check it. Rust's ecosystem has verification tools built for exactly this (Kani, Creusot, and contract-based crates), and they're tractable here for the same reason the rest of this post exists: the type system already rules out the aliasing and hidden mutation that make formal reasoning impractical in most other languages. This is on our roadmap, not in production, but it's a much shorter walk from Rust than from a runtime where "what could this pointer alias" doesn't have a clean answer.
+
+## What we're not going to pretend
+
+None of this is free, and we're not pretending it is. Rust's learning curve is real, especially for teams used to a GC doing memory management for them. The talent pool is smaller than Java's or Go's, though it's growing fast, and it skews toward engineers who actively sought out these guarantees. Compile times are longer than a dynamically typed language's "just run it." Parts of the async ecosystem are still younger than their JVM or Node equivalents.
+
+We think that trade is worth making for exactly one category of software: the piece that sits in the critical path of everything else, that attackers hit first, and that has to be right more than it has to be quick to write on day one. An IAM is that category.
+
+## The part everyone actually asked about
+
+So, back to where this started: yes, FerrisKey is fast. A few megabytes at idle instead of hundreds. A cold start under a second instead of thirty. Now you know why that's not the interesting part. It's the visible side effect of everything above it: no GC to warm up, no heap to grow, a binary that either fully proves what it claims or doesn't compile at all.
+
+Speed was never the argument. It's just what you can measure from the outside.
+
+---
+
+FerrisKey is open source, Apache 2.0, and the code discussed here is public. If you want to see the real authentication flow, the actual ports, or the mock features in context, the repo is on [GitHub](https://github.com/ferriskey/ferriskey). Clone it, read it, argue with our choices in an issue. That's the point of doing this in the open.
diff --git a/apps/blog/src/content/posts/fr/why-rust-for-an-iam.mdx b/apps/blog/src/content/posts/fr/why-rust-for-an-iam.mdx
new file mode 100644
index 0000000..838b00c
--- /dev/null
+++ b/apps/blog/src/content/posts/fr/why-rust-for-an-iam.mdx
@@ -0,0 +1,97 @@
+---
+title: "La vitesse n'était pas l'argument"
+description: "Tout le monde suppose qu'on a choisi Rust pour FerrisKey parce que c'est rapide. C'est vrai, mais c'est un effet de bord, pas l'argument. La vraie raison, c'est ce que le compilateur nous interdit de livrer, à quel point nos tests ne ressemblent en rien à une suite Java ou Node classique, et pourquoi ce même déterminisme fait des tests par simulation et des preuves formelles une prochaine étape réaliste plutôt qu'un vœu pieux."
+short_description: "On n'a pas choisi Rust pour la vitesse. On l'a choisi parce que le compilateur refuse de laisser exister des classes entières de bugs."
+date: 2026-07-26
+tags: [rust, architecture, security, engineering, testing, ferriskey, open-source]
+status: published
+author: nathaelb
+---
+
+Chaque fois qu'on mentionne que FerrisKey est écrit en Rust, la première réaction est toujours la même : "ah, donc c'est rapide." C'est vrai. Nos chiffres de comparaison sont publics : quelques mégaoctets de mémoire au repos, un démarrage à froid en moins d'une seconde, face à un IAM basé sur la JVM qui a besoin de centaines de mégaoctets et d'une trentaine de secondes avant de pouvoir répondre à la moindre requête.
+
+Mais si la vitesse était tout l'argument, on aurait écrit une page marketing, pas cet article. La vitesse, c'est ce qu'on remarque de l'extérieur. Ce n'est pas pour ça qu'on a parié une brique critique de sécurité sur ce langage.
+
+## La classe de bug qu'un IAM ne peut pas se permettre
+
+Un IAM se trouve là où les attaquants frappent en premier, et là où la latence compte le plus. Chaque requête authentifiée de votre infrastructure passe par lui. Il parse des tokens qu'il n'a pas générés, des headers qu'il ne contrôle pas, des payloads envoyés par des clients auxquels il n'a aucune raison de faire confiance.
+
+Historiquement, c'est exactement ce genre de code (parsers, crypto, gestion de session, écrits en C ou C++) qui a produit les pires vulnérabilités de notre industrie. Heartbleed n'était pas un défaut de conception de TLS. C'était une lecture mémoire hors limites dans la gestion mémoire d'OpenSSL : une classe de bug, pas une erreur de logique métier.
+
+Le modèle d'ownership de Rust élimine cette classe de bug à la compilation, pas par discipline, par relecture de code, ou par un fuzzer qui l'aurait trouvée le premier. Un binaire FerrisKey qui compile ne peut pas avoir de data race ou de use-after-free. Ce n'est pas un résultat de test. C'est une propriété mathématique du code qui a survécu à `cargo build`.
+
+Et ce n'est pas que le code applicatif. La stack TLS, c'est [rustls](https://github.com/rustls/rustls), pas OpenSSL : memory-safe, sans dépendance C, exactement dans la couche qui parle au réseau en premier. Le hachage des mots de passe tourne sur `argon2`. Les passkeys et le WebAuthn tournent sur `webauthn-rs`. Le code qui touche ce qu'un attaquant vous envoie est écrit dans le même langage memory-safe que le reste du système, pas greffé de l'autre côté d'une frontière FFI vers du C.
+
+## Le compilateur comme un mur, pas une suggestion
+
+C'est la partie qui n'apparaît dans aucun benchmark : Rust nous permet de faire passer une règle du statut "documentée dans un wiki, respectée si tout va bien" au statut "le code ne compile pas sinon."
+
+Prenez le flow de login. Une tentative d'authentification chez FerrisKey ne peut se terminer que d'une de ces quatre façons :
+
+```rust title="ferriskey-domain: le flow de login ne peut finir que de quatre façons"
+#[derive(Debug, Clone, PartialEq)]
+pub enum AuthenticationStepStatus {
+ Success,
+ RequiresActions,
+ RequiresOtpChallenge,
+ Failed,
+}
+```
+
+Rien d'exotique. Mais le handler qui transforme ça en réponse HTTP doit faire un `match` dessus, et Rust refuse de compiler un `match` qui oublie une variante. Ajoutez une cinquième issue demain (disons une étape de challenge passkey) et chaque mapper de réponse qui doit en tenir compte la gère explicitement, ou la build casse. Personne n'a besoin de se souvenir de la mettre à jour. Personne ne peut l'oublier, parce qu'oublier n'est plus un chemin de code possible, c'est une erreur de compilation.
+
+C'est ce glissement qui compte : une règle cesse d'être quelque chose qu'un ingénieur senior doit penser à vérifier en review, et devient quelque chose que le système de types impose à chaque commit, de chaque contributeur, pour toujours.
+
+## Une stratégie de test qui ne ressemble à rien de connu
+
+Si les états invalides ne peuvent pas être représentés, et que les cas oubliés ne compilent pas, vos tests n'ont plus besoin de se défendre contre eux. Ça change ce que vous testez réellement, et comment.
+
+FerrisKey suit une architecture hexagonale : le domaine définit des *ports* (des traits) pour tout ce qui parle à l'extérieur, la base de données, le mailer, le dispatcher de webhooks. Plusieurs de ces crates exposent une feature `mock` :
+
+```toml title="core/Cargo.toml (extrait)"
+ferriskey-security = { path = "../libs/ferriskey-security", features = ["mock"] }
+ferriskey-seawatch = { path = "../libs/ferriskey-seawatch", features = ["mock"] }
+ferriskey-webhook = { path = "../libs/ferriskey-webhook", features = ["mock"] }
+```
+
+Ce mock n'est pas un substitut généré à l'exécution par réflexion, comme le font Mockito ou `jest.mock()`. C'est une vraie implémentation, séparée, du même trait exact, vérifiée par le compilateur au même titre que l'implémentation de prod. Si le contrat du port change, le mock est mis à jour en conséquence ou tout le workspace arrête de compiler. Il ne peut pas dériver silencieusement de ce que la prod appelle réellement.
+
+Le résultat, c'est une suite de tests qui consacre son énergie aux règles métier (cette transition est-elle autorisée, cette politique s'applique-t-elle, ce token est-il vraiment expiré) plutôt qu'à re-prouver, dans chaque fichier de test, que le mock ne plante pas ou ne renvoie pas la mauvaise forme. Le compilateur a déjà prouvé cette partie-là.
+
+## Capitaliser sans céder sur la sécurité
+
+Chaque module qu'on livre (Trident pour le MFA et le WebAuthn, SeaWatch pour les pistes d'audit, notre système de webhooks) est son propre crate, qui ne parle au cœur du domaine qu'à travers ces mêmes ports. Ce n'est pas une convention de dossiers qu'une PR pressée peut discrètement casser. Un module ne peut pas contourner le cœur de sécurité pour toucher quelque chose pour lequel il n'a pas de port ; il n'existe simplement aucun chemin dans le binaire compilé pour le faire.
+
+C'est ce qui nous permet de construire plus vite sans dépenser cette vitesse contre notre propre posture de sécurité. Faire grandir la surface fonctionnelle et garder la frontière de confiance intacte ne sont pas en tension ici. La seconde est déjà imposée par le compilateur pendant qu'on fait la première.
+
+## Moins de surprises, en théorie
+
+Un IAM n'est pas un service périphérique. C'est la brique dont dépend silencieusement chaque autre système de votre SI pour rester correct. Quand il se trompe, ce n'est pas une fonctionnalité dégradée. C'est chaque login en aval, chaque vérification d'autorisation en aval, en même temps.
+
+Pour les SRE qui sont réveillés quand ce n'est pas le cas : pas de garbage collector, donc pas de pause GC qui se manifeste en pic de latence sous charge. Des `match` exhaustifs sur les realms, les rôles et les états de session font qu'un cas "impossible", que Java ou Python vous laisseraient volontiers construire à l'exécution, n'existe tout simplement pas en tant que valeur ici. Rien de tout ça ne rend les incidents impossibles. Mauvais déploiement, mauvaise config, mauvaise hypothèse sur le monde restent tout à fait possibles. Mais toute une catégorie de surprises du type "comment est-ce même arrivé" est fermée avant même que le binaire soit livré. Moins d'inconnues inconnues, en théorie. Et dans un système aussi central, cette théorie mérite d'être de votre côté.
+
+## Ce que le déterminisme nous permet ensuite
+
+La sécurité mémoire fait les gros titres, mais ce n'est pas la seule propriété que ce design nous offre gratuitement. L'ownership et l'absence de garbage collector rendent aussi le runtime déterministe : pas de pause GC, pas de courbe de chauffe JIT, pas d'allocateur qui décide discrètement de compacter le heap sous charge. Donnez la même entrée au même binaire deux fois, il emprunte le même chemin les deux fois, avec les mêmes caractéristiques de timing. Ce n'est pas qu'une propriété de sécurité, c'est aussi une propriété de performance : le même déterminisme qui exclut les data races est ce qui garde la latence de queue prévisible sous charge.
+
+Ce déterminisme est aussi une fondation, pas juste un résultat, et il pointe vers deux choses qu'on veut voir grandir chez FerrisKey.
+
+La première, c'est le développement piloté par la simulation. Des systèmes comme FoundationDB et TigerBeetle font tourner toute leur suite de tests contre une horloge et un réseau simulés, en rejouant des séquences d'événements identiques au bit près tout en injectant des pannes (connexions coupées, écritures disque retardées, décalage d'horloge), et en reproduisant un bug rare à partir d'une seule seed plutôt qu'en chassant une CI capricieuse pendant une semaine. Ce genre de harnais ne fonctionne que si le programme n'a pas de non-déterminisme caché à combattre. On n'a pas encore construit ça pour FerrisKey, mais c'est une direction que Rust rend réellement atteignable, contrairement à un runtime où le timing du GC est une source de bruit de plus à simuler.
+
+La seconde, c'est la preuve formelle sur les invariants qui comptent le plus. Pas prouver tout le système correct, ce n'est pas un objectif réaliste pour un projet de cette taille, mais des affirmations précises et étroites qui valent la peine d'être vraiment prouvées plutôt que seulement testées : un token révoqué ne peut plus jamais être accepté, une frontière de realm ne peut jamais être franchie par un chemin de code qui aurait oublié de la vérifier. L'écosystème Rust a des outils de vérification construits exactement pour ça (Kani, Creusot, des crates à base de contrats), et ils sont praticables ici pour la même raison que le reste de cet article existe : le système de types exclut déjà l'aliasing et les mutations cachées qui rendent le raisonnement formel impraticable dans la plupart des autres langages. C'est sur notre feuille de route, pas en production, mais c'est un chemin bien plus court à parcourir depuis Rust que depuis un runtime où "qu'est-ce que ce pointeur peut aliaser" n'a pas de réponse propre.
+
+## Ce qu'on ne va pas prétendre
+
+Rien de tout ça n'est gratuit, et on ne va pas prétendre le contraire. La courbe d'apprentissage de Rust est réelle, en particulier pour des équipes habituées à un GC qui gère la mémoire à leur place. Le bassin de talents est plus petit que celui de Java ou de Go, même s'il grandit vite, et qu'il penche vers des ingénieurs qui sont allés chercher ces garanties activement. Les temps de compilation sont plus longs que le "on lance et on voit" d'un langage dynamique. Certaines parties de l'écosystème async sont encore plus jeunes que leurs équivalents JVM ou Node.
+
+On pense que ce compromis vaut la peine d'être fait pour exactement une catégorie de logiciel : celle qui se trouve dans le chemin critique de tout le reste, que les attaquants visent en premier, et qui doit être juste plus qu'elle ne doit être rapide à écrire le premier jour. Un IAM, c'est cette catégorie.
+
+## La partie que tout le monde a vraiment demandée
+
+Donc, retour au point de départ : oui, FerrisKey est rapide. Quelques mégaoctets au repos plutôt que des centaines. Un démarrage à froid en moins d'une seconde plutôt qu'une trentaine. Vous savez maintenant pourquoi ce n'est pas la partie intéressante. C'est l'effet de bord visible de tout ce qui précède : pas de GC à chauffer, pas de heap à faire grossir, un binaire qui prouve entièrement ce qu'il prétend ou qui ne compile pas du tout.
+
+La vitesse n'a jamais été l'argument. C'est juste ce qu'on peut mesurer de l'extérieur.
+
+---
+
+FerrisKey est open source, sous licence Apache 2.0, et le code dont on parle ici est public. Si vous voulez voir le vrai flow d'authentification, les ports réels, ou les features de mock en contexte, le repo est sur [GitHub](https://github.com/ferriskey/ferriskey). Clonez-le, lisez-le, contestez nos choix dans une issue. C'est tout l'intérêt de faire ça en public.
diff --git a/apps/blog/src/layouts/base.astro b/apps/blog/src/layouts/base.astro
index 5799ec7..a518c01 100644
--- a/apps/blog/src/layouts/base.astro
+++ b/apps/blog/src/layouts/base.astro
@@ -15,7 +15,9 @@ interface Props {
const { title, description = 'Explainer Blog', thumbnail, locale = 'en', locales = ['en'], localeSwitchUrls = {} } = Astro.props
const t = useTranslations(locale)
-const thumbnailUrl = thumbnail ?? `${Astro.url.pathname.replace(/\/$/, '')}/thumbnail.png`
+const thumbnailPath = thumbnail ?? `${Astro.url.pathname.replace(/\/$/, '')}/thumbnail.png`
+// og:image/twitter:image must be absolute — Discord and most link-preview bots won't resolve a relative path.
+const thumbnailUrl = Astro.site ? new URL(thumbnailPath, Astro.site).toString() : thumbnailPath
const appUrlOverrides = {
website: import.meta.env.PUBLIC_WEBSITE_URL,
diff --git a/apps/docs/public/sponsors/anthropic-dark.svg b/apps/docs/public/sponsors/anthropic-dark.svg
new file mode 100644
index 0000000..cc8b904
--- /dev/null
+++ b/apps/docs/public/sponsors/anthropic-dark.svg
@@ -0,0 +1 @@
+
diff --git a/apps/docs/public/sponsors/anthropic-light.svg b/apps/docs/public/sponsors/anthropic-light.svg
new file mode 100644
index 0000000..983bfc4
--- /dev/null
+++ b/apps/docs/public/sponsors/anthropic-light.svg
@@ -0,0 +1 @@
+
diff --git a/apps/docs/public/sponsors/kong.svg b/apps/docs/public/sponsors/kong.svg
new file mode 100644
index 0000000..6247a00
--- /dev/null
+++ b/apps/docs/public/sponsors/kong.svg
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/apps/docs/public/sponsors/mestier.svg b/apps/docs/public/sponsors/mestier.svg
new file mode 100644
index 0000000..176211b
--- /dev/null
+++ b/apps/docs/public/sponsors/mestier.svg
@@ -0,0 +1,9 @@
+
diff --git a/apps/docs/src/layouts/docs.astro b/apps/docs/src/layouts/docs.astro
index fd3920f..563d21c 100644
--- a/apps/docs/src/layouts/docs.astro
+++ b/apps/docs/src/layouts/docs.astro
@@ -54,7 +54,11 @@ const contributors = sortContributors(
)
const showTabs = projects.length > 1
-const thumbnailUrl = '/assets' + (thumbnail ?? `${Astro.url.pathname.replace(/\/$/, '')}/thumbnail.png`)
+// Thumbnails are served from the site root (dist/**/thumbnail.png), not under /assets — and
+// og:image/twitter:image must be absolute, since Discord and most link-preview bots won't
+// resolve a relative path.
+const thumbnailPath = thumbnail ?? `${Astro.url.pathname.replace(/\/$/, '')}/thumbnail.png`
+const thumbnailUrl = Astro.site ? new URL(thumbnailPath, Astro.site).toString() : thumbnailPath
const appUrlOverrides = {
website: import.meta.env.PUBLIC_WEBSITE_URL,
diff --git a/apps/website/src/layouts/base.astro b/apps/website/src/layouts/base.astro
index 87ddfd4..0ea11bf 100644
--- a/apps/website/src/layouts/base.astro
+++ b/apps/website/src/layouts/base.astro
@@ -9,7 +9,9 @@ interface Props {
}
const { title, description = 'Explainer v2', thumbnail } = Astro.props
-const thumbnailUrl = thumbnail ?? '/thumbnail.png'
+const thumbnailPath = thumbnail ?? '/thumbnail.png'
+// og:image/twitter:image must be absolute — Discord and most link-preview bots won't resolve a relative path.
+const thumbnailUrl = Astro.site ? new URL(thumbnailPath, Astro.site).toString() : thumbnailPath
---
diff --git a/apps/website/src/pages/index.astro b/apps/website/src/pages/index.astro
index e2d6761..316aefe 100644
--- a/apps/website/src/pages/index.astro
+++ b/apps/website/src/pages/index.astro
@@ -27,7 +27,7 @@ const translations = ui.fr
const thumbnails = { en: '/thumbnail.png', fr: '/thumbnails/fr/thumbnail.png' }
---
-
+