Why my portfolio has a Garden OS mode
Most developer portfolios are a hero, three project cards, and a contact link. I wanted to ask: what if the portfolio itself was a small product? Here is why I built a fake operating system on top of mine, and the three engineering decisions worth defending.
Why a portfolio needs an OS mode
Most developer portfolios are a hero, three project cards, an about paragraph, and a contact link. That format works. It also disappears into the average within ten seconds of landing on the page.
When I rebuilt my portfolio this spring, I asked a different question: what would it feel like if the portfolio itself was a product? Not a brochure. Not a CV. A product the visitor can poke at and discover things in.
The answer is what I now call Garden OS — a hidden second mode of the same site that turns the page into a fake desktop operating system. Same content, different envelope.
The two-mode trick
The site has two top-level experiences, switchable from the nav or the Display Options panel:
- Website mode — what you'd expect. Linear pages, garden illustrations, Fraunces serif headlines.
- Garden OS mode — a 1990s-meets-modern OS chrome. Menu bar at the top, draggable windows with macOS traffic-light controls, a desktop wallpaper, snap-to-edge tiling, and a desktop pet that wilts if you don't visit.
Both modes render the same routes internally. About page is About
page; project case studies are project case studies. The only thing
that changes is the chrome. In OS mode, opening "About" opens a
draggable window that loads /about inside an iframe. The window
remembers its position via react-rnd. The traffic lights work:
red closes, yellow minimises (the window stays mounted so iframe
state is preserved), green maximises.
This means all my content stays SEO-friendly. Search engines crawl the regular routes. Visitors who want a portfolio that feels like a small game get one. Visitors who want a portfolio that feels like a portfolio get one. No fork, no duplication.
Three engineering decisions worth defending
Why iframes for the OS-mode windows
The obvious alternative is to render the page content inline inside each window component — call the same React tree twice with different layout. I rejected that because:
- The OS chrome is not a layout primitive. It's a container metaphor. The window has its own scroll, its own focus, its own "tab" that should feel separate from the page hosting it.
- Iframes get window scope isolation for free. Click into a window's /projects iframe → focus stays in that iframe → keyboard shortcuts for back/forward navigate inside it without nuking the OS state.
- Routing-as-iframe means the same
/projects/tricomhubURL works in both modes. Bookmarking inside Garden OS mode just opens the route directly in website mode (because the iframe target is the real route).
The trade-off is real: an iframe means a second React mount per window, which is more memory than a single-tree shared render. For a portfolio with at most 5 windows open at once, that's a non-issue.
Why CSS Modules instead of Tailwind
The earlier Once UI scaffold I wiped used Tailwind utility classes everywhere. The Garden OS rebuild went CSS Modules + CSS custom properties. Two reasons:
- The OS chrome has very specific custom geometry — traffic
lights, glassmorphism panels, snap-preview rectangles, drag-handle
chevrons. Half my CSS is bespoke. Authoring those as one-off
utility classes inside a className string makes them harder to
read than a focused
.windowTitleBar { … }rule. - Theme variables travel cleanly across modes. Both website mode
and OS mode read the same
--color-accent(garden emerald,#00c781). Themes can mutate at runtime viadata-theme="dark"on<html>without touching component code.
Tailwind is great for speed; for a site whose entire identity is its look, paying upfront for ownership of the CSS pays back fast.
Why the menubar clock reads the real time
The menu bar in Garden OS shows a live clock, battery, wifi, and
cellular indicator — all driven by real browser APIs (Date,
navigator.getBattery(), navigator.connection, navigator.onLine).
The temptation was to fake them. A static "12:34 PM" + a hardcoded battery icon would have been a fraction of the code. Fake instruments are the uncanny valley of UI: as soon as you notice they're fake, the whole metaphor breaks. So the clock ticks every second, the battery percentage matches your actual device, and the wifi indicator goes red when you go offline.
Trade-off: my battery code has to handle browsers (Safari) that
don't expose getBattery(). Falls back to a 0.85 mock with a comment.
That's the price of consistency.
What surprised me
How much of "feeling native" was the animation transitions, not the static rendering. Early versions had perfect window chrome but the open / close / minimise transitions were instant — the whole thing felt jarringly snappy in a way that reads as broken on a desktop OS.
I burned a full session debugging window-drag jitter that turned out
to be Framer Motion fighting with react-rnd's transforms. The fix
was separating the concerns: opacity-only Framer for entry/exit, CSS
class transitions for minimise, no transforms on the wrapper. Once I
stopped trying to do everything with one tool, the animations became
buttery — and the OS mode finally read as a real OS instead of a
costume.
Should you build one for your portfolio?
Probably not. The Garden OS metaphor only earns its keep because this site is also where I keep my digital garden — the long-form notes you're reading right now. The "OS hosts apps" mental model fits a site that hosts both my projects and my writing. For a static freelance brochure, two pages and a CTA still beats a clever idea.
If your portfolio is also where you experiment, where you cultivate, where the tool is the message — then yes, an OS mode is one of the most fun things you can build, and it'll teach you more about animation, state isolation, and visual metaphor than any tutorial.
The Garden OS mode lives under src/components/GardenOS/. The design
system is documented under .ai/design-guide/.