lessisamor.tv · UI/UX + performance

The 102-finding audit.

A deep audit of the site across nine lenses — typography, spacing, motion, performance and more — run by 51 agents and adversarially verified before anything was called real. Every fix was checked before it shipped.

102 findings9 high23 medium70 low9 lenses

motion13

mediummotion

Reduced-motion gap: WebGL carousels, hero autoplay video, and smooth-scroll are completely uncovered

Evidence

revamp.css:242's blanket reduce rule literally annotates the gap: "a11y: respect reduced motion (does not touch the WebGL carousels)". grep for reduce/prefers-reduced across home-cgvfx-clean.js?v=22, home-photography-clean.js?v=32, home-explorations-clean.js?v=16 = 0 matches — idle auto-spin (FLOW_MIN=0.0012, cgvfx.js:553), jelly wobble (uAutoBulge, cgvfx.js:289), ambient sin-wave (cgvfx.js:179) and entry springs all run for vestibular-sensitive users. Hero <video autoplay muted loop> (home-rendered.html:1962) has no gating. revamp.js:166 scrollIntoView({behavior:'smooth'}) and customcss.css:663 html{scroll-behavior:smooth} have no reduce guard. Contrast: home-face-clean.js:22 + drawPlain(FRONT), badge spin (revamp.css:221) and hearts (revamp.css §27, line ~461) ARE handled — the coverage stops exactly at the showpiece animations.

Fix

In each carousel's boot, read matchMedia('(prefers-reduced-motion:reduce)') once: set FLOW_MIN/FLOW_AMP to 0 (cards hold still, drag still works), freeze uTime (kills ambient+jelly), and call the heading's enter(1) so text appears settled. Gate the hero video: if reduce, remove autoplay and show the poster (the modal play button still works). Wrap behavior:'smooth' in the same check. Default-theme visuals are untouched — but these are the fragile-trio files, so verify via the CDP/SwiftShader harness before deploy.

mediummotion

First viewport runs three unsynchronized infinite loops at 1.4s / 2.0s / 2.2s over an autoplaying video — polyrhythmic, reads busy not expensive

Evidence

Above the fold simultaneously: masthead heart double-beats at liaHeartbeat 1.4s to scale(1.28) !important (customcss.css:609-618, injected into the site title 'Less is Am♥r'); scroll-cue arrow bobs at scrollBob 2s (customcss.css:313); 'Show Reel' pulses at lia-heartbeat 2.2s scale(1.05) (revamp.css:763-764) — confirmed both .ja-btn-primary instances exist (hero 'Show Reel', contact 'Hit me Up') in home-rendered.html. Periods 1.4/2.0/2.2 never phase-align, so the eye gets a constant flicker of competing pulses, the loudest one (1.28 scale, fastest tempo) being chrome, not content.

Fix

Establish one pulse hierarchy: the primary CTA keeps the persistent 2.2s heartbeat (it's the conversion target); demote the masthead heart to a single beat every ~6s (JS-triggered class) or hover-only, and cap its scale at ~1.12 — a 28% scale jump every 1.4s forever is the most aggressive loop on the site; start the scroll-cue bob only after ~2s of no scroll. One rhythm = intentional; three = noisy.

mediummotion

Zero entrance choreography on HTML sections — WebGL sections spring in, everything else pops; page load is one 0.18s all-at-once fade

Evidence

The only load reveal is customcss.css:1549 body.ja-loaded > * { transition: opacity 0.18s ease-out } — after the FOUC black-hold (and a measured 6.0s FCP) the entire page fades in as one block in 180ms. No IntersectionObserver reveal classes or section-entrance keyframes exist anywhere in revamp.css/customcss.css (the only entrance keyframe is the mobile drawer's jaItemIn, customcss.css:1201-1208). Meanwhile the three carousel sections have a full scroll-linked spring entrance with per-letter stagger (stiffness=80/damping=22, cgvfx.js:257; entryX/entryY/entryScale easeBack, cgvfx.js:174-178). Result: alternating sections of choreographed motion and inert pop-in — the seam is visible every time you scroll from a carousel into Clients/About/Contact/footer.

Fix

Add one restrained shared reveal using the existing tokens: opacity 0→1 + translateY(12px)→0, .42s var(--ease), IO-triggered once at ~20% visibility, applied to .ja-clients inner, the .lia-abt text columns, .ja-cta block and footer (already reduce-guarded by the revamp.css:242 blanket). Optionally stagger hero H1 → sub → CTAs by 80ms on load so the 6s wait ends with a moment instead of a blink. Keep it to transform+opacity only — no scale/blur — so it stays Material-quiet.

mediummotion

Carousel idle drift, sweep timing and flick friction are frame-rate-dependent — 2x design speed on 120Hz displays

Evidence

The card-ring loop advances per FRAME, not per second: cgvfx.js:750-760 `sweepT++; scroll+=DIR*FLOW_MIN+DIR*sweepAmp*shape+flickVel; ... flickVel*=FRICTION` with FLOW_MIN=0.0012 rad/frame (cgvfx.js:553) and savorDur≈42 frames (cgvfx.js:705); identical pattern in photo.js:1332-1337 and expl.js. On a 120Hz MacBook/ProMotion screen the idle tempo and flick decay run exactly 2x the designed speed; on a thermally-throttled 30fps device, half. The same files DO use dt correctly for the heading spring (cgvfx.js:276-282 `velocity+=accel*dt`) — so the fix pattern already exists in-file. The hover-follow lerps (uMouseUV +=(target-cur)*0.20/frame, cgvfx.js:284-286) have the same dependency.

Fix

Normalize the per-frame increments by dt: compute k=dt/(1/60) and use scroll+=k*(DIR*FLOW_MIN+...), flickVel*=Math.pow(FRICTION,k), and convert sweepT/savorT to ms accumulators. This restores the hand-tuned 60fps tempo on every refresh rate. Fragile-trio files — verify with the SwiftShader CDP harness and a real 120Hz check.

lowmotion

Duration/easing palette scatter: 13 distinct durations, 6 easing families, and the primary CTA carries two competing transition specs

Evidence

motion-css.txt inventories transitions at .1/.12/.15/.18/.2/.22/.25/.28/.3/.32/.4/.42/.5s across var(--ease), ease, ease-out, ease-in-out, linear, and cubic-bezier(.22,.61,.36,1). The three overlay surfaces — functionally identical patterns — use three durations: reel modal .32s (revamp.css:689/704), contact modal .28s (revamp.css:790/793), theme switch-mask .22s (revamp.js:54). And .ja-btn-primary gets transition background-color .42s var(--ease) !important at revamp.css:185 ('synced with every other surface') then background-color .18s !important at revamp.css:501 (§hybrid-elevation) — the later rule wins, silently de-syncing the flagship buttons from the .42s theme fade and dropping `color` from the list entirely.

Fix

Codify a 3-step scale next to --ease in :root — --t-micro:.15s (icons, hovers), --t-small:.25s (knobs, rows), --t-surface:.42s (theme/bg/overlays) — and sweep motion-css.txt's outliers onto it. Unify both modals + switch-mask at one duration (.32s var(--ease) feels right for an overlay). Fix revamp.css:501: keep only box-shadow .18s there and let the §general .42s rule own background-color/color so the CTA rejoins the synced theme fade.

lowmotion

Hero overlay keeps a 0.3s CSS transition while parallax JS drives its opacity per frame — the documented #work2 'fighting pipelines' bug, still live in the hero

Evidence

parallax.js:16-18 writes bg.style.opacity, bg.style.transform AND overlay.style.opacity every scroll rAF. customcss.css:656-659 fixed exactly this for the bg layer (.ja-hero-bg { transition:none !important } under the header 'TRANSFORM + OPACITY ARE CONTROLLED BY JS') but line 660 left .ja-hero-overlay { transition: opacity 0.3s ease; } in place — every per-frame write restarts a 300ms tween, so the darkening overlay perpetually lags the instantly-tracking video layer during scroll. revamp.css §32 documents this same class of bug as the cause of the #work2 split ('A CSS transition here would fight those per-frame inline updates (lag)').

Fix

Add .ja-hero-overlay { transition: none !important; } alongside the existing .ja-hero-bg rule (or fold the overlay fade into the same JS write that owns it). One-line change, removes the scroll-linked luminance smear.

lowmotion

Carousel blur→sharp is a single-frame texture pop, not a dissolve — and it lands mid-rotation, seconds after the card is visible

Evidence

cgvfx.js:598-611: the baked LQIP is assigned to uTex, then when the full 768x1024 streams in it's hard-swapped in one statement (`card.__full=1; card.material.uniforms.uTex.value=tex`) — no blend. With origin TTFB ~1.4s/image (desktop-light.json waterfall) the pop happens while the card is already on screen and idle-drifting. The site's own mobile heading shows the intended standard: photo.js:108 hides its texture swaps 'behind a glass-blur dissolve'. The cards skip that.

Fix

Add a second sampler + uMixB uniform per card material; when the full texture lands, ramp uMixB 0→1 over ~350ms through the in-file EASE curve (cgvfx.js:30) instead of reassigning uTex. The blur then visibly 'focuses' — which reads as intentional progressive reveal rather than a loading artifact.

lowmotion

Heartbeat CTAs snap to scale(1) the instant you hover — animation:none mid-beat

Evidence

revamp.css:764-765: body .ja-btn-primary { animation: lia-heartbeat 2.2s ... infinite } then :hover,:active { animation:none; }. The keyframes peak at scale(1.05) at 10%/30% — mousing in during a beat discards the animation and the button jumps from 1.05 to 1.0 in one frame. On the most-hovered elements on the page (both primary CTAs), the first interaction touch is a jitter.

Fix

Move the pulse to an inner <span> (the button's own hover transform/shadow never conflicts), and on hover set the span's animation-play-state:paused plus a transform transition .15s var(--ease) to scale(1) — the beat settles instead of snapping. Pure CSS restructure, no JS.

lowmotion

Theme toggle: the scrolled header flips instantly while every other surface crossfades over .42s

Evidence

revamp.css:225-231 deliberately excludes background-color from the masthead transition ('header SOLIDIFY: bg snaps to white INSTANTLY') — correct for the scroll-solidify, but the same exclusion fires during the bulb toggle: html/body, sections, buttons, carousels all lerp .42s on --ease (revamp.css:27,224; cgvfx.js:27-30 mirrors the same curve), while a scrolled solid header + its nav text jump to the other theme in 1 frame, leaving a ~400ms mismatched band pinned to the top of the viewport.

Fix

In toggle() (revamp.js:79) add a body class e.g. lia-theming for 500ms; under body.lia-theming #masthead enable background-color .42s var(--ease), color .42s var(--ease). Scroll-solidify keeps its snap (class absent), the theme flip joins the synced fade.

lowmotion

Global html{scroll-behavior:smooth} contradicts the JS that assumes 'auto', animates keyboard paging, and slows the masked scroll-restore

Evidence

customcss.css:663 html { scroll-behavior: smooth; } is live (no override in revamp.css — grep confirms). Effects: (1) revamp.js:149-150's comment 'The window scroll stays default-auto so the theme-toggle scroll-restore is never smoothed' is factually wrong; (2) restoreScroll's per-rAF window.scrollTo loop (revamp.js:120-125) restarts a smooth animation every tick, delaying settle behind the cover mask toward its 2.5s failsafe; (3) PgDn/Space/Home keyboard scrolling becomes animated in Chrome — sluggish for power users; (4) no reduced-motion guard. Anchor smoothness is already provided by scrollIntoView({behavior:'smooth'}) in revamp.js:166.

Fix

Delete the global rule — the JS anchor handler already delivers smooth in-page nav where it's wanted. If any CSS smooth is kept, scope it inside @media (prefers-reduced-motion: no-preference).

lowmotion

About figure: four motions run simultaneously and the liquid fill never rests — 850ms hold vs 4300ms rise means it's mid-pour 83% of the time

Evidence

home-face-clean.js:89 HOLD=850, RISE=4300 — a full colour state is held under a second before the next 4.3s rise begins, cycling skin→lime→neutral every 5.15s forever; concurrently the badge spins (lia-badge-spin 26s linear, revamp.css:217), the turntable chases cursor/scroll (loop(), line 140-151), and the canvas scale 'breathes' per frame (line 149). The cluster is the thematic centerpiece ('I move things.') so constant motion is defensible — but with no rest state the fill reads as churn rather than as discrete, intentional colour events.

Fix

Raise HOLD to ~2600ms so each fully-filled colour registers as a pose (the 5.15s cycle becomes ~6.9s, calmer); optionally skip advancing the fill while cursorActive() is true so that when the visitor plays with the turntable, one motion clearly leads. Both are one-constant tweaks exposed in the file.

lowmotion

Dead and contradictory motion code: 2 orphaned heart keyframes, and revamp.js paint() styles a bulb that is never created — on every scroll event

Evidence

(1) customcss.css:276 heartPulse 2.2s targets .ja-hero-h1 .heart — the rendered H1 ('Art direction across brand, motion, and CG.') contains no .heart span, the keyframes ship dead. (2) customcss.css:512 liaHeartPulse 2.6s is fully overridden by the V2 liaHeartbeat 1.4s !important at line 609 — two competing heartbeat definitions for the same element. (3) revamp.js:16-19 paint() does querySelectorAll('.lia-bulb-btn') and is called from onScroll (line 35) on every scroll, but only .lia-bulb-fab is ever created (injectBulb, line 132-143) — a guaranteed-empty DOM query per scroll event; revamp.css:137's .lia-bulb-btn block is equally dead.

Fix

Delete the .ja-hero-h1 .heart rule + heartPulse keyframes, delete the V1 liaHeartPulse block, remove paint() and its onScroll call (bulb colour is CSS-token-driven per the §31 comment), and drop revamp.css:137. Zero visual change; less CSS to fight the next time a heartbeat is tuned.

lowmotion

Header fade is smoothed twice — JS smoothstep per frame, then re-eased through a .18s LINEAR CSS transition (the only linear move on the site)

Evidence

headerfade.js:14 applies smoothstep to the scroll→fade ramp and writes opacity/filter/transform inline every frame; revamp.css:231 then runs those same inline values through transition: opacity .18s linear, transform .18s linear !important. Two smoothing layers on one property: after an abrupt scroll-stop the header keeps drifting ~180ms, and `linear` violates the site's stated --ease cubic-bezier(.4,0,.2,1) standard. revamp.css §32 establishes the doctrine for scroll/JS-driven surfaces: 'A CSS transition here would fight those per-frame inline updates'.

Fix

Pick one owner. Preferred: drop opacity/transform from the revamp.css:231 transition (keep border-color) and let the JS smoothstep own the fade like #work2 does — if discrete wheel steps then feel notchy, add a small lerp inside compute() instead. Minimum fix: swap linear → var(--ease) so the trailing motion at least follows the house curve.

polish13

mediumpolish

No favicon at all — browser tab shows the default globe and /favicon.ico 404s

Evidence

Zero <link rel=icon|shortcut|apple-touch-icon> tags in /tmp/audit/home-rendered.html (grep count 0). Live check: https://lessisamor.tv/favicon.ico returns HTTP 404 (4,511-byte HTML error page). The waterfall in desktop-light.json confirms the browser auto-requests /favicon.ico and receives the 404 ('t':'other', dec 4511, at 12,045ms). Every tab, bookmark, and share-sheet shows a generic placeholder icon next to an otherwise bespoke brand.

Fix

Add a Site Icon in WP Customizer (512x512 PNG of the lime heart from the 'Less is Am♥r' wordmark, or a lime 'A' mark on #0A0A0A). WordPress then emits all icon sizes + apple-touch-icon automatically and the 404 disappears. One-line optional extra in lia-revamp.php: an SVG icon link for crisp dark/light tab rendering.

mediumpolish

Every carousel card bakes a bold 'Untitled' headline + 'One-line description placeholder.' under it — the unfinished state is the most repeated text on the page

Evidence

All 95 pool items in the inline JSON (home-rendered.html line 37) carry alt_text:'Untitled' and caption:'One-line description placeholder.'. The caption pipeline in home-photography-clean.js (makeSubtextTexture, lines 728-756, fetched live) bakes alt_text as the TITLE at font '600 80px Inter' and the caption as a 44px description line onto a 1400x700 canvas texture shown under each visible card — so every card in all 3 desktop carousels displays bold 'Untitled' over the placeholder sentence. Line 744 even hard-defaults to 'Untitled' when alt is empty.

Fix

Don't wait for curation — add a placeholder guard in makeSubtextTexture (and the cgvfx/explorations equivalents): if alt_text==='Untitled' or caption starts with 'One-line description placeholder', return blankSubTex() (captions row simply absent, cards read as a clean visual reel) or bake a neutral auto-label like 'MOTION · 04' from the section name + index. The guard self-retires as real captions sync into WP media, zero re-deploy needed.

mediumpolish

The three work sections are empty divs + canvases with zero fallback — before WebGL initialises they render as giant blank colour fields

Evidence

Section markup is only <div id=jaHead></div><div id=jaCyl></div> (home-rendered.html, #work/#work2/#work3) — no text, no img, no noscript, no skeleton. All four full-page screenshots captured the carousels in exactly this state: shot-mobile-light-full slices 1-3 show ~1.5 viewports of solid empty lime; desktop shots show the same flat lime field. Dependencies arrive late: three.js (143KB) finishes at ~5.1s, FCP is 6.0s, pool images stream until 8.8s (desktop-light.json). The portfolio area also has zero accessible/indexable text (headings are baked into canvas textures).

Fix

Add a static fallback layer inside each .ja-work-stack: the LQIP base64 thumbnails (already embedded in the 91KB head script — zero new bytes) rendered as a simple CSS strip plus a real DOM <h2> ('Motion' / 'CG & VFX' / 'Explorations'), fading out on the carousel's first rendered frame. Cheap, makes slow first visits look intentional instead of broken, and gives screen readers/SEO actual content. Add aria-label on each section as part of the same edit.

mediumpolish

JetBrains Mono never loads — its @import is illegally mid-stylesheet, so the orbit badge, SCROLL cue, hero meta, drawer links and timecode fall back to Courier New / random system mono

Evidence

customcss.css line 400 has the Google Fonts @import (JetBrains Mono + Fraunces + Bricolage) AFTER ~399 lines of rules — CSS requires @import before all rules, so browsers ignore it. Confirmed: desktop-light.json fonts=["Inter|100 900|loaded"] only, and no fonts.googleapis.com request among all 103 requests. Literal 'JetBrains Mono' stacks remain at revamp.css:218 (orbit badge SVG text), :739 (reel timecode), :798 (contact-modal eyebrow), plus p835 inline styles for .ja-hero-top, .ja-scroll-cue and #ast-hf-mobile-menu li a. Visually verified: the orbiting DESIGN·CG·VFX·MOTION ring renders in unmistakable Courier New typewriter serifs in BOTH mobile screenshots (zoom of shot-mobile-light-full.png y5392-5800).

Fix

Pick one: (a) self-host JetBrains Mono 500/600 woff2 next to inter-latin.woff2 in /wp-content/uploads/lia-fonts/ with a proper @font-face in revamp.css (restores the intended techy mono accent), or (b) replace every literal 'JetBrains Mono' stack with var(--font-mono) so revamp's Inter token applies consistently. Today's result is a per-OS font lottery (Courier New on macOS, Consolas-ish on Windows) — the single cheapest-looking detail on the page. Also delete the dead @import (it pulls nothing but signals abandoned experiments).

mediumpolish

Pill buttons are mismatched twins: 39px vs 41px vs 43px tall, visible side-by-side in the hero — and all below the 44px tap minimum

Evidence

Measured on mobile (runC.out tap data): .ja-btn-primary h=39, .ja-btn-ghost h=41, .ja-btn-ghost.ja-btn-icon h=43 — the stacked hero pair differs by 4px and the CTA pair by 2px. Visually confirmed on desktop: in shot-throttled.png the 'Download CV' ghost pill is clearly taller than the adjacent 'Show Reel' lime pill (native-res crop x1050-1950 y1330-1500). Root cause: height is derived from padding arithmetic (revamp.css:184 .92/.64rem asymmetric 'optical centre' hack, :192 .78/.78rem icon variant) so the icon's SVG line-box and the ghost border change the total. The CSS comment claims 'same total height' — measurement disproves it.

Fix

Stop deriving pill height from padding: give .ja-btn { display:inline-flex; align-items:center; justify-content:center; min-height:48px; padding:0 1.6rem } — one true height for all variants, icon vertically centred for free (delete the .ja-btn-icon padding override and the optical-centre comment hack), and it clears the 44px Apple/Material tap-target floor that all four pills currently miss (39-43px).

mediumpolish

Show Reel opens a 23.5MB MP4 with no poster and no loading state — black void until it buffers

Evidence

headers.txt: Showreel_Amor-Des_Jatin-Gupta_2026_1.mp4 content-length 23,494,668. desktop-light.json videos[]: preload:'none', poster:0, readyState:0. home-reelmodal.js has no 'waiting'/'canplay' spinner handler (grep shows only a buffered-progress bar at line 87). So clicking the hero's pulsing primary CTA pops a modal whose 16:9 stage (#0A0A0A, revamp.css:702-708) sits as a black rectangle for seconds on a normal connection — at the measured 1.5MB/s throttle, ~4-6s before first frame.

Fix

Two small fixes: (1) add a poster attribute (first-frame webp, ~30KB, same pipeline as the hero poster which already exists) so the stage shows the reel instantly; (2) in home-reelmodal.js show a small lime spinner/pulse on 'waiting' and hide on 'playing'. Separately worth re-encoding the reel (23.5MB is ~3x a typical 1080p portfolio reel at CRF 23) — server-side ffmpeg, no CDN involved.

lowpolish

Two different heartbeats run at once: logo heart thumps at 1.4s while the CTA buttons pulse at 2.2s

Evidence

customcss.css:609 .site-title .lia-heart, .lia-heart { animation: liaHeartbeat 1.4s ... scale peaks 1.28/1.22 } vs revamp.css:763-764 .ja-btn-primary { animation: lia-heartbeat 2.2s ... peaks 1.05 }. In the hero viewport both are on screen simultaneously (wordmark heart top-left, Show Reel button centre — see shot-throttled.png); in the footer the Film Am♥r heart (.lia-heart, 1.4s) sits one viewport under 'Hit me Up' (2.2s). A third unused keyframe set (heartPulse 2.2s, customcss:276; liaHeartPulse 2.6s, :512) is dead weight from earlier iterations.

Fix

One heartbeat spec: keep 2.2s as the master period, let the small glyph hearts keep their larger amplitude but on the same 2.2s clock (or an exact 2:1 subdivision) so the page reads as one organism, not three accidental metronomes. Delete the two orphaned keyframe blocks while there.

lowpolish

In light theme the bulb FAB nearly vanishes over the dark hero — a dark circle on a near-black background

Evidence

revamp.css:649-653: FAB background is var(--text) (#202124 in light mode) with only a soft shadow (0 3px 12px rgba(0,0,0,.20)). Native-res crop of shot-desktop-light-full.png (x2680-2880, y4660-4860) shows the #202124 circle sitting on the #0A0A0A hero — the disc reads only as a faint smudge; just the white bulb glyph is visible. Same situation over the dark third carousel panel. The inverted-surface idea is correct on light sections but has no affordance on dark ones.

Fix

Add a hairline that only matters on dark surroundings: box-shadow: inset 0 0 0 1px rgba(255,255,255,.16), 0 3px 12px rgba(0,0,0,.20) on the light-mode FAB (the inset ring is invisible on white, defines the disc on black). Dark-mode FAB is already a white disc and needs nothing (verified in zoom of shot-desktop-dark-full.png).

lowpolish

Three icon languages collide: Font-Awesome filled brand glyphs in the footer, stroke icons elsewhere, and a raw text '↓' for the scroll cue

Evidence

Footer (home-rendered.html #colophon): Astra ahfb-svg-iconset FA path icons — outlined Instagram square, solid-filled LinkedIn block, 'Bē' wordmark glyph — three different visual weights in one row (visible in shot-mobile-light-full slice 5 / dark slice 5). The site's own icons are stroke-based: Download CV inline SVG (stroke paths, hero), bulb FAB (stroke=currentColor, revamp.css:662). The SCROLL indicator's arrow is a literal text node <span class="arrow">↓</span> (rendered HTML) whose glyph shape depends on the fallback font, while Download CV uses a drawn SVG arrow two sections away.

Fix

Redraw the 3 social icons as simple stroke SVGs at the same stroke weight as the bulb/download icons (Astra header builder accepts custom SVG, or hide Astra's row and emit your own in lia-revamp.php), and swap the '↓' text node for the same SVG arrow used in Download CV. One icon language site-wide is a classic expensive-site marker.

lowpolish

Scrollbars are completely unstyled — stock grey Windows scrollbar against the dark theme

Evidence

Zero scrollbar rules in both stylesheets: grep for 'scrollbar' across /tmp/audit/revamp.css (64KB) and customcss.css (68KB) returns nothing. On macOS overlay scrollbars hide the issue, but on Windows/Linux Chrome a default 17px light-grey track runs the full height of the dark-mode page (#0E0E0E body) — the single most visible default-browser element on an otherwise fully art-directed page.

Fix

Add theme-aware scrollbar styling to revamp.css: html { scrollbar-color: var(--text-muted) var(--bg); scrollbar-width: thin; } plus the ::-webkit-scrollbar (10px, var(--bg) track, var(--border) thumb, var(--text-muted) on hover) equivalents. Both flip automatically with the existing token swap on theme toggle.

lowpolish

Legacy neon greens (#90ff00, #aaff50) still live in the Astra Customizer tokens beneath the revamp

Evidence

Astra's generated inline CSS in home-rendered.html still carries ::selection{background-color:#90ff00} and #aaff50 for input:focus borders, checkbox/radio checked states, tagcloud hovers and entry-meta — two different greens, neither the brand lime #C7F23C. revamp.css:155 currently out-cascades the selection rule, but native form controls (e.g. SureForms on the contact page — its CSS already loads on home per desktop-light.json styles[]) and any revamp.css load failure surface the old neon palette.

Fix

Update the accent/link colors in Astra Customizer global palette to #C7F23C (with #A8D028 hover) so the generated CSS agrees with the brand instead of being masked by it. Five minutes in wp-admin or one wp-cli astra-settings update; removes an entire class of 'wrong green' edge cases.

lowpolish

Duplicate H1: a hidden 'Home' entry-title H1 precedes the real hero H1

Evidence

home-rendered.html:1935 <h1 class="entry-title">Home</h1>; runA.out headings list confirms two H1s in the DOM ('H1|32px|Home' + 'H1|104px|Art direction across...'). It is display:none'd via customcss.css:19 (body.home .entry-title) — CSS-hiding rather than removal, so the document outline and crawlers still see a generic 'Home' as the first H1 of a portfolio whose headline is the actual pitch.

Fix

Remove it server-side instead of hiding it: in lia-revamp.php add add_filter('astra_the_title_enabled','__return_false') gated on is_page(835) (or disable 'Show Page Title' in the page's Astra meta box). Then delete the now-dead display:none rule.

lowpolish

Some mobile text renders at 10px — below the legibility floor

Evidence

runC.out (405px viewport) reports minFont:10 across the rendered page. Candidates from the source: .ja-hero-top at 0.7rem (p835 inline) and small footer/legal strings; the label tier was normalised to 12px in revamp.css:165 but at least one element escapes it and bottoms out at 10px.

Fix

Find the offender with a one-liner in DevTools ([...document.querySelectorAll('*')].filter(e=>parseFloat(getComputedStyle(e).fontSize)<11 && e.innerText.trim())) and either lift it into the existing 12px label tier (add its selector to revamp.css:163) or delete it if it is a dead element. 11px is the practical minimum for the spaced-uppercase style this site uses.

uxflow13

highuxflow

Hero 'Download CV' button 404s — the single most hiring-critical action is broken

Evidence

Rendered HTML: <a href="/cv.pdf" class="ja-btn ja-btn-ghost ja-btn-icon" download>. curl https://lessisamor.tv/cv.pdf returns HTTP 404. No JS intercepts it (home-reelmodal.js only grabs the first .ja-btn-primary; grep across all home-*.js shows no cv/download handler). A creative director's literal first hiring action — grabbing the CV from the hero — dead-ends on a 404 page.

Fix

Upload the CV PDF to /cv.pdf (or point the href at an existing uploads path, e.g. /wp-content/uploads/lia/Jatin-Gupta-CV.pdf) and verify with curl -I = 200. If no CV is ready, hide the button until it is — a 404 is worse than no button.

highuxflow

Final-CTA 'View All Work' links to /work/ which 404s

Evidence

Rendered HTML: <a class="ja-btn ja-btn-ghost" href="/work/">View All Work. curl https://lessisamor.tv/work/ = 404, while /gallery/ = 301→200 (exists). No JS interception (home-contact.js only wires the .ja-cta .ja-btn-primary 'Hit me Up'). The closing conversion block offers two doors and one of them is broken.

Fix

Repoint href to /gallery/ (the existing work page), or create /work/ as an alias/redirect. One-line fix in page 835's CTA markup.

highuxflow

The proof layer is a dead end: work is unclickable, unnamed, and never connects to the client logos

Evidence

All 3 carousel JS files register only pointerdown/up drag handlers — zero click handlers, zero hrefs (grep across home-cgvfx-clean.js:770, home-explorations-clean.js:803, home-photography-clean.js:1359/1664). Live capture /tmp/audit/live-work1.png shows recognizable client work (an Amazon/Nike/ASUS motion piece front-centre) captioned 'Untitled — One-line description placeholder'. Image filenames even prove named client work exists in the pool (Black__0008_DMD_Powerpuff-Girls_Final-Output… = Nickelodeon's Powerpuff Girls) — but the page shows the Nickelodeon logo in section 2 and never tells you the spinning card below IS Nickelodeon work. A hiring CD can spin pretty cards but cannot open, identify, or verify a single project.

Fix

When the captions get curated, lead each with CLIENT — PROJECT ('Nickelodeon — Powerpuff Girls promo') so the logo wall and the work corroborate each other. Make at least the front/center card clickable (lightbox with project blurb, or anchor into /gallery/). This is the difference between decoration and proof.

mediumuxflow

'A few motion stuff.' is ungrammatical, and the triple 'A few…' template amplifies the sameness

Evidence

home-photography-clean.js: HEAD_TEXT='A few motion stuff.' ('stuff' is uncountable — 'a few stuff' reads as an error, not a vibe, on a page selling art direction). home-cgvfx-clean.js: 'A few recent snaps.', home-explorations-clean.js: 'A few CG/VFX things.' — three consecutive sections, one sentence template.

Fix

Keep the lowercase casual voice but vary it and fix the grammar: e.g. 'Things I made move.' / 'Snaps between renders.' / 'CG/VFX experiments.' Each heading is a one-string edit in its JS file (canvas-drawn, so no layout risk).

lowuxflow

Three near-identical cylinder sections in a row — the middle of the page is rhythmically monotone

Evidence

Live captures live-work1/2/3.png: all three sections are the exact same component — curved canvas heading top-center, 7-card cylinder arc, tiny tilted captions — differing only in background (home.css:1: #work lime #C7F23C, #work2 #0A0A0A, #work3 #3b07ed) and image pool. Headings follow one template: 'A few motion stuff.' / 'A few recent snaps.' / 'A few CG/VFX things.' (HEAD_TEXT/TITLES in the three JS files). Mobile already proves the alternative: home.css @media(max-width:921px) hides #work2/#work3 and runs ONE unified cylinder with rotating titles.

Fix

Break the rhythm at section 2 or 3: keep the cylinder for motion (it's the hero category), then switch form — e.g. photography as a flat full-bleed filmstrip/marquee, CG/VFX as a 2×3 hover-to-play loop grid — or adopt the mobile pattern on desktop: one carousel with three tabbed pools. Repetition of a spectacular component makes it stop feeling spectacular by the third screen.

lowuxflow

Section order breaks the hero's promise: 'brand, motion, and CG' is answered by motion → holiday snaps → CG

Evidence

H1: 'Art direction across brand, motion, and CG.' (desktop-light.json headings). The proof sections deliver: 'A few motion stuff.' (professional), then 'A few recent snaps.' (live-work2.png = personal travel/street photography — Amsterdam street, basilica ceiling), then 'A few CG/VFX things.' (professional). Personal photography is sandwiched BETWEEN the two professional categories, and no section maps to 'brand'.

Fix

Reorder to Motion → CG/VFX → Snaps so professional proof is contiguous, or move photography down beside About as explicit personality content ('Off the clock' framing — it already matches the About copy 'Off the clock I'm into films, design…'). If brand-identity work exists in the pools, label a section with the word 'brand' so the H1's promise is visibly kept.

lowuxflow

'Selected Clients' consumes a full screen before any work appears

Evidence

Section order (sections.txt): hero → ja-clients → 3 work sections. The clients panel (myslice-1/slice-light-1: 9 grey logos in two centred rows on a grey field) occupies the entire second screen, so a visitor's first sight of actual work is screen 3 — later still on first visit because carousel images load progressively (4.85MB / 103 requests, FCP 6.0s). Logos-before-work is a legitimate trust play, but it spends the page's most valuable scroll position on borrowed credibility rather than owned proof.

Fix

Compress the clients into a single-row auto-scrolling marquee strip (~120px tall) directly under the hero — same trust signal, four-fifths less scroll cost — or swap it below the first carousel so work leads.

lowuxflow

About eyebrows and the orbiting badge render in Courier New — JetBrains Mono is specified but never loaded

Evidence

home.css:10: .lia-abt-label{font-family:"JetBrains Mono","Courier New",monospace !important} (same stack used ~8× in customcss.css incl. the orbit badge). desktop-light.json fonts array: ["Inter|100 900|loaded"] — Inter is the ONLY webfont. Screenshots confirm the fallback: 'WHAT I MAKE' / 'WHERE MY HEAD'S AT' (slice-light-4.png) and the orbiting 'CG · VFX · MOTION · DESIGN' ring (zoom-badge.png) render in serif-footed Courier — a default-y typewriter face inside an otherwise tightly art-directed Inter system.

Fix

Self-host a tiny JetBrains Mono latin subset (same pattern as the existing inter-latin.woff2) — or change the stack to Inter with .26em letter-spacing, which is what every other eyebrow on the page already is.

lowuxflow

Meta description is a garbled text dump — the Google-snippet first impression reads 'acrossbrand'

Evidence

home-rendered.html: <meta name="description" content="AMOR DES JATIN GUPTA · SINGAPORE Art direction acrossbrand, motion, and CG. Art Director · CG Generalist · Crafting at Carbon Show Reel Download CV SCROLL ↓ SELECTED CLIENTS What I make…"> — auto-concatenated page text including button labels and 'SCROLL ↓', with the H1's line break collapsed into 'acrossbrand'. Same string in og:description and twitter:description, so shared links and SERPs both show it.

Fix

Hand-write one 150-char description (e.g. 'Jatin Gupta — art director in Singapore. Motion design, CG and VFX for Nickelodeon, Razer, Colgate, Marina Bay Sands and more.') and set it via the mu-plugin or an SEO field for page 835.

lowuxflow

Heading semantics: duplicate hidden H1 'Home', and the About headings aren't headings

Evidence

desktop-light.json headings: [H1 'Home' (32px, hidden via customcss.css:19 display:none), H1 hero, H2 'Got something to move?'] — only 3 DOM headings on the whole page. 'I move things.' and 'What I make' are styled divs (class lia-abt-lead / lia-abt-label in home-rendered.html), and the work sections contribute nothing (canvas). Document outline a recruiter's reader-mode or a crawler sees: Home → hero → CTA, with the entire middle missing.

Fix

Remove the WP entry-title H1 server-side in lia-revamp.php (instead of CSS-hiding it), and promote 'I move things.' to an <h2> with lia-abt-label as a <p> eyebrow.

lowuxflow

Button label casing inconsistency: 'Hit me Up'

Evidence

CTA buttons across the page are Title Case — 'Show Reel', 'Download CV', 'View All Work' (home-rendered.html) — except 'Hit me Up' (lowercase 'me', capital 'Up'), visible in slice-light-7.png and slice-dark--7.png.

Fix

Make it 'Hit Me Up' (or 'Hit me up' if the intent is sentence-case voice — but then the voice should apply to all four buttons). One-string edit in page 835.

lowuxflow

Six-plus screens with no conversion touchpoint between hero and final CTA (header fades away on scroll)

Evidence

The only contact/work affordances are hero buttons (screen 1) and the ja-cta block (last screen); the masthead — including the Contact nav link — fades out on scroll (home-headerfade.js, #masthead opacity transition in home.css:6). Mid-page, the only persistent UI is the theme-toggle FAB (zoom-fab.png). A CD convinced by carousel #1 has no shortcut to act on it.

Fix

Either let the header fade back in on scroll-up (if it doesn't already), or add one quiet mid-page link after the third carousel ('Like what you see? → Hit me up') so intent formed mid-proof has somewhere to go.

lowuxflow

CG/VFX carousel imagery reads markedly softer than the other two pools — verify on a real GPU

Evidence

In identical capture conditions (same 6s-wait SwiftShader CDP run), live-work3.png's centre card is visibly blurry/low-res while live-work1.png and live-work2.png centres are sharp. The vfx pool is 54 PNG→WebP conversions of video-frame extractions, so source fidelity may genuinely be lower — meaning the section proving his core craft (CG/VFX art direction) shows the weakest pixels. Could also be the LQIP→full upgrade losing its race under SwiftShader, so this needs a real-GPU check.

Fix

Eyeball #work3 on a real display; if sources are genuinely soft, re-extract the VFX frames at higher resolution or curate only the frames that hold up at 768×1024 card size.

typography12

hightypography

JetBrains Mono never loads — labels, badge, popovers and mobile drawer render in Courier New

Evidence

The only mono source is an @import at customcss.css:400 sitting mid-stylesheet (after 399 lines of rules), which browsers must ignore — confirmed: /tmp/audit/desktop-light.json fonts = ["Inter|100 900|loaded"] only, and no fonts.googleapis request anywhere in the 103-request waterfall. Every rule that asks for mono therefore falls to Courier New, clearly visible in shot-mobile-light-full.png and shot-mobile-dark-full.png: 'WHAT I MAKE' / 'WHERE MY HEAD'S AT' eyebrows (slab-serif Courier; .lia-abt-label, /wp-content/uploads/lia/home.css:10 'JetBrains Mono','Courier New',monospace !important) and the orbiting DESIGN·CG·VFX·MOTION badge ring (revamp.css:218). Same fallback hits the contact popover eyebrow 'Get in touch' (revamp.css:798), the 10px 'Singapore/India' subs (revamp.css:805), the reel-modal timecode (revamp.css:739), and the mobile drawer links (customcss.css:126-128, '#ast-hf-mobile-menu li a { font-family:'JetBrains Mono', monospace !important }' — ID specificity beats revamp's .menu-link→Inter rule). Courier New at 11-13px tracked caps is the single cheapest-looking type on the page, on a site whose revamp deliberately mapped --font-mono to Inter (revamp.css:25).

Fix

Pick one direction and apply it everywhere: (a) finish the Inter unification — replace every literal '"JetBrains Mono","Courier New",monospace' (home.css:10, revamp.css:218/739/798/805, customcss.css:127) with var(--font-mono) and delete the dead @import at customcss.css:400; or (b) if a true mono accent is wanted for the techy bits (badge ring, timecode, eyebrows), self-host a JetBrains Mono latin subset woff2 (~20KB, same pattern as inter-latin.woff2 in revamp.css:9-16) and keep the literal stacks. Either way no element should ever resolve to Courier New.

mediumtypography

WebGL carousel headings break the display-type system: weight 400, zero tracking, size set by string length not scale

Evidence

The three biggest type moments after the hero ('A few motion stuff.' home-photography-clean.js:382, 'A few recent snaps.' home-cgvfx-clean.js:74, 'A few CG/VFX things.' home-explorations-clean.js:52) are canvas-baked at g.font='400 '+fs+'px Inter' with fs auto-scaled so the string fills 90% of a 2400px texture (cgvfx:81-84) — i.e. font-size is a function of character count, not a scale step, and canvas fillText applies no letter-spacing. Meanwhile every DOM display heading is 700 weight with tight tracking: hero 104px/700/-0.04em (revamp.css:172, computed -4.16px), CTA h2 64px/700/-0.035em (revamp.css:173). So the page alternates between two display voices: tight heavy Inter (DOM) and airy regular Inter (canvas). Bonus: the 2400px-wide texture is stretched across a full-width panel that is ~2880 device px on a 1440 retina screen (renderer.setPixelRatio min(dpr,2), cgvfx:92), so the canvas headings also render slightly soft versus DOM text.

Fix

In all three drawHeading() functions: bake at '700' weight, set g.letterSpacing='-0.035em' (supported in Chrome 99+/Safari 17+, harmless no-op elsewhere), and clamp fs to a scale-derived target (~equivalent of the 64px H2 step on screen) instead of fill-90%-width; bump tc.width to 2880 (or size it from devicePixelRatio) to kill the retina softness. The fonts.ready re-bake already exists (cgvfx:101) so the change is contained to the draw call.

mediumtypography

Mobile hero H1 rags badly ('across' stranded alone) and CTA H2 drops a widow ('move?')

Evidence

p835.html hardcodes the desktop break: <h1 class="ja-hero-h1">Art direction across<br>brand, motion, and CG<span>.</span></h1>. At 390px (font-size clamp(2rem,11vw,4rem), customcss.css:385) the natural wrap collides with the forced <br>, producing 'Art direction / across / brand, motion, / and CG.' — the word 'across' sits alone mid-phrase on line 2 (visible in shot-mobile-light-full.png, hero). The contact H2 shows 'Got something to / move?' — single-word widow (shot-mobile-light-full.png, CTA section). The lh 1.04 itself holds up fine even at 4 lines; the problem is purely the rag.

Fix

Add `@media (max-width:768px){ .ja-hero-h1 br{ display:none; } }` and `text-wrap:balance` on .ja-hero-h1 and .ja-section-h (progressive enhancement, Chrome/Safari support it) — mobile then wraps 'Art direction / across brand, / motion, and CG.' and the CTA balances to 'Got something / to move?'. Keep the <br> for desktop where the 2-line break is deliberate.

mediumtypography

Carousel captions: titles hard-truncate to ONE line with '…' and are never re-baked after the font loads

Evidence

Caption display pipeline (home-cgvfx-clean.js:422-452, mirrored in the other two): title baked '600 80px Inter' via wrapText(g,title,c.width-120,1) — maxLines=1, and wrapText:415 chops overflow and appends an ellipsis (charCode 8230). Desc is a single '400 44px' line. So the moment real curated titles replace 'Untitled', anything longer than the card width gets clipped mid-phrase with '…' and there is no second line. Separately, the fonts.ready re-bake only covers headings (cgvfx:101 calls drawHeading only) — caption textures created in build() before Inter resolves would stay baked in the fallback face forever. And as canvas pixels, captions are unselectable/invisible to assistive tech.

Fix

Before the curated captions land: allow 2 title lines (wrapText maxL=2, advance startY by titleLines.length), or auto-shrink fs to fit; extend the document.fonts.ready handler to re-run makeSubtextTexture for all cards (the _photos array is already retained for re-theming, cgvfx:455); and mirror title/desc into aria-labels on the canvas containers for accessibility.

lowtypography

Popover/modal micro-type ignores the unified label tier and bottoms out at 10px on mobile

Evidence

revamp.css:163-167 establishes ONE label tier (12px/.16em/600 caps) and the comment even brags 'was 5 sizes + 3 tracking units → systematic'. But the interactive layers re-introduce the chaos: contact-card eyebrow 11px/.22em (revamp.css:798), contact rows' 'SINGAPORE/INDIA' subs 10px/.1em (revamp.css:805 — this is the minFont:10 reported in runC.out for the 390px viewport), reel-modal timecode 12.5px/.02em (revamp.css:739). Three new sizes and three new tracking values, all also Courier-fallback (see typography-1). 10px tracked uppercase is below comfortable legibility on a phone.

Fix

Re-point all three to the existing tier tokens: 12px/.16em/600 for .ja-contact-eyebrow, and at minimum 11px for .ja-contact-row .sub and .ja-reel-time (or keep timecode at 12px and tabular-nums via font-variant-numeric since it's a counter). One tier everywhere is exactly the system the revamp already declared.

lowtypography

Display-tier tracking/weight drifts across four values while the size scale itself is coherent

Evidence

Sizes step cleanly at ~1.6 ratio (104 → 64 → ~39 (about-lead clamp(25px,2.7vw,40px) = 38.9 at 1440, home.css:12) → 25 (contact title, revamp.css:799) → 15/16 body → 12 labels) — the modular scale is genuinely fine. But tracking and weight wander: hero -0.04em/700 (revamp.css:172), h2 -0.035em/700 (revamp.css:173), about-lead -0.015em/600 (home.css:12), contact title authored -0.01em/600 (revamp.css:799, the global h3 rule at revamp.css:33-36 then forces -0.021em), generic h2-h4 -0.021em/600 (revamp.css:33-36), canvas headings 0/400 (typography-2). Five tracking values and three weights across one display family is what separates 'system' from 'assembled'.

Fix

Define three display tokens and apply them everywhere a display size appears: XL (≥96px): 700/-0.04em; L (48-64px): 700/-0.035em; M (32-40px): 600/-0.02em. Concretely: bump .lia-abt-lead letter-spacing -0.015em → -0.02em, align .ja-contact-title to -0.02em, and fix the canvas headings per typography-2.

lowtypography

Desktop nav caps (11.52px/.22em/500) are a near-duplicate of the 12px/.16em/600 label tier

Evidence

customcss.css:548-556 sets .main-header-menu .menu-link to 0.72rem/.22em/500 (computed in desktop-light.json: navLink 11.52px, ls 2.5344px, w500) while every other caps label on the page is the revamp tier 12px/.16em/600 (revamp.css:163-167). Two uppercase micro-styles 0.5px and 0.06em apart read as accidental, not intentional, and 11.52px is an off-grid value falling out of the rem math.

Fix

Fold the nav into the label tier: 12px/.16em/600 (or, if the airier .22em is preferred for nav, make .22em the single tier tracking everywhere). One caps style site-wide.

lowtypography

Button label casing is inconsistent: 'Hit me Up' vs 'View All Work' vs 'Show Reel'

Evidence

Rendered HTML (home-rendered.html, CTA section): <a class="ja-btn ja-btn-primary">Hit me Up</a> next to <a class="ja-btn ja-btn-ghost">View All Work</a>; hero has 'Show Reel' and 'Download CV' (both Title Case). 'Hit me Up' mixes lowercase 'me' with capitalized 'Up' — visible on every viewport (shot-mobile-light-full.png CTA, shot-desktop-light-full.png hero).

Fix

Pick one convention for all four buttons — either Title Case ('Hit Me Up') or sentence case ('Hit me up', 'View all work', 'Show reel', 'Download CV') — sentence case skews more modern/editorial and matches the lowercase-friendly brand voice.

lowtypography

Footer text is untokenized Astra default: 16px vs the site's 15px body

Evidence

desktop-light.json computed: footer 16px/400/lh 26.4px (1.65) vs p 15px/1.55 and about-body 15.12px/1.55 — the footer ('Lesmo Studios Pvt Ltd ™', shot-mobile-light-full.png bottom) is the only body-level text not on the 15px token, betraying theme-default styling in the last thing a visitor reads.

Fix

Set footer copy to the body token (15px/1.55, var(--text-muted) if it should recede) in revamp.css so the footer participates in the same system as everything above it.

lowtypography

Desktop About right column sets body copy ragged-left (text-align:right)

Evidence

home.css:37-40: @media (min-width:922px) .lia-abt-inner>.lia-abt-col:last-child{text-align:right} with the 34ch body pushed right via margin-left:auto — the 'Where my head's at' paragraph (4-5 lines) reads with a ragged left edge on desktop. Symmetry with the left column is the intent, but rag-left multi-line body text measurably slows reading and tends to look unstable. (Desktop screenshot couldn't capture this section — page hard-locks programmatic scroll, the full-page shot loops the hero — but the rule is unambiguous and mobile screenshots confirm the column stacks left-aligned below 922px.)

Fix

Keep the column right-POSITIONED for symmetry but left-align the text inside it (drop text-align:right, keep the auto margins on .lia-abt-body/.lia-abt-rule, right-align only the 11px label if the mirrored look matters). Verify on the live desktop since this section isn't capturable headless.

lowtypography

Dead/overridden legacy type rules make the cascade fragile (ghost fonts, dead sizes)

Evidence

Leftovers from the pre-revamp 'TYPOGRAPHY V2 — DESIGNER STACK': the ignored @import for Fraunces/Bricolage/JetBrains (customcss.css:400); .lia-abt-lead/.lia-abt-body still asking for 'Bricolage Grotesque' first (home.css:12-13 — only saved by revamp.css:31's body !important remap to Inter); .ja-clients-label 13.6px lime !important (customcss.css:1401) silently beaten by revamp's 12px tier; .ja-btn 0.78rem/.18em (customcss.css:~566) beaten by revamp's .875rem/.04em (revamp.css:183). None of these currently render, but any load-order or specificity change (revamp.css must inject at priority 99999 to win, per lia-revamp.php) resurrects a different typeface or size with zero warning.

Fix

Prune customcss.css/home.css of every font rule the revamp overrides: delete the @import, the Bricolage/Fraunces stacks, the 13.6px clients-label and the 0.78rem .ja-btn block. The rendered page won't change; the cascade stops depending on injection order to look right.

lowtypography

Heading semantics: hidden duplicate H1 'Home', section titles that aren't headings, and 'acrossbrand' leaking into the meta description

Evidence

home-rendered.html contains <h1 class="entry-title">Home</h1> (Astra page title, display:none'd at customcss.css:19) ahead of the real hero H1 — two H1s in source. The three carousel titles exist only as WebGL pixels and 'I move things.' is a <p class="lia-abt-lead"> (home-rendered.html:~2006), so the document outline is H1 → (nothing) → H2 'Got something to move?'. And the hero's space-less <br> concatenates in extracted text: the AIOSEO meta description literally reads 'Art direction acrossbrand, motion, and CG.' (home-rendered.html, meta description).

Fix

Disable the Astra page title for the page (Astra meta setting) instead of CSS-hiding it; promote .lia-abt-lead to an <h2> (style unchanged); give each carousel section a visually-hidden <h2> matching its canvas text; and hand-write the AIOSEO meta description so the <br> artifact never reaches search snippets.

color12

mediumcolor

The full-bleed lime carousel panel is identical #C7F23C in dark mode — the single brightest surface on an otherwise near-black page, with hard entry/exit edges

Evidence

shot-mobile-dark s1→s3 and shot-desktop-dark s2: the #C7F23C panel (relative luminance ~0.78) sits directly between the #1a1a1c clients panel and #121212 about section (luminance ~0.007) with hard cuts on both edges — a ~110:1 luminance jump in a dark-adapted viewing context. Light mode has the same panel but against #EEEEF0/#FFFFFF the jump is far smaller. No dark-mode override of the panel exists in revamp.css (its line 3 comment: carousels 'NEVER referenced').

Fix

Decide whether the lime slab is the deliberate brand moment in dark mode (defensible) or soften it for dark only: via the existing __liaRetheme handler, retheme block-1 to --accent-dark #A8D028 or a deep lime surface (e.g. #2A3310 panel + lime heading) when body.lia-dark. At minimum add a short (~120px) dark→lime ramp at the panel's top edge in dark mode so it doesn't strobe during scroll. Present to the user as a taste question, not a defect.

lowcolor

Dark mode has a 7-shade near-black surface zoo instead of the 2-token scale

Evidence

Tokens define --bg:#121212 and --bg-elevated/--bg-card:#1E1E1E (revamp.css:146), but rendered dark surfaces hardcode 5 more near-blacks: clients panel #1a1a1c (revamp.css:778), contact card #1a1a1a (revamp.css:794), reel modal #101010 (revamp.css:691), reel stage/work panels #0A0A0A (revamp.css:702,708; home-rendered.html:1620 .ja-work-stack background:#0A0A0A), plus the scrolled-header glass tinted rgba(10,10,10,.72) (revamp.css §25, 2 occurrences). Measured separation is sub-perceptual: #1a1a1c vs #121212 = 1.08:1, #0A0A0A vs #121212 = 1.06:1, #1E1E1E vs #121212 = 1.12:1.

Fix

Consolidate to the existing two tokens: #1a1a1c and #1a1a1a → var(--bg-elevated) (#1E1E1E), #101010 → #0A0A0A or var(--bg). Keep #0A0A0A as one named token (e.g. --bg-deep) for the work panels/reel stage so dark mode has exactly three deliberate levels (#0A0A0A / #121212 / #1E1E1E) instead of seven accidental ones. Differences are 2-8 RGB points, so the change is invisible but makes every future dark-mode edit land on-system.

lowcolor

Dark mode uses lime as default chrome colour (all footer icons, badge text, bulb glyph) while light mode uses it only as accent — accent economy is asymmetric between themes

Evidence

body.lia-dark .site-footer a svg/path { fill:var(--accent) } turns Instagram + LinkedIn + Behance + the Film Amour pill ALL lime (revamp.css:127-130, 437-447; confirmed in shot-mobile-dark s5 — four lime elements in one row), the about badge text flips to #C7F23C (revamp.css:219, visible in shot-mobile-dark s3/s4), and the bulb glyph goes lime when dark (revamp.js:12). Light mode footer icons are var(--text) dark (shot-mobile-light s5) and the badge is #0A0A0A ink — lime stays reserved for CTAs/highlights.

Fix

Match the light-mode economy in dark: footer social icons → var(--text) (#E8EAED, 15.8:1) with lime on hover, keep lime ONLY on the Film Amour pill (or only on hover states). Badge text in dark could stay lime as the one accent, or go #E8EAED to mirror light's ink treatment. One rule of 'lime = action/highlight' applied to both themes reads more deliberate.

lowcolor

Light elevated-surface colour is hardcoded #EEEEF0, bypassing --bg-elevated; three near-identical light greys defined, only one renders

Evidence

Clients panel: body .ja-clients { background:#eeeef0 !important } (revamp.css:777) — a slightly blue-tinted one-off. Meanwhile :root defines --bg-elevated:#F8F9FA (revamp.css:20) and §28 defines --md-sys-color-surface-container:#F1F3F4 (revamp.css:490); neither token paints anything visible. Light mobile/desktop screenshots show only the #EEEEF0 panel (shot-mobile-light s1, shot-desktop-light s1).

Fix

Pick one elevated grey (the rendered #EEEEF0 is fine — it reads clearly against white at scale), assign it to --bg-elevated, point --md-sys-color-surface-container at var(--bg-elevated), and change .ja-clients to background:var(--bg-elevated). Zero visible change, kills two phantom greys.

lowcolor

Customizer CSS carries a dead legacy palette: olive gradient ramps, lime-glass drawer ink, and a second lighter lime that never render

Evidence

The black→olive→lime entry ramps (#1a200a, #4d6314, #97b929 — 3 occurrences each, customcss.css:1090, 1114, 1491) never paint: screenshots show hard grey→lime and lime→white/black cuts (shot-mobile-light s1/s3, shot-desktop-light s2), the gradient-out ::after is killed by a later height:0 override (home-rendered.html:1628), and carousel JS sets section bg inline. Drawer ink #26360a (customcss.css:1147, 1214) is overridden by revamp.css:353-358 → var(--text). Reel close hover #D6F76A (customcss.css:1329) is overridden by revamp.css:757 → var(--accent). The old glass CTA #101a00/rgba(199,242,60,.42) (customcss.css:~1275) is overridden by revamp.css:109/466.

Fix

Delete the dead blocks from Astra Additional CSS (gradient .ja-work-section backgrounds + ::after, the #26360a drawer rules, the #D6F76A hover, the glass .ja-btn-primary). Removes ~8 phantom colours from the system with zero rendered change — and removes the risk of them resurfacing if rule order ever shifts. Use the wp-cli/SSH edit path per the established workflow, and back up first.

lowcolor

Latent invisible-text rule: .lia-tm forces the footer ™ to lime #C7F23C (1.30:1 on the white light-mode footer); currently saved only by an override

Evidence

.lia-tm { color:#C7F23C !important } (customcss.css:1518, marker LESMO-TM-LIME-V2) targets the ™ in 'Lesmo Studios Pvt Ltd ™' (home-rendered.html:2063). It only stays legible because .site-footer span { color:var(--text) !important } (revamp.css:119) wins on specificity — verified in the footer crops: ™ renders dark in light, light in dark. Lime on white = 1.30:1 (revamp.css's own §28 comment calls lime-as-text 'invisible'); if the override ever loses (rule reorder, class change), the ™ vanishes in light mode.

Fix

Resolve the intent conflict: delete the .lia-tm colour rule (no visible change), or if the lime ™ micro-accent is actually wanted, re-scope it to dark mode only (body.lia-dark .lia-tm — 14.45:1 there) where it also aligns with finding color-4's accent economy.

lowcolor

Magenta #ff2079 and cyan #00d4ff exist only inside the custom cursors — and there are no purple/blue panels anywhere in the system

Evidence

The chromatic-aberration cursor SVGs use fill #ff2079 and #00d4ff ghost copies at 0.7 opacity (home-rendered.html:~1790-1825, three cursor variants); neither hex appears anywhere else in revamp.css or customcss.css (verified by full hex extraction). For the record: no purple/blue carousel panel exists in CSS — the only other purple/blue on the page is hero-video CONTENT (the indigo phone-UI scene visible in shot-desktop-dark s0), which is curation, not system colour.

Fix

Keep the RGB-split cursors (on-brand for CG/VFX), but make the motif read intentional rather than orphaned: either echo it once more in a hover/active state (e.g. carousel drag feedback or link underline glitch) or document the two hexes as named tokens (--rgbsplit-m / --rgbsplit-c) so they are visibly part of the system, not strays. No change needed to the hero video.

lowcolor

Dark mode runs two different border greys: ghost buttons borrow the LIGHT theme's text colour #5F6368 while everything else uses --border #3C4043

Evidence

body.lia-dark .ja-btn-ghost, .film-amour-cta { border-color:#5F6368 !important } (revamp.css:201) vs the dark token --border:#3C4043 (revamp.css:160). #5F6368 is simultaneously light-mode --text-muted (revamp.css:21) — one hex playing two unrelated roles across themes. Visible in shot-mobile-dark s5: the 'View All Work' pill border reads brighter than the section hairline above the footer.

Fix

If the stronger button border is deliberate (it is a reasonable affordance bump: 3.1:1 vs bg against the hairline's 2.0:1), name it: add --border-strong:#5F6368 to the dark token block and reference it, breaking the accidental coupling to light-mode text. If not deliberate, drop to var(--border) for one consistent stroke weight.

lowcolor

Dark --border is declared twice in the same file (#2E2E2E then #3C4043 thirteen lines later) — dead token invites drift

Evidence

revamp.css:147 sets --border:#2E2E2E inside the §11 dark block; revamp.css:160 (§13) re-declares --border:#3C4043 for the same selectors, so #2E2E2E never renders. --text-label is also split across two :root blocks (lines 19-26 vs 159-160).

Fix

Merge the §13 token re-declarations back into the §1/§11 :root and .lia-dark blocks so each token has exactly one definition per theme. Pure hygiene, zero rendered change — prevents the next edit from 'fixing' the dead #2E2E2E and silently shifting every dark hairline.

lowcolor

Theme-toggle FAB focus ring is lime on the page background — 1.30:1 against white in light mode, failing non-text contrast

Evidence

.lia-bulb-fab:focus-visible { outline:2px solid var(--accent); outline-offset:3px } (revamp.css:663). With the 3px offset the ring sits on the page bg, not the dark FAB disc: lime on #FFFFFF = 1.30:1 (WCAG 2.2 non-text minimum is 3:1). Dark mode is fine (14.45:1 on #121212).

Fix

Theme-aware ring: outline-color:var(--text) (which already inverts per theme and matches the FAB's own inverted-surface logic), or keep lime but add a 1px var(--text) inner stroke. Only visible to keyboard users, but this is exactly the audience an accessibility-literate client notices.

lowcolor

Hero sub-line is 60%-opacity white over the video's weakest scrim band — legibility floats with whatever frame is playing

Evidence

body .ja-hero-sub { color:rgba(255,255,255,0.6) } (revamp.css:485) at 12px uppercase sits in the overlay's mid-band, where the scrim drops to rgba(10,10,10,0.35) (customcss.css:211-217). On the dark frames captured (both theme screenshots) it reads, but the video also has bright lime/white passages (visible in shot-desktop-light s0's bright frame) where 60% white over a ~50% luminance frame falls under 3:1. The lime 'CRAFTING AT CARBON' strong (#C7F23C, customcss.css:290) survives everywhere; only the 60%-white spans are at risk.

Fix

Cheap insurance, no design change: raise the sub-line to rgba(255,255,255,0.78), or add a text-shadow: 0 1px 12px rgba(10,10,10,.45), or deepen the overlay mid-band 0.35→0.45. Verify on the brightest video frame, not the dark ones.

lowcolor

Neutral ramp is verbatim Google-Material grey (#202124/#5F6368/#DADCE0/#3C4043/#9AA0A6/#BDC1C6/#E8EAED) — disciplined, but reads stock rather than owned

Evidence

Every neutral token in revamp.css:19-26, 145-149, 159-160 is an exact Google grey-ramp value (Grey 900 #202124, Grey 700 #5F6368, Grey 300 #DADCE0, Grey 800 #3C4043, Grey 500 #9AA0A6, Grey 400 #BDC1C6, Grey 200 #E8EAED). Discipline itself is good — text contrast all passes (muted on white 6.05:1, dark muted on bg 7.09:1, labels 9.03:1/10.35:1) — but the palette is recognisably Google Docs chrome next to a lime brand.

Fix

Optional brand-polish pass, only with user buy-in: warm the neutrals ~2-3 points toward the lime's hue (e.g. #202124→#1F2118, #5F6368→#5E635A, dark bg #121212→#121310) so the greys feel mixed from the brand ink, not borrowed. Keep the same luminance steps so every measured contrast ratio is preserved. Skip entirely if the Material-twin look is the point.

mobile12

mediummobile

Tap targets under 44px and the stacked button pairs have unequal heights

Evidence

runC.out measured: 'Show Reel' (ja-btn-primary) 283x39, 'Download CV' (ja-btn-ghost ja-btn-icon) 280x43, 'Hit me Up' 281x39, 'View All Work' 280x41, three footer social icons 43x43 — all below the 44px iOS minimum, and the hero pair differs by 4px despite the revamp.css:182-192 comment claiming 'same total height' (cause: .ja-btn font-size .875rem + line-height:1 + padding .92rem/.64rem = ~39px; the icon button's svg makes the ghost taller). Visible in slice-light-1: Download CV pill is slightly taller than Show Reel.

Fix

Add `@media (max-width:768px){ .ja-btn{ min-height:48px !important; display:inline-flex; align-items:center; justify-content:center; } }` in revamp.css — one min-height normalizes both the 44px-floor problem and the 39-vs-43 twin mismatch. Give the footer social icon anchors min-width/height 44px too.

mediummobile

Mobile's only work section (~1030px tall lime panel) has zero HTML fallback or accessible text

Evidence

home.css mobile block hides the other two carousels (`#work2,#work3{display:none !important}` at max-width:921px), leaving one #work section: heading 18vh + cylinder 70vh + 60px paddings ≈ 1030 CSS px (measured in shot-mobile-light-full.png: solid lime from ~y2310 to ~y4370 raw). Both the heading (#jaHead) and cards are WebGL canvases — the DOM section contains no text at all (home-rendered.html: `<div id="jaHead" class="ja-head"></div><div class="ja-cyl" id="jaCyl"></div>`). If THREE fails to init (a latent silent-blank failure mode was previously found in these loaders), the entire mobile portfolio showcase is a blank lime rectangle, which is exactly what the headless capture shows. Screen readers get nothing either.

Fix

Add a real `<h2>` (visually hidden, or revealed via a `.no-webgl` body class set when init/THREE fails) plus aria-label on #work, and a static 2-3 image fallback grid that displays only when the renderer fails to start. Invisible in normal operation, rescues the worst-case.

lowmobile

Carousel hot-zone is a vertical-drag scroll trap in the middle of the screen with no affordance

Evidence

home.css mobile block: `.ja-cyl-hot{position:absolute;left:25%;top:30%;width:50%;height:40%;touch-action:none !important}` — a ~195x236px dead-center zone of the 70vh (590px) cylinder where page scrolling is fully blocked (touch-action:none), and photo.js:1359-1371 maps `e.clientY` to wheel spin, so the spin gesture IS the scroll gesture. The JS comment at photo.js:1147-1149 claims 'touch-action:pan-y so vertical swipes still scroll the page' but the CSS overrides it with none. No swipe/spin hint exists anywhere (grep for spin/swipe/hint in photo.js: only that comment). A thumb scrolling through the natural center path of this ~1030px section gets hijacked into spinning cards.

Fix

Either (a) draw a one-time 'swipe to spin' micro-cue near the front card that fades after first interaction, or (b) keep touch-action:pan-y on the hot zone and start the spin only after horizontal intent (dx>dy), letting vertical thumbs scroll through. Verify the chosen behavior on a real device — this is untestable headless.

lowmobile

Sub-11px type on mobile: hero-top container at 9.6px and contact-popup labels at 10px

Evidence

runC.out minFont=10. Source: customcss.css:383 `.ja-hero-top { font-size: 0.6rem }` inside the max-width:768px block (= 9.6px; the .ja-brand-mark/.ja-brand-credit children are rescued to 12px !important by revamp.css:163-167, but the container and `.dot-sep` separators inherit 9.6px), plus revamp.css:805 `body .ja-contact-row .sub` hardcodes `font:600 10px/1 JetBrains Mono` with .1em tracking.

Fix

Delete the 0.6rem override at customcss.css:383 (children are already !important 12px, so it only shrinks separators) and raise .ja-contact-row .sub to 11-12px — 10px uppercase mono with letter-spacing is below comfortable phone legibility.

lowmobile

Hero eyebrow line can sit over a bright busy video frame with only a 35% scrim

Evidence

shot-mobile-dark-full.png (slice-dark--1): the video's bright green 'Pay Bill' sign frame renders directly behind 'ART DIRECTOR · CG GENERALIST · CRAFTING AT CARBON' (12px uppercase, letter-spaced, rgba-white/lime). The overlay gradient at customcss.css:211-217 dips to rgba(10,10,10,0.35) exactly in that middle band (0.7 top → 0.35 mid → 0.9 bottom), so legibility depends on which video frame is playing.

Fix

On mobile, deepen the mid-band of .ja-hero-overlay to ~0.5, or give .ja-hero-sub a subtle text-shadow (0 1px 8px rgba(0,0,0,.6)) so the smallest hero text never fights the video.

lowmobile

Carousel captions and headings are baked into WebGL textures — unselectable, unverifiable, resolution-bound

Evidence

photo.js bakes title at 80px and caption at 44px into canvas textures (`g.fillText(titleLines...)` photo.js:749, desc lines at 56px pitch photo.js:756) rendered as 3D caption meshes; the heading is also canvas (`g.fillText(HEAD_TEXT...)` photo.js:393). On a 390px phone the final on-screen text size depends on mesh scale and card distance — it cannot be read, selected, or zoomed as text, and its legibility could not be verified in this audit (WebGL blank in the headless capture). This flags HOW captions display, not the placeholder content.

Fix

When curating real captions, verify legibility on an actual phone at the cylinder's resting position; if small, consider a DOM caption overlay for the front/bulged card only (keeps the WebGL cards, makes the one readable caption real text).

lowmobile

Mobile drops the dark and blue work panels — section rhythm and category signposting degrade vs desktop

Evidence

Desktop shows three colour-blocked work sections (#work lime #C7F23C, #work2 #0A0A0A, #work3 #3b07ed per home.css:1); mobile hides #work2/#work3 and cycles all pools through one lime cylinder, swapping the WebGL heading between TITLES ['A few motion stuff.','A few recent snaps.','A few CG/VFX things.'] (photo.js) only as blocks shuffle on scroll. A mobile visitor who doesn't linger sees one lime block and may never learn there are three work categories; the lime/dark/blue page rhythm that makes desktop feel rich is gone.

Fix

Use the existing block re-theme hook to fade the #work background lime → #0A0A0A → #3b07ed as the cylinder advances blocks (restores the desktop colour journey in one section), and/or add three tiny HTML progress dots / category label under the cylinder so the structure is legible at a glance.

lowmobile

Character canvas was blank in the light-mode capture; first paint depends entirely on the rAF loop

Evidence

shot-mobile-light-full.png (slice-light-5): the orbiting badge ring rendered but the circle center is empty — no character. The dark capture one minute later (slice-dark--5) rendered him fully, so this is most likely a capture race, but the code makes it possible: home-face-clean.js:155-163 only ever draws via `start()` → requestAnimationFrame loop (drawPlain is reached only under prefers-reduced-motion), so any context where rAF is throttled or hasn't ticked (backgrounded tab during load, snapshot timing) leaves a blank canvas.

Fix

In the frame onload handler, synchronously `drawFigure/drawPlain` the first ready frame once (regardless of reduce) so a static character always exists before the loop takes over; then verify the About block once on a real phone.

lowmobile

Bulb FAB placement is solid (verified both themes); only minor transient overlap with centered CTAs

Evidence

FAB = 50x50 at right:18px, bottom:22px+safe-area (revamp.css:644-664) — at 390px its left edge is x=322. Verified in both captures: dark circle/white bulb in light (slice-light-1), and pixel-sampled (232,234,237) light circle/dark bulb in dark — the inverted surface flips correctly. It exceeds 44px and sits in prime thumb reach. Remaining nit: centered 280px-max buttons span to x≈335, so while scrolling, the FAB covers up to ~13px of a CTA pill's right end when it passes the bottom band; the footer 'Film Am♥r' pill ends at x≈321, clearing it by ~1px.

Fix

No change needed for placement or theming. If the 13px CTA graze ever bothers, cap mobile CTA button max-width at ~264px or nudge FAB bottom to ~18px; purely optional polish.

lowmobile

Dark-mode client logos run very low contrast (Razer, Singapore Tourism Board borderline)

Evidence

slice-dark--2: on the #1a1a1c panel (customcss.css:778) the muted-grey logo treatment leaves the thin-stroke RAZER wordmark and the Singapore Tourism Board mark barely legible at the mobile 22vw/max-86px/34px size (customcss.css:783); Kenvue/7up/CapitaLand read fine. Light mode (slice-light-2, #eeeef0) is acceptable across all nine.

Fix

Bump the dark-mode logo brightness one step (e.g. filter: brightness(1.35) or a lighter grey fill on body.lia-dark .ja-client-logo) so the thinnest marks clear comfortable contrast at 86px width — clients wall is a credibility section, it should never strain.

lowmobile

Hamburger drawer untested in this audit (static captures cannot open it)

Evidence

runC.out confirms the toggle itself is a clean 44x44 target, but the open drawer (off-canvas menu, link sizes, bulb-over-menu styling per revamp.css:337-355) cannot be exercised in a full-page screenshot, so its current state at 390px is unverified in this pass. (It was hand-verified at 412px in an earlier session — flat/no-glass, 20px breathing — but the carousel/heading scripts have shipped several versions since.)

Fix

One interactive device-emulation pass: open the drawer at 390px in both themes, check link tap heights ≥44px, the lime-glass override, and that the FAB doesn't float above the open menu.

lowmobile

Core mobile adaptation is healthy: hero scale, clients grid, about stack, contact, footer all hold up

Evidence

Verified top-to-bottom in both captures: H1 resolves to ~40px over 4 lines and fits with air (slice-light-0); hero is exactly 100vh with full-width 280px CTAs; clients 3x3 at 22vw/max-86px with 38/26px gaps is tidy (slice-light-1/2); About stacks label→lead→body→character→label→body in correct reading order with even spacing around the 298px badge ring; contact H2 floors at 40px over two lines; footer centers icons + Film Am♥r pill + 'Lesmo Studios Pvt Ltd ™' cleanly (slice-light-7/dark--7); hScroll=false and CLS 0 (runC.out). Nothing reads 'shrunken desktop' — the degradations are confined to the findings above.

Fix

No action — recorded so the fix pass doesn't 'improve' sections that are already deliberate and working.

perf-network10

highperf-network

46-frame character sequence (1.66MB, 46 requests) downloads eagerly at page load

Evidence

desktop-light.json: all 46 /wp-content/uploads/lia/jatin-seq/f00..f45.webp requests start at s=4655ms and saturate the connection until 11,094ms — 1,663,580 B transfer, 46 of the 103 total requests. Cause: /tmp/audit/home-face-clean.js:155-163 creates all 46 Image() objects unconditionally at script eval; the About section is ~5 viewports below the fold. These downloads directly contend with the Black__ carousel images (batches at s=6329 and s=8830) and wp-emoji (d=6,124ms for 5KB shows the contention).

Fix

Gate frame loading on an IntersectionObserver on `.lia-abt` with rootMargin ~1500px (same LOAD-EARLY pattern already proven in the 3 carousels). Load in a ladder: f23 (FRONT, the reduced-motion/centre pose) first, then every 4th frame (f00,f04,…,f44 = 12 frames ≈ 430KB), then backfill the rest. Change drawFigure's early-return at home-face-clean.js:102 (`if (!ready(idx)) return;`) to snap to the nearest ready frame so partial loads never stutter. Saves 1.62MB + 46 requests from the initial load window; onload should drop several seconds since the seq currently finishes at ~11.1s.

highperf-network

Hero video is 6.38MB at 1102kbps — re-encode to ≤2.5MB (≤433kbps for its 46.16s)

Evidence

ffprobe of /tmp/audit/hero-src.mp4 (= Showreel_Amor_Optimised.mp4, 6,379,280 B per server-sizes.txt): h264 1280×720 @25fps, 46.16s, 1,102kbps video, NO audio track. desktop-light.json videos[0]: autoplay+muted+loop, readyState 4, bufferedEnd 44 — it fully buffers despite preload=metadata because autoplay forces progressive download. The existing 540p variant (Showreel_Amor-Des_Jatin-Gupta_2025MUTE-540p.mp4, 5,331,611 B ≈ 970kbps) is NOT small enough. In-progress test encodes in /tmp/audit: hero-vp9.webm = 2,952,195 B (537kbps), hero-h264-560.mp4 = 3,307,760 B (600kbps) — both still above 2.5MB.

Fix

≤2.5MB over 46.16s requires ≤433kbps. Two paths: (a) VP9 2-pass at ~420kbps 720p (≈2.4MB; VP9 holds up far better than H.264 at this rate) served as <source type=video/webm> with an H.264 960×540 ~430kbps mp4 fallback for Safari; or (b) trim the loop to ~22-24s and keep H.264 720p at ~850kbps (≈2.4MB, visually safest). The hero sits under .ja-hero-overlay, which masks most low-bitrate artefacts. Saves ~3.9MB of the ~11MB first visit.

highperf-network

Hero video src is inline — it competes with the entire critical path from t≈2.3s; defer src until after load

Evidence

p835.html:22: <video src=…Showreel_Amor_Optimised.mp4 autoplay muted loop preload="metadata" poster=[blank 64×36 black PNG data-URI]>. With autoplay, Chrome starts streaming the full 6.38MB immediately, in parallel with the font (s=2277), CSS (s=3281/3546), three.js (143.7KB) and all images. Throttled run (runE.out, 1.5MB/s): load 14,944ms, transfer counter 6,313,032 B — the video eats roughly half the available bandwidth during the first paint window. The video bytes are ON TOP of the measured 4.85MB page transfer.

Fix

Remove the src attribute; keep poster; a tiny inline script sets video.src + .play() on window 'load' (or FCP + ~1s, whichever first). Replace the current flat-black placeholder poster with a real blurred first-frame webp/jpeg (~20KB) so the pre-motion gap reads as a designed still, not a dead black panel. Combined with perf-network-2 the cold first-paint no longer shares bandwidth with megabytes of video. Visible effect: hero motion starts a few seconds later on cold loads only.

mediumperf-network

Carousel pool webps: 25 Black__ files = 2.71MB, six outliers over 150KB each

Evidence

desktop-light.json: 25 Black__*-1024.webp = 2,707,784 B transfer. Outliers: Black__0023 269,314 B; Black__0022 239,382 B; Black__0002 173,644 B; Black__0007 170,880 B; Black__0003 162,634 B; Black__0021 162,078 B — these six total 1,177,932 B while the pool median is ~90KB. They are 1024px textures for WebGL cards displayed at roughly half that size.

Fix

Recompress the pool server-side with the existing GD pipeline (~/motion-webp.php pattern): imagewebp quality ~72, target ≤120KB per file. The six outliers alone drop to ~600KB (save ~550-580KB); a full-pool pass should land ~1.7-1.9MB (save ~0.8-1.0MB). Bump the file mtimes/transient so lia-revamp.php LQIP cache + lia_carousel_pools regenerate, and purge LiteSpeed. At q72 on 1024px AI-gen imagery shown ~500px wide in motion, the difference is imperceptible.

mediumperf-network

The 15 off-screen shuffle-pool images (1.77MB) download before onload — defer to idle

Evidence

desktop-light.json: Black__ images load in two batches — 10 at s=6329 (940,810 B, the visible 10-card window) and 15 more at s=8830 (1,766,974 B), all before onload at 12,044ms. The second batch exists only to feed the off-screen shuffle (photo.js:1395 "10 visible cards = a window onto the full motion pool", photo.js:1529).

Fix

Trigger the pool-remainder preload on window 'load' + requestIdleCallback instead of immediately after the first 10. The shuffle already has the LQIP-first progressive path as cover if a swap beats a download. Cuts ~1.77MB and 15 requests out of the measured load window — onload should drop ~2-3s on this connection.

lowperf-network

wp-emoji loads on every view (5.2KB script took 6.1s under contention + 3KB inline settings)

Evidence

desktop-light.json: /wp-includes/js/wp-emoji-release.min.js = 5,191 B transfer / 22,762 B decoded, started s=5016, duration 6,124ms (a 5KB file taking 6.1s = proof of connection contention). home-rendered.html also carries the 2,966 B inline wp-emoji-settings script. The site's content needs no emoji shimming; modern browsers render emoji natively.

Fix

In lia-revamp.php add: remove_action('wp_head','print_emoji_detection_script',7); remove_action('wp_print_styles','print_emoji_styles'); remove_action('wp_print_footer_scripts','print_emoji_detection_script'); add_filter('emoji_svg_url','__return_false'). Saves 1 request + ~8KB wire + ~23KB of JS parse, site-wide.

lowperf-network

SureForms payment-history.min.css is render-blocking on the home page and unused

Evidence

home-rendered.html: <link id='srfm-payment-history-css' …payment-history.min.css?ver=2.9.1> in <head>, media='all' → render-blocking. desktop-light.json: 2,085 B transfer / 7,600 B decoded, fetched at s=3438 (d=436ms) inside the pre-first-paint window. No payment-history markup exists anywhere on the home page.

Fix

wp_dequeue_style('srfm-payment-history') + wp_deregister_style on wp_enqueue_scripts priority 100 in lia-revamp.php (gate on lia_is_chrome_page() or apply site-wide — the contact form doesn't render payment history either). Removes one render-blocking request from the critical chain.

lowperf-network

three.js (143.7KB) is only discovered when the parser reaches mid-body — preload it from <head>

Evidence

desktop-light.json: three-0.137.0.min.js = 143,724 B transfer / 618,904 B decoded, async, starts s=3,583 (parser reach time) and takes 1,515ms → THREE is ready ~5.1s. The p835 comment block says the preload block was meant to cover it, but only the image preloads got media-scoped — there is no <link rel=preload as=script> for three.js in the rendered head (only the font and the 10 image preloads).

Fix

Print <link rel="preload" as="script" href="/wp-content/uploads/lia-vendor/three-0.137.0.min.js"> from lia-revamp.php wp_head (front page only). Download then starts ~2.3s alongside the font instead of 3.58s, so the first carousel can initialize ~1.2s earlier. HTTP/2 priorities keep CSS ahead of it; risk is minimal since it is async anyway.

lowperf-network

Client logos + profile pic are served at ~3× display size (~170KB → ~80KB)

Evidence

desktop-light.json imgs[]: all 9 client logos natural width 440px, displayed at 150px (nw:440, dw:150); transfer total 154,826 B (logo-5 alone 39,266 B). jatin-profile.webp 400×400 displayed at 150px, 16,382 B. All load in the first seconds (s=4079, s=3583).

Fix

Re-export logos and the profile pic at 300px wide (2× display) webp — roughly (300/440)² ≈ 53% of current bytes → save ~85-95KB and lighten the pre-FCP window. 2× density keeps them retina-sharp; no visible change.

lowperf-network

TTFB 2.27s on a LiteSpeed cache HIT is connection-setup bound — enable HTTP/3 to shave round-trips

Evidence

headers.txt: `x-litespeed-cache: hit` for / yet nav.ttfb=2,270ms. Baseline per-resource durations show RTT ~400-440ms (favicon 1.9KB → 439ms; payment css 2KB → 436ms), so DNS+TCP+TLS+request ≈ 4-5 RTTs accounts for most of the TTFB — the server itself is fast. headers.txt shows no alt-svc header, suggesting HTTP/3/QUIC is not advertised.

Fix

In Hostinger hPanel / LiteSpeed, enable HTTP/3 (QUIC) so repeat connections use 0/1-RTT setup — worth ~400-800ms on TTFB for far-from-Singapore visitors, the only lever available without a CDN (vetoed). Verify with `curl -sI --http3` or chrome://net-internals. Everything else about the HTML response (brotli, 300s TTL with purge-on-edit) is already sane.

spacing9

mediumspacing

Each desktop carousel grows its section by 173px AFTER init — late layout shift below

Evidence

Live CDP probe at 1440x900: #work and #work2 (initialized) are 967px tall with #jaCyl computed margin-bottom:173px; #work3 (not yet started, beyond the 1500px load margin) is 794px with margin-bottom:0. Source: home-explorations-clean.js line ~501 `cylEl.style.marginBottom=Math.round(Y_OFFSET/6.25*H)+'px'` (Y_OFFSET=2, H=540 → 172.8px), same pattern in all 3 desktop carousel scripts — the margin reserves space for the absolutely-positioned #jaSubs caption canvas (probe: jaSubs spans 1701-2241, hanging 172px below the 540px cylinder). Net effect: About/CTA/footer sit 173px higher until carousel 3 boots, then jump down; with all 3 cold that is up to ~519px of post-load growth. Headless CLS=0 only because the capture never scrolls.

Fix

Bake the caption reserve into CSS so geometry is stable before JS runs: in home.css add `.ja-work-section .ja-cyl{margin-bottom:calc(60vh*0.32)}` (exactly Y_OFFSET/6.25 = 32% of cyl height; tablet override calc(42vh*0.32) for the 922-1400px breakpoint), and change the JS to only set marginBottom if it differs. Desktop-only — the mobile unified carousel adds no margin (mobile probe: work1 1067 = 20+158+789+100, no extra).

mediumspacing

Client logo wall has uneven optical weight — uniform 150x46 boxes vs 6.8:1-to-1.1:1 source aspects

Evidence

revamp.css:781 `body .ja-client-logo{width:clamp(108px,15vw,150px);height:46px;object-fit:contain}` and :783 mobile `width:22vw;max-width:86px;height:34px`. Natural sizes from runA imgs[]: Nickelodeon 440x65, Razer 440x67 (wide marks fill the 150px width at ~22px tall) vs 7up 440x390 and Singapore Tourism Board 440x343 (near-square marks shrink to ~52-60px wide inside the same box). Visible in shot-mobile-light-full.png / dt-1 crop: Colgate and Kenvue read large while STB and Marina Bay Sands read tiny; the 9-logo grid (5+4 desktop, 3x3 mobile, both centered) is geometrically perfect but optically lumpy.

Fix

Optically normalize: give the near-square logos wider boxes or taller render heights (e.g. per-logo CSS `:nth-child` width/height tweaks: 7up and STB height ~58px desktop, MBS ~54px) or re-export the source WebPs cropped to roughly equal optical area, then keep the uniform box. Aim for equal perceived ink, not equal bounding boxes.

lowspacing

Paired pill buttons differ in height (hero: 40px vs 43px; CTA: 39px vs 41px)

Evidence

Mobile probe: Show Reel 288x40 vs Download CV 280x43 stacked centered (runC tap data agrees: 283x39 vs 280x43; CTA pair 281x39 vs 280x41). Cause: primary uses asymmetric padding `.92rem 1.6rem .64rem` (revamp.css:184, optical-centering trick), the icon button gets symmetric `.78rem` top/bottom (revamp.css:192), and ghost adds border — three different computed heights. On mobile the two pills sit 12px apart at identical 280px max-width, so the 3-4px height difference is readable (visible in mb-0 crop: ghost pill slightly fatter). Width delta (288 vs 280) is partly the heartbeat scale animation mid-frame, not layout.

Fix

Give all .ja-btn variants one fixed height (e.g. 44px — also fixes the 39-40px tap targets to meet the 44px minimum) with inline-flex + align-items:center for vertical centering, keeping the existing optical baseline nudge as a 1px translateY on the label span instead of asymmetric padding.

lowspacing

Section vertical rhythm uses three unrelated scales: 106 / 135 / 160px

Evidence

Live probe, all internally symmetric: clients grey panel 106px above the label and 106px visible below the logos (156px padding-bottom minus the deliberate 50px #work lime overlap — customcss.css:1430-1431 + home.css #work margin-top:-50px); About 135px top/bottom (home.css:8 clamp(96px,15vh,180px) → 135 at 900h); CTA 160px top/bottom (customcss.css:760 10rem); footer 48/40 (customcss.css:1049 3rem 2rem 2.5rem). Mobile equivalents: 36 / 84 / 96px. So each section is balanced within itself (good) but the page mixes px, vh and rem units with no shared base.

Fix

Low priority polish: the 106→135→160 progression down the page reads as a gentle crescendo and is defensible — if keeping it, document it; otherwise define one `--sect-pad` token (e.g. 136px desktop / 88px mobile) and derive all four from it so future edits stay rhythmic. Note any change to clients must account for the -50px #work overlap that currently creates the 106/106 symmetry.

lowspacing

Hero grouping: 116px gap headline→subline vs 19px subline→buttons

Evidence

Desktop probe: H1 bottom 527, subline 643-662, buttons 681-724 → 116px vs 19px (mobile identical: 117px vs 20px). Driven by the flex auto-margins (customcss.css:1373 `.ja-hero-h1{margin-top:auto;margin-bottom:auto}` + `.ja-ctas{margin-bottom:auto}`): the headline centers alone while 'ART DIRECTOR · CG GENERALIST · CRAFTING AT CARBON' fuses with the buttons far below. Visible in dt-0/mb-0 crops as a floating headline with an orphaned control cluster.

Fix

Judgement call, not a defect: if the isolated-headline look is intended, keep it. To tighten the editorial grouping, cap the headline→subline gap (e.g. give .ja-hero-sub `margin-top:56px` and move the auto-margin to the subline's bottom or buttons' top) so the H1 owns its subline and the whitespace sits between the text group and the buttons instead.

lowspacing

About right column body copy is right-aligned (ragged-left paragraph) on desktop

Evidence

home.css:38 `@media(min-width:922px){.lia-abt-inner>.lia-abt-col:last-child{text-align:right}}` — confirmed in the dt-abt-cta-footer.png clip capture: the 5-line 'Before I start I want to know why…' paragraph renders ragged-left at the right edge. The mirror placement (cols at x=72 and ending x=1368, character centered between — probe abtCols) is well balanced, but ragged-left body text of this length is measurably harder to read and rare on high-end sites.

Fix

Keep the mirrored position but left-align the copy: on the last .lia-abt-col keep `text-align:right` for the label and rule, and add `.lia-abt-col:last-child .lia-abt-body{text-align:left;margin-left:auto}` (the existing margin-left:auto already pins the 34ch block right).

lowspacing

--lia-maxw:1360px token is silently defeated for 2 of its 3 targets

Evidence

revamp.css:204 `body .ja-clients-inner, body .lia-abt-inner, body .ja-cta{max-width:var(--lia-maxw)!important}` (specificity 0,1,1) loses to customcss.css body.home rules (0,2,1): :1023 `body.home .ja-clients-inner{max-width:1400px!important}` and :1009 breakout `body.home … .ja-cta{max-width:100vw!important}`. Probe confirms computed: clients-inner 1400px, .ja-cta 1440px (=100vw), only .lia-abt-inner gets 1360px. Zero visible harm today (clients track caps at 1000px and CTA content at 800px, both centered), but editing the token does not do what the comment says it does.

Fix

Consolidate so the token is real: either delete the legacy 1400px rule at customcss.css:1023 (clients) and exclude .ja-cta from the revamp token rule (it intentionally needs 100vw for the full-bleed background, with inner content capped separately), or raise the revamp selectors to `body.lia-chrome .ja-clients-inner` etc. so they win. Pure cascade hygiene — no pixel changes expected; verify computed widths stay 1400→1360 (20px narrower clients wrapper is invisible since content is 1000px centered).

lowspacing

Five superseded generations of section-spacing CSS (plus ~45 lines for removed DOM) make every spacing edit a cascade gamble

Evidence

customcss.css carries dead layout regimes all nullified by later rules: :35 `.ja-section{padding:120px 24px}`, :666 `padding:9rem 2.5rem!important`, :1080 `.ja-work-section .ja-section-title{padding:22rem 2.5rem 5rem!important}`, :1097-1098 `14rem`, :1119 mobile `10rem` — final authority is :1490-1492 `padding:0!important` + home.css. No `.ja-section-title`, `.ja-grid3` or `.ja-card` element exists in the rendered DOM (grep of home-rendered.html class attributes: 0 hits) yet :690-755 styles them. This stack is exactly why the 1360px token rule (spacing-7) silently fails and why the 22rem ghost padding looks alarming in audits.

Fix

One pruning pass over the Additional CSS via wp-cli (per the established `wp eval` + file backup workflow): delete the dead .ja-section padding generations, .ja-section-title/.ja-grid3/.ja-card blocks and superseded .ja-clients rules, keeping only the live final regime. Take the customcss backup first and verify with a before/after computed-style diff on the live page (the CDP probe in this audit is reusable as the verifier).

lowspacing

Mobile left gutter drifts: 20 / 22 / 32 / 36px across sections (20px is the standard)

Evidence

Mobile probe computed paddings: hero content 20px (customcss) and CTA 20px (customcss:813-814 1.25rem), but About 22px (home.css:17 `padding:84px 22px`), clients inner 32px + track inset 36px (logos start x=48), footer 32px. The only visible victim is the About text rail: its left-aligned labels/lead/body sit at x=22 while the CTA heading box sits at x=20 — a 2px drift between the page's left-aligned text blocks. The 32/36px cases are centered content (label text-align center, logo grid centered) so they read fine.

Fix

Snap About to the 20px standard: home.css mobile block `.lia-abt{padding:84px 20px}`. Leave the centered clients/footer insets as-is (changing them moves nothing perceptible). Smallest finding here — bundle it with any other home.css edit rather than shipping alone.

perf-render8

highperf-render

93.5KB carousel-pool inline script sits BEFORE every stylesheet, delaying CSS discovery ~1s

Evidence

home-rendered.html: the pool script (window.__liaMF=Promise.resolve(...)) occupies decoded offsets 4,693-98,242 (93,549B; 95 lqip base64 strings = 71,812B of it, so it gzips to only ~52KB). The FIRST stylesheet link (astra main.min.css) is at offset 98,454 — it starts s=3281, i.e. 1,011ms after TTFB 2270; revamp.css is at offset 249,672 (end of head) — s=3546 (+1,276ms). The head is 250,549B of the 281,127B document (89%); head WITHOUT the blob gzips 83KB→30KB. Cause: lia-revamp.php:182-189 prints the pools at wp_head priority 6, before wp_print_styles at priority 8.

Fix

Move the pool <script> out of the head: safest is wp_body_open (still upstream of its only consumers — the mid-body home-photography/cgvfx/explorations-clean scripts — so document-order semantics are untouched); best is wp_footer AFTER verifying those scripts read window.__liaMF inside their IO-deferred start() rather than at eval time. Expected: astra css discovery TTFB+~50ms (−950ms), revamp.css −800-900ms, FP from 4116 → ~3.2-3.4s. Bonus: strip the lqip fields (~50KB compressed) from pools whose consumers already use the JS-baked blur data-URIs.

highperf-render

FP→FCP gap of ~1.9s is bandwidth-INDEPENDENT — main thread is pegged after the first frame

Evidence

Desktop: FP 4116 → FCP 6048 (Δ1932ms). Throttled 1.5MB/s (runE.out): FP 4036 → FCP 5952 (Δ1916ms — identical despite throttling). Mobile (runC.out): 3572 → 6308 (Δ2736ms). The font is NOT the blocker: preload + font-display:swap verified at revamp.css:9-15, woff2 finished 2277+1351=3628 < FP. What fills the gap: parsing 144KB of inline head CSS (astra-inline 61,064B + wp-custom-css 68,113B + global-styles 14,627B), the mid-body parser-blocking chain (home.css link gating 9 sync scripts → DCL 4979), then three.js (618,904B decoded, download done ~5098) eval plus synchronous carousel WebGL/texture boot before the next — first contentful — frame commits at 6048.

Fix

Free the main thread right after first paint: (a) add defer to the non-carousel mid-body scripts (home-parallax, home-reelmodal, home-contact, home-face-clean, astra frontend.min, revamp.js); (b) defer carousel boot to requestIdleCallback / first-rAF-after-load instead of synchronously after three.js eval (the LOAD/RENDER IO split already exists — shift the WebGL context creation behind it); (c) do perf-render-4. Expected FCP −1.0-1.6s, landing near FP+300-500ms.

mediumperf-render

First script in <head> fires 3 REST fetches whose results are ALWAYS discarded

Evidence

home-rendered.html line 8 (offset 267, the very first script): defines window.__liaMF=J("Black"); __liaVF=J("vfx"); __liaPF=J("Photography") via fetch('/wp-json/wp/v2/media?search=...'). The pool inline at offset 4,693 then REASSIGNS the same three globals to Promise.resolve(<inlined JSON>) before any consumer runs, so the fetched data is never read. Waterfall: 3 wp-json requests at s=2281 (instantly at TTFB), each d≈1.3s, 9.1KB transfer / 101KB decoded — 3 of the 103 requests + 3 PHP REST executions per view, competing during the critical HTML-stream window. The script is NOT in p835.html, revamp.js, or the Customizer CSS — it is injected by some other hook (grep server mu-plugins/theme for fromCharCode(38)).

Fix

Find and delete the injector (search wp-content/mu-plugins and theme functions for 'fromCharCode(38)' / '__liaMF=J'). Zero functional risk: the globals are overwritten 4KB later in the same document and the carousel scripts keep their own REST fallback. Saves 3 requests, ~1.3s of PHP REST work per view, and head bytes ahead of the font preload.

mediumperf-render

The promised Three.js preload does not exist — the 143KB vendor lib starts late and gates all 3 carousels

Evidence

p835.html preload-block comment says 'self-hosted Three.js … Three.js preload is unscoped because the desktop benefits too', but the rendered HTML contains ZERO as="script" preloads (grep count 0; the only preload is the font). three-0.137.0.min.js is an async mid-body script discovered only at s=3583 with d=1515 → available ~5098, then 618,904B of eval; every carousel waits on it.

Fix

Add <link rel="preload" as="script" href="/wp-content/uploads/lia-vendor/three-0.137.0.min.js"> to the wp_head priority-1 injector in lia-revamp.php (lines 40-44, beside the font preload), front-page-gated. Download starts at ~TTFB instead of after the full HTML stream → ready ~1.3s earlier, pulling carousel first-frame earlier. Also fix the stale comment in p835.

lowperf-render

home.css is a render/parser-gating stylesheet in the MIDDLE of <body>

Evidence

Rendered line 1997: <div class="ja-work-stack"><link rel="stylesheet" href="/wp-content/uploads/lia/home.css?v=18">…<script src=home-photography-clean.js>. A body <link> is only discovered once the full 108KB HTML has streamed (s=3583, 1,313ms after TTFB); it blocks paint of all content after it, and the sync scripts behind it cannot execute until it loads (done ~4046). Size: 2,015B br / 5,285B decoded.

Fix

Move the home.css <link> into wp_head (front-page-gated, next to the revamp.css echo in lia-revamp.php) or inline its 5.3KB as a <style>. Today it co-terminates with revamp.css so it's hidden; after perf-render-1 lands it would become the new first-paint long-pole. Saves ~100-450ms on the script-chain start and removes the ordering fragility.

lowperf-render

144KB of theme CSS is inlined into every HTML response (max-age 300) instead of cached files

Evidence

Head inline styles in home-rendered.html: astra-theme-css-inline-css 61,064B (gzip 8,735), wp-custom-css 68,113B (gzip 15,294), global-styles-inline-css 14,627B — ~24KB compressed re-downloaded with every page view because the HTML is cached only max-age=300 (headers.txt) while static assets get 7-30 days. This inline bulk is why the head is 89% of the document and why revamp.css discovery needs the whole head to stream.

Fix

Enable Astra's 'Load dynamic CSS in external file' option (kills the 61KB inline block); optionally serve the 68KB Customizer CSS as a versioned external file the way revamp.css is served (dequeue wp_custom_css_cb + print a link) — do that one with a full backup and instant-revert plan, since Customizer-CSS surgery has caused site-wide regressions before. Net: ~24KB less per HTML fetch, shorter stream-to-last-blocking-CSS.

lowperf-render

Head debris: render-blocking SureForms payment CSS, wp-emoji, and dead Google-Fonts preconnects

Evidence

payment-history.min.css render-blocks in head (s=3438, d=436, 2,085B br) on a page with no payment UI. wp-emoji-release.min.js loads from head (s=5016, d=6124) plus a 340B emoji inline style and inline detection script. Two preconnects to fonts.googleapis.com / fonts.gstatic.com sit at head offsets 3947/4009 even though Google Fonts are dequeued (lia-revamp.php:48-52) — zero google-font requests appear anywhere in the waterfall.

Fix

On lia_is_chrome_page(): wp_dequeue_style the srfm payment-history handle; disable WP emoji (remove_action print_emoji_detection_script / print_emoji_styles); remove the two preconnect resource hints via the wp_resource_hints filter. One fewer blocking stylesheet, one fewer request, ~1KB less head.

lowperf-render

Speculation-rules block is WP core conservative prefetch — harmless to home render, leave it

Evidence

Rendered HTML body end (offset 272,164): {"prefetch":[{"source":"document","where":{...},"eagerness":"conservative"}]} — WordPress core speculative loading. 'Conservative' only prefetches a same-origin page when a link is hovered/pressed, and it excludes /wp-content/*, /wp-admin/*, query-string URLs, and nofollow links. No speculation-triggered requests appear in the home waterfall — it costs the home render nothing.

Fix

No action. It gives free fast navigation to About/Gallery/Contact. Do NOT raise eagerness to 'moderate' — the home page already saturates the slow origin with its own 103 requests.