diff --git a/e2e/blog.spec.ts b/e2e/blog.spec.ts
index 7b2fd08..26c7dae 100644
--- a/e2e/blog.spec.ts
+++ b/e2e/blog.spec.ts
@@ -98,22 +98,25 @@ for (const skin of ["standard", "terminal"] as const)
[]
);
if (path.includes(introSlug)) {
- await expect(page.locator(".product-screens-hero img")).toHaveCount(
- 1
- );
+ await expect(page.locator(".product-screens-hero")).toHaveCount(0);
await expect(page.locator(".product-screens-row img")).toHaveCount(
- 2
+ 3
);
await expect(page.locator(".product-screens-row")).toHaveCSS(
"overflow-x",
"auto"
);
const stack = page.locator(".mobile-stack");
- await expect(stack.getByRole("img")).toBeVisible();
- await expect(stack.getByRole("region")).toHaveCSS(
- "overflow-x",
- "auto"
- );
+ const appwrite = stack.getByText("Appwrite", { exact: true });
+ await appwrite.scrollIntoViewIfNeeded();
+ await expect(appwrite).toBeVisible();
+ if (width < 700) {
+ const box = await appwrite.boundingBox();
+ expect(box?.x).toBeGreaterThanOrEqual(-1);
+ expect((box?.x ?? 0) + (box?.width ?? 0)).toBeLessThanOrEqual(
+ width + 1
+ );
+ }
}
if (path.includes(architectureSlug)) {
// Diagram geometry must survive missing component CSS.
diff --git a/src/components/Blog/Blog.test.tsx b/src/components/Blog/Blog.test.tsx
index 584ef23..4df2f87 100644
--- a/src/components/Blog/Blog.test.tsx
+++ b/src/components/Blog/Blog.test.tsx
@@ -69,18 +69,16 @@ test("mobile stack and sample screens stay in the article", () => {
const { container } = render(
<>
-
>
);
expect(
- screen.getByRole("img", { name: /Car Brain mobile stack/ })
+ screen.getByRole("figure", { name: /Car Brain mobile stack/ })
).toBeVisible();
- expect(container.querySelectorAll(".product-screens-hero img")).toHaveLength(
- 1
- );
+ expect(screen.getByText("Appwrite")).toBeVisible();
+ expect(container.querySelector(".product-screens-hero")).toBeNull();
expect(container.querySelectorAll(".product-screens-row img")).toHaveLength(
- 2
+ 3
);
});
for (const locale of ["en", "pl"] as const) {
diff --git a/src/components/Blog/MobileStack.tsx b/src/components/Blog/MobileStack.tsx
index e29bd74..d960d6d 100644
--- a/src/components/Blog/MobileStack.tsx
+++ b/src/components/Blog/MobileStack.tsx
@@ -1,140 +1,56 @@
-import { useId } from "react";
import type { BlogLocale } from "@/content/blog/types";
-const ink = "var(--text, #1c282c)";
-const muted = "var(--text-secondary, #52646a)";
-const accent = "var(--accent, #0f6b75)";
-const surface = "var(--surface, #ffffff)";
-const canvas = "var(--canvas, #f7f5f0)";
-const soft = "var(--accent-soft, #e2efee)";
-const border = "var(--separator, #d4deda)";
-
export function MobileStack({ locale = "en" }: { locale?: BlogLocale }) {
const pl = locale === "pl";
- const id = useId();
const title = pl ? "Stos mobilny Car Brain" : "Car Brain mobile stack";
- const description = pl
- ? "Aplikacja Expo na iOS i Androida rozmawia z Appwrite: logowanie, tabele, pliki i funkcje. W oryginalnej aplikacji nie ma osobnego serwera API."
- : "The Expo app on iOS and Android talks to Appwrite for auth, tables, files and functions. There is no separate API server in the original app.";
return (
-
-
-
diff --git a/src/components/Blog/blog.css b/src/components/Blog/blog.css
index 357bc1c..57c40fc 100644
--- a/src/components/Blog/blog.css
+++ b/src/components/Blog/blog.css
@@ -114,12 +114,9 @@
margin: 32px 0;
min-width: 0;
}
-.product-screens-hero {
- display: flex;
- justify-content: center;
-}
.product-screens-row {
display: flex;
+ justify-content: safe center;
gap: 12px;
overflow-x: auto;
min-width: 0;
@@ -143,6 +140,83 @@
font-size: 14px;
line-height: 1.45;
}
+.mobile-stack {
+ margin: 32px 0;
+ min-width: 0;
+}
+.mobile-stack-layout {
+ display: grid;
+ grid-template-columns: minmax(160px, 230px) auto minmax(0, 1fr);
+ align-items: stretch;
+ gap: 16px;
+ padding: 28px;
+ border: 1px solid var(--separator);
+ border-radius: var(--radius-surface, 16px);
+ background: var(--surface);
+}
+.mobile-stack-node {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ margin: 0;
+ padding: 24px 16px;
+ border-radius: 12px;
+ text-align: center;
+}
+.mobile-stack-node p {
+ margin: 0;
+ font-size: 15px;
+ line-height: 1.4;
+}
+.mobile-stack-title {
+ font-size: 18px;
+ font-weight: 650;
+}
+.mobile-stack-expo {
+ background: var(--canvas);
+ border: 1.5px solid var(--text-secondary);
+ color: var(--text);
+}
+.mobile-stack-appwrite {
+ background: var(--accent-soft);
+ border: 2px solid var(--accent);
+ color: var(--accent);
+}
+/* Terminal light mixes a 10% green tint. Accent text on that tint is just
+ under the 4.5:1 minimum, so the words use the text color. The border
+ still marks the box. */
+html.skin-terminal:not(.dark-theme) .mobile-stack-appwrite {
+ color: var(--text);
+}
+.mobile-stack-muted {
+ color: var(--text-secondary);
+}
+.mobile-stack-arrow {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+}
+.mobile-stack-sdk {
+ color: var(--text-secondary);
+ font-size: 13px;
+ line-height: 1;
+}
+.mobile-stack-arrow-icon {
+ display: block;
+ color: var(--accent);
+}
+@media (max-width: 700px) {
+ .mobile-stack-layout {
+ grid-template-columns: 1fr;
+ }
+ .mobile-stack-arrow-icon {
+ transform: rotate(90deg);
+ }
+}
@media (prefers-reduced-motion: reduce) {
.blog-list-item {
transition: none;
diff --git a/src/content/blog/articles/why-i-built-car-brain/en.mdx b/src/content/blog/articles/why-i-built-car-brain/en.mdx
index 0365b4c..8d441de 100644
--- a/src/content/blog/articles/why-i-built-car-brain/en.mdx
+++ b/src/content/blog/articles/why-i-built-car-brain/en.mdx
@@ -5,7 +5,9 @@ import { ProductScreens } from "@/components/Blog/ProductScreens";
Fuel, service, costs and receipts were scattered. A spreadsheet is a poor filing cabinet for photos. I also wanted to learn mobile by shipping a whole product, not by finishing a tutorial. Car Brain is that product: React Native for iOS and Android, built in 2024–2025, which I am bringing back now.
-
+
+
+The numbers and the car on these screens are sample data.
## What I chose, and the bill
@@ -61,10 +63,6 @@ The mobile source covers several vehicles on one account, photos, VIN auto-fill,
The public page is [car-brain.com/en](https://car-brain.com/en). That is the landing page. It is not a store listing.
-
-
-The numbers and the car on these screens are sample data.
-
Not in this product: subscriptions, a shared family account, push notifications, calendar sync, or OCR on receipts.
**Development snapshot — 6 October 2026:** the feature list comes from the repository and its decision records. These screens are existing design captures. I did not re-run the app while writing this. Car Brain is coming back to the stores. This article does not claim the listing is live.
diff --git a/src/content/blog/articles/why-i-built-car-brain/pl.mdx b/src/content/blog/articles/why-i-built-car-brain/pl.mdx
index 456556f..76a0a51 100644
--- a/src/content/blog/articles/why-i-built-car-brain/pl.mdx
+++ b/src/content/blog/articles/why-i-built-car-brain/pl.mdx
@@ -5,7 +5,9 @@ import { ProductScreens } from "@/components/Blog/ProductScreens";
Paliwo, serwis, koszty i paragony miałem porozrzucane. Arkusz kalkulacyjny słabo nadaje się do trzymania zdjęć. Chciałem też nauczyć się aplikacji mobilnych na prawdziwym produkcie, a nie na kolejnym tutorialu. Car Brain jest właśnie tą aplikacją: React Native na iOS i Androida. Zbudowałem ją w latach 2024–2025 i teraz do niej wracam.
-
+
+
+Liczby i auto na tych ekranach to dane przykładowe.
## Co wybrałem i ile mnie to kosztuje
@@ -61,10 +63,6 @@ W aplikacji jest kilka aut na jednym koncie, zdjęcia, uzupełnianie VIN, daty u
Publiczna strona po polsku jest na [car-brain.com/pl](https://car-brain.com/pl). To landing page, nie ogłoszenie, że aplikacja jest w sklepie.
-
-
-Liczby i auto na tych ekranach to dane przykładowe.
-
Nie ma tu subskrypcji, wspólnego konta rodzinnego, powiadomień push, synchronizacji z kalendarzem ani OCR paragonów.
**Stan na 6 października 2026:** lista funkcji pochodzi z repozytorium i zapisanych decyzji. Te ekrany to zdjęcia zrobione wcześniej do portfolio. Dane na nich są przykładowe. Przy pisaniu nie uruchamiałem aplikacji. Car Brain wraca do sklepów. Nie piszę, że jest już w sklepie.