One site, two directions, three motion tiers: how I built idanach.com
How my portfolio flies letters into the logo on a strong laptop, stands still for anyone who asked for less motion, and reads the same in Hebrew.
My portfolio opens with my name inside a slowly turning orbit of project clips. As you scroll, the letters of the name fly up into the logo in the header. On a strong laptop it is the most animated page I have built. For anyone who has asked their device for less motion, the same page stands perfectly still. In Hebrew, it is the same page again, read from right to left, with the letters landing in the logo on the other side.
This post is about how one page does all of that, and what I had to give up to keep it honest.
The problem
I want employers to know me as the system builder who is also good at design. A portfolio is the one place where I can show both halves at once, so it had to be unusual and carefully designed. But “unusual” is where portfolios often go wrong:
- Search engines and AI assistants have to read it. A page that draws its text with script, or hides it until an animation ends, tells them nothing.
- Two languages, two directions. English at
/, Hebrew at/he/. Hebrew is not a translation pasted into the same layout; it reads the other way, and every arrow, margin and animation has to know that. - Not every device is a strong laptop. A phone on a slow network, an old computer, or a visitor with reduce-motion switched on must get a complete page, not a broken one.
- It has to be fast, because a slow portfolio says something too.
What I tried first
My first idea for the hero was a portrait of me drawn in particles. It looked good, and it was too close to a portfolio I admired. A personal site that borrows someone else’s signature isn’t personal, so I dropped it.
The direction that stuck came from a scroll I liked on getartcraft.com: tiles orbiting around a title, letters flying into the logo, and a chapter index with a ruler. I built my own version of it as a prototype, with my real project screens, five-second loops from my own demo videos and a colour cover for every project. Only once I was happy with the prototype did the real build start. The site’s tests check its main pages at seven screen widths, from 1920 down to 360 pixels.
What worked
Content first
The site is built with Astro as plain static HTML. Every word, in both languages, is in the page before any script runs. The motion is added on top and never carries content.
The orbit is a good example. The page’s <h1> is always my real name, as one ordinary heading. The flying letters are
a decorative copy, hidden from screen readers with aria-hidden. If the script never runs, you still get the name, the
line under it and every project.
Three motion tiers, decided before the first paint
A small script inlined in the <head> runs before anything is drawn and picks a tier:
function detect() {
if (mm('(prefers-reduced-motion: reduce)')) return ['still', 'reduce'];
var c = navigator.connection || {};
if (c.saveData) return ['light', 'save-data'];
if (/(^|-)2g|3g/.test(c.effectiveType || '')) return ['light', 'slow-network'];
if ((navigator.deviceMemory || 8) <= 2) return ['light', 'low-memory'];
if ((navigator.hardwareConcurrency || 8) <= 2) return ['light', 'few-cores'];
if (get('sessionStorage', 'idanach-lowfps') === '1') return ['light', 'low-fps'];
var g = gpu(); // no WebGL, or a software renderer
if (g !== 'ok') return ['light', g];
return ['full', 'capable'];
}
- Full gets the orbit with live clips, mouse tilt, the letter flight, smooth scrolling (not on touch screens), the pinned reel and page-to-page image transitions.
- Light gets the orbit with still posters, simple reveals and normal scrolling, and no video plays by itself. A device that runs the first seconds under 40 frames a second drops to light by itself.
- Still gets no movement at all. The orbit becomes a static collage, and everything is readable.
The result goes on the <html> element before the first paint, so there is no flash of the wrong version. A small
“Motion: auto / full / light / off” switch in the footer (in the menu on a phone) lets you override it, and the site
remembers your choice. The content is identical in every tier; only the movement changes.
One stylesheet, two directions
The layout uses CSS logical properties throughout: margin-inline-start instead of margin-left,
inset-inline-end instead of right. One stylesheet serves both directions, and the Hebrew pages mirror themselves
without a second set of rules. The few things that are not layout, I handle by hand: in Hebrew the project reel moves
the other way, and the letter flight lands in the logo on the right.
Typography needed its own answer. English pages use Schibsted Grotesk, with Instrument Serif italic for accent words. Hebrew has no italic, so Hebrew pages use Heebo, and accent words switch to Gveret Levin, a handwritten Hebrew font. In both languages the accent colour carries the emphasis, so the design says the same thing in either script.
The fonts are self-hosted, and most are cut into subsets with unicode-range, so a browser downloads only the letter
sets a page actually uses. Gveret Levin is the exception: its licence doesn’t allow changing the file, so it is served
exactly as shipped, and unicode-range only limits it to Hebrew letters. Only the main font is preloaded.
Page transitions, only where they help
Clicking a project card on the home page grows its image into the hero of the project page, using cross-document View Transitions. Two rules keep it from going wrong. It runs only in the full tier. And because one project appears several times on the home page, a transition name is given only to the image you clicked and its match on the next page. Browsers without View Transitions simply load the next page.
Telling search engines who I am
Every page has a self-referencing canonical address and reciprocal hreflang links between its English and Hebrew
versions. The structured data describes one person, with my Hebrew name, עידן אחרק, as an alternate name. Share images
are rendered in a real browser, and the renderer checks that the Hebrew font is really drawing the Hebrew letters
before it takes the picture, so a shared Hebrew link never shows empty boxes.
The trap: a test that lied about speed
The automatic drop from full to light motion worked a little too well. In the emulated browser I used for checks, the first seconds ran under 40 frames a second, so the site quietly switched itself to the light tier, and I was testing a version real visitors on a laptop would never see. Headless Chrome, which speed tools use, does the same.
The fix was to make the tier something a test can choose. A ?motion=full address forces a tier for one page load
without storing it, and the speed checks run twice: once forced to full, once in light.
What it measured
I measured the live site with Lighthouse’s mobile test, which simulates a mid-range phone on a slow 4G connection. For Home, About and one case study, in English and Hebrew, in the full and light tiers:
- Largest Contentful Paint: 2.8 to 3.3 s (budget: 3.5 s or less)
- Cumulative Layout Shift: 0 on every page (budget: 0.1 or less)
- JavaScript on Home: 92 KB in the full tier, 44 KB in the light one (budget: under 150 KB)
One number I already have: the images. They start as full-size PNG and JPG files, and Astro’s image pipeline turns them into WebP at the widths each page needs. Astro still copies every original into the build, so a build step deletes each image no page refers to: 348 files, about 90 MB. That took the built site from about 170 MB to about 80 MB.
What I would do differently
I would build the tier switch for tests before the first animation, not after a test misled me. Any feature that adapts to the device needs a way to pin it, or every measurement is a measurement of the test machine.
Who made what
I built this site with Claude Code, under my direction. The art direction, the right-to-left mirroring, the motion tiers and the decision that every image and clip comes from my own projects are mine. The About page has a short colophon with the rest.
Let visitors break my demos: an undo journal that resets them three hours later