A website checked frame by frame
A veterinary practice's new website. The interface is in place and the copy is being finalised — this page describes where it stood in October 2026.
- The situation
- You run a veterinary practice and want a new website. You have already put the structure together in an outline, you would leave the design to someone else — and while the new one is built, the old site has to keep working.
- What I have built so far
- A custom theme on the outline's structure, with no plugins, in two languages. I measured loading, motion and the phone layout frame by frame, in two browser engines, and treated an old iPhone as a target device too.
- Where it stands
- The new site is built on a staging address, and by 4 October 2026, twenty-one versions had gone out there. The copy is being finalised, with avatars standing in for the team photos until then; the old site keeps running meanwhile.
The work in numbers
| Collaboration started | August 2026 — a quote request with a structural outline |
|---|---|
| Status | in progress — on a staging address, copy being finalised |
| Languages | 2 — Hungarian and English |
| Service subpages | 14 |
| Versions released to staging | 21, by 4 October 2026 |
| Test suite | 37 items (October 2026), the browser checks in two engines |
| Counter-tests | 98, each reverting one safeguard (October 2026) |
- Project
- veterinary practice
- Role
- Design on the client's outline, custom theme, motion and loading, phone layout, releases
- Engagement
- Contracted build, in feedback rounds
- What I take on
- A custom website, with loading measured frame by frame.kristof@kristofkarner.com
The outline we started from
The request for a quote for the veterinary practice's new website arrived in August 2026, with a slide-deck outline of the structure: which pages there should be and what goes on them. The client left the design to me. Later, nine other veterinary sites arrived as references, some with a particular part highlighted.
From the nine I took three things: the team's personal introductions, a clear page structure, and a dropdown menu for the services. What became unique is the live opening status: the site shows whether the practice is open right now, which none of the nine did.
The copy came from the client's material — the outline, the old site and passages marked up and sent later — and the English version is a translation of it. Where something was still missing, a placeholder stands until the final version arrives: avatars in place of the team photos, for instance, at the client's request.
The old site kept running throughout
From day one the veterinary practice's new website was built on a staging address, kept apart from the old one; the old site kept running on the main address — the client asked for this at the start. After every round of feedback the new version went out there, so the practice looked at the real, clickable site in every round.
By 4 October 2026, twenty-one such versions had gone out. Before every release the staging site is backed up, and the release only continues with an intact backup. Afterwards an automated check runs: the pages and every file they reference, the security headers, and finally, file by file, whether the server holds exactly what the local machine does.
Refinement, measured
On the practice's new website my bar was that nothing should pop in or jump while loading, and that the site should behave the same in every browser. I did not check this by eye: a machine loads the page and records, frame by frame, what is visible of it.
An example: in one version the content sharpened out of a blur while loading, yet it still felt like a pop. The frames showed why: the text became 84 per cent visible within 80 milliseconds, and the blur only cleared after that. In the version of 4 October 2026 the page settles first — the header, the section backgrounds, the large image — and the content follows a beat later, softly.
Frame-by-frame measurement also found that during page changes the icons stood empty for 60–130 milliseconds, that on a first visit the menu items jumped 7–15 pixels when the fonts arrived, and that the opening image faded for a moment at the end of loading. To the eye each was only a brief jolt; what caused it came from the frames.
What this means if you are in a similar position
If you want a new website, you can put the structure together yourself, even as a slide deck, and leave the design and the build to me. Your old site keeps working meanwhile, and you can review every round on the real site, on your own phone too.
As for refinement, I look at what the visitor sees: what appears when the page loads and when — measured frame by frame — and whether it works the same way on an older phone too.
If you want a new website and already have an idea of its structure, write a few lines about what you have now and what you would like. One exchange of emails usually shows how much work it is.
kristof@kristofkarner.comFrom here on, the details: how I measure page loading frame by frame, how I check the tests themselves, what an old iPhone means as a target device, and a few bugs only the measurement revealed.
The size of the build
| Custom theme | 53 PHP files, 9,348 lines |
|---|---|
| Hand-written CSS and JavaScript | 3,065 lines of CSS, 1,822 lines of JavaScript |
| Copy | kept apart from the code, in two JSON sets, Hungarian and English |
| Plugins | none |
| Test suite | 37 items — 36 files, 7,975 lines (October 2026) |
| Counter-tests | 98 rows with 118 replacements (October 2026) |
$ find . -name "*.php" | wc -l # in the theme folder 53 $ grep -c '^\s*fut "' teszt/mind.sh # in the project root 37 $ python3 teszt/ellenproba.py --horgonyok Ellenpróba-horgonyok: mind a 118 egyértelmű.
One page load, frame by frame
On this project the page load is examined by a measuring script of my own. A machine loads the page on a throttled network with an empty cache, in the engines of Chrome and Safari (Chromium and WebKit), at desktop and phone sizes. For the first 3.5 seconds it looks at the elements on screen on every frame — images, icons, headings, paragraphs, buttons — and records how visible they are and where they stand.
It counts as a failure if anything appears within a single frame with no fade, if a visible element jumps more than three pixels, or if an element of the page turns up ready-made after the first paint. The measurement runs separately for the first visit and for page changes, because the browser's cache holds different things in the two cases.
The measuring script checks itself too: when it deliberately switches off the header's hiding on the first visit, it has to report the jump of the font swap. If it does not, the measurement is blind, and the run fails.
- 01 Slow network, empty cache two engines, desktop and phone
- 02 Every frame, every element opacity, position, image, icon
- 03 Fail: if it pops in within one frame, with no fade
- 04 Fail: if it jumps over 3 pixels, while visible
- 05 Self-check with a guard removed, it must report
first visit and page change apart · five pages · two engines
The test I break on purpose
On this project the counter-test script removes each safeguard from the source, one at a time and temporarily, runs the test that belongs to it, and expects it to fail. If the test stays green even then, it is not measuring what I meant it to. After the run the source is restored byte for byte, and the script checks that too.
By October 2026 there were 98 such rows, with 118 replacements. On 3 October 2026 it happened three times in a single day that a row went stale without a sound: the code changed underneath, and the place of the replacement was gone. The full run only stopped at the broken row, and the test suite did not see it at all. Since then, the first step of the suite checks the place of all 118 replacements, in seconds.
For the inline script in the page head this takes more steps: the browser only runs it if its fingerprint (hash) is listed in the security policy, so reverting it means swapping the fingerprint too. On 2 October 2026 the head changed five times in a single day, and with it, each time, five fingerprints computed by hand. Since then the counter-test computes the fingerprint at run time.
An iOS 15 iPhone as a target device
On this website one of the target devices was an iPhone 6s on iOS 15. That older
Safari does not know several parts of current CSS — subgrid or the lh unit, for
example — so wherever I used them, a fallback stands before them, and the layout
stays the same.
This is also why the transition between pages is a script of my own. The browsers' built-in solution (View Transitions) is unknown to iOS 15 and Firefox, while the bar was that it should work the same in every browser. The old page's content fades within 140 milliseconds, the header stays put, and the new page's content sharpens.
The security policy, measured live
The veterinary website's pages run under a full Content Security Policy: no foreign script can run, and the site's own inline scripts only with their fingerprints. Since 24 September 2026, after every release a checker walks through the staging site in both browser engines, and verifies with a deliberately injected foreign script that the block really holds.
What is not obvious
The blur that did not prevent a pop-in
In one version of the load-time sharpening the text came out of a blur, yet it felt like a pop. The cause was the curve: opacity reached 45 per cent within 30 milliseconds and 84 within 80, and the blur only cleared after that. In the corrected version the opacity starts gently — at 100 milliseconds it is at about 40 per cent — and the blur clears along with it.
The menu the animation had no effect on
In the desktop view the menu's container is set to display: contents, so it has
no box of its own. The entrance animation placed on it therefore did nothing:
the menu jumped in at once while the logo and the buttons sharpened. Since the
fix the container's children get the animation, and the measuring script does
not count the animation of such an element as a fade.
The hidden icon set that took up 150 pixels
The icons come from a single hidden SVG set. The hidden attribute hid nothing on
this element: the browser's rule for it applies to HTML elements, not to SVG. So
the set left a 150-pixel empty strip at the bottom of every page. Since then
display: none hides it, and the measurement checks the bottom of the page too.
Safari computed it but did not repaint
The button fills were first built on the transition of a registered CSS variable
(@property), with the background or the mask drawn from it. Safari's engine ran
the variable's transition but did not refresh the painting built on it, so the
buttons, the team cards and the fading of the reviews jumped to their end within
a single frame. Meanwhile the computed value showed that everything was fine.
Since then the fills run on the browser's own animatable properties — size,
clipping, mask size — and I look at motion on WebKit frames too, not at the
computed value.
The image that faded after loading
On a slow network the home page's opening image faded in after loading, then
faded for a moment from 0.99 to 0.11 opacity, and reappeared: the image
rotator's own opacity transition restarted on load. Since the fix the image is
invisible rather than transparent until it loads (visibility), so the rotator's
transition never starts, and the image fades in once, evenly.
Questions about this project
How do you check that a website works the same on every phone and browser?
By measuring it. On this project a machine loads every important view in two browser engines — those of Chrome and Safari — at desktop and phone sizes, zoomed in and on a slow network too. By October 2026 the test suite had 37 items, and a full run takes about three quarters of an hour. I also look at it on an iOS 15 iPhone, because that older Safari does not know some newer CSS features, and there they need a fallback.
Why do images or icons pop in while a page loads, and what can be done about it?
Most often because they arrive later than the text, and at the moment they load they appear within a single frame. On this site an image stays invisible until it has loaded, then fades in over 400 milliseconds; anything already in the cache appears at once. The icons had a different cause: their drawings stood at the very end of the page, and during page changes they showed empty for 60–130 milliseconds, so the drawings moved to the start of the page. Frame-by-frame measurement revealed both, and since then the test suite watches for them on every page.
What does it mean that you test the tests?
It means I revert each safeguard in the source code, one at a time and temporarily, run the test that belongs to it, and expect it to fail. If it stays green, the test is not measuring what I meant it to. On this project there were 98 such counter-tests by October 2026; after a run the source is restored byte for byte, and the script checks that too. Because a counter-test can go stale without a sound when the code changes underneath it, the first step of the full suite checks the place of all 118 replacements automatically.
Can a new website work on an old iPhone?
Yes, if the old browser is a target device from the design stage on. On this project an iPhone 6s on iOS 15 was a target device. That older Safari does not know subgrid or the lh unit, for example, so wherever I used them, a fallback stands before them, and the layout stays the same. This is also why I wrote the page transition as a script of my own: the browsers' built-in solution is unknown to iOS 15 and Firefox.
Does the old website have to go offline while the new one is built?
No. On this project the new site is built on a staging address, kept apart from the old one, while the old one keeps running on the main address. After every round of feedback the new version goes out to the staging address — twenty-one by 4 October 2026 — so the client looks at the real site in every round. The switch comes when the copy is final.
Summary
A veterinary practice's new website has been in the works since August 2026. The client asked for a quote with a slide-deck outline of the structure and nine reference sites, left the design to me, and asked that the old site keep running meanwhile; so the new one is built on a staging address, where twenty-one versions had gone out by 4 October 2026. What is being built is a custom theme with no plugins: 53 PHP files, 9,348 lines, with the copy kept apart from the code, in two languages. The bar was that nothing should pop in or jump while loading, the same in every browser; frame-by-frame measurement guards this, separately for the first visit and for page changes, in the Chromium and WebKit engines, and an iOS 15 iPhone is a target device too. By October 2026 the test suite had 37 items, and 98 counter-tests check that the tests really measure: each reverts one safeguard and expects the test to fail. The measurement found the sharpening that rose too fast, the icons that stood empty during page changes, the menu that jumped in at once because of display: contents, and the opening image that faded after loading.