PageSourceSearch

https://portfolio.gateaux-09suntan.workers.dev/homemove/

html portfolio.gateaux-09suntan.workers.dev collected 2026-10-03 08:09:19 UTC 66,881 bytes, 606 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4  <meta charset="UTF-8" />
5  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
7  <!-- Microsoft Clarity -->
8  
8<script type="text/javascript">
9    (function(c,l,a,r,i,t,y){
10        c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
11        t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
12        y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
13    })(window, document, "clarity", "script", "xb1o8ifxda");
14  </script>
14
15
16  <title>Orchestrated Clarity In Chaos · Homemove · John Miller</title>
17  <meta name="description" content="How I redesigned Homemove's home-moving platform using OOUX — shifting from a service-first dashboard to a timeline-first task hierarchy." />
18  <!-- Favicon — the href is set by the script below the theme bootstrap, so it
19       follows the RESOLVED theme (manual data-theme override > OS preference)
20       rather than prefers-color-scheme alone. -->
21  <link rel="icon" id="favicon" href="/favicon.png">
22  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
23  <link rel="preload" href="/fonts/geist-latin.woff2" as="font" type="font/woff2" crossorigin>
24
25  <!-- LCP image: preload the responsive hero so it starts downloading during HTML
26       parse. Static pre-generated AVIF (scripts/generate-image-variants.mjs) — no
27       live /cdn-cgi/ transform, so no cold-start latency and no /cdn-cgi/ blocker
28       risk. type="image/avif" gates it to AVIF-capable browsers; the rest fall
29       through the <picture> (WebP → PNG). -->
30  <link
31    rel="preload"
32    as="image"
33    type="image/avif"
34    href="/homemove-mockup-1280.avif"
35    imagesrcset="/homemove-mockup-768.avif 768w, /homemove-mockup-1280.avif 1280w, /homemove-mockup-2880.avif 2880w"
36    imagesizes="(max-width: 767px) 85vw, 1280px"
37    fetchpriority="high">
38
39  <!-- Theme bootstrap — see index.html for context. Inline + tiny on purpose
40       so the saved theme applies before stylesheets paint. -->
41  
41<script>
42    (function () {
43      try {
44        var saved = localStorage.getItem('theme');
45        var theme = (saved === 'light' || saved === 'dark') ? saved : 'auto';
46        document.documentElement.setAttribute('data-theme', theme);
47      } catch (e) {
48        document.documentElement.setAttribute('data-theme', 'auto');
49      }
50    })();
51  </script>
51
52
53  <!-- Favicon swap. MUST stay below the theme bootstrap above: it reads the
54       resolved data-theme, so running it earlier (when the attribute is still
55       unset) silently falls back to prefers-color-scheme — the dark favicon on
56       a dark OS even when the user has manually switched the page to light.
57       Re-runs on OS-scheme change and the theme toggle (themechange). -->
58  
58<script>
59    (function () {
60      var fav = document.getElementById('favicon');
61      function set() {
62        var t = document.documentElement.getAttribute('data-theme');
63        var dark = t === 'dark' || (t !== 'light' && matchMedia('(prefers-color-scheme: dark)').matches);
64        fav.href = dark ? '/favicon-dark.png' : '/favicon.png';
65      }
66      set();
67      matchMedia('(prefers-color-scheme: dark)').addEventListener('change', set);
68      window.addEventListener('themechange', set);
69    })();
70  </script>
70
71
72  <!-- Mobile status-bar tint (the strip behind the clock/battery). Sets
73       <meta name="theme-color"> from the --color-bg token, so the OS status bar
74       always matches the page background — and on subpages meets the hero
75       shader's faded top edge with no seam. Sourced live from the token (single
76       source of truth, OKLCH — no duplicated colour to keep in sync); re-synced
77       on OS-scheme change and the theme toggle (themechange). -->
78  
78<script>
79    (function () {
80      var meta = document.createElement('meta');
81      meta.name = 'theme-color';
82      document.head.appendChild(meta);
83      function paint() {
84        var bg = getComputedStyle(document.documentElement)
85          .getPropertyValue('--color-bg').trim();
86        if (bg) meta.content = bg;
87      }
88      // tokens.css is render-blocking, so --color-bg resolves by the first frame.
89      requestAnimationFrame(paint);
90      matchMedia('(prefers-color-scheme: dark)').addEventListener('change', paint);
91      window.addEventListener('themechange', paint);
92    })();
93  </script>
93
94
95  <!-- Warms up the DNS/TCP/TLS handshake to the Cal.com booking embed used by
96       the "Book a call" button in the Next-steps CTA card below; see book-call.js. -->
97  <link rel="preconnect" href="https://app.cal.eu">
98  <link rel="dns-prefetch" href="https://app.cal.eu">
99
100  <!-- Shader engine is self-hosted (shader-base.js → /vendor/paper-shaders-<ver>.min.js).
101       Preload the module AND its bundled engine so a fresh tab fetches both in
102       parallel during HTML parse — instead of discovering them late and stalling
103       the entrance animation while the engine downloads. -->
104  <link rel="modulepreload" href="/shader-base.js?v=1">
105  <link rel="modulepreload" href="/vendor/paper-shaders-0.0.76.min.js">
106
107  <style>:root{color-scheme:light dark;--color-bg: oklch(.97 .001 106.424);--color-bg-secondary: oklch(.985 .001 106.423);--color-bg-secondary-hover: oklch(1 0 0);--color-bg-secondary-pressed:oklch(.97 .001 106.424);--about-card-surface: var(--color-bg-secondary);--color-bg-inverted: oklch(0 0 0);--color-bg-primary: rgb(12, 10, 9);--color-bg-alpha: oklch(.147 .004 49.25 / .031);--color-bg-behind: oklch(.985 .001 106.423);--color-bg-browser: oklch(.985 .001 106.423 / .502);--color-text-primary: oklch(.147 .004 49.25);--color-text-secondary: oklch(.444 .01 73.639);--color-text-muted: oklch(.716 .009 56.259);--color-text-primary-inverted: oklch(.985 .001 106.423);--color-text-title: oklch(.147 .004 49.25);--color-text-disabled: oklch(.444 .01 73.639);--color-text-secondary-alpha: oklch(.444 .01 73.639);--color-text-secondary-inverted: oklch(.716 .009 56.259);--color-icon-primary: var(--color-text-secondary);--color-icon-secondary: var(--color-text-muted);--color-icon-strong: var(--color-text-primary);--color-icon-hover: var(--color-icon-strong);--color-fg-primary: oklch(.444 .01 73.639);--color-fg-secondary: oklch(.716 .009 56.259);--color-fg-primary-alpha: oklch(.147 .004 49.25 / .7);--color-fg-secondary-alpha: oklch(.147 .004 49.25 / .4);--color-fg-secondary-inverted: oklch(.444 .01 73.639);--color-fg-tertiary-alpha: oklch(.147 .004 49.25 / .102);--color-fg-muted-alpha: oklch(.147 .004 49.25 / .6);--color-border: oklch(.923 .003 48.717);--color-border-hover: oklch(.869 .004 56.366);--color-border-bg: oklch(.923 .003 48.717);--color-border-bg-hover: oklch(.923 .003 48.717);--color-border-inverted: oklch(.216 .006 56.043);--color-border-secondary-alpha: oklch(.147 .004 49.25 / .078);--color-focus-ring: #0C0A09;--color-selection-bg: var(--color-border-hover);--color-selection-text: var(--color-text-primary);--color-bd: oklch(.52 .095 194.8);--color-bd-bg: oklch(.52 .095 194.8 / .161);--color-bd-border: oklch(.52 .095 194.8 / .502);--color-hm: oklch(.665 .223 21.421);--color-hm-bg: oklch(.665 .223 21.421 / .161);--color-hm-border: oklch(.665 .223 21.421 / .502);--color-pikl: oklch(.58 .213 2.613);--color-pikl-bg: oklch(.58 .213 2.613 / .161);--color-pikl-border: oklch(.58 .213 2.613 / .502);--color-wren: oklch(.654 .176 151.021);--color-wren-bg: oklch(.654 .176 151.021 / .161);--color-wren-border: oklch(.654 .176 151.021 / .502);--color-wren-text: oklch(.513 .121 152.545);--color-pikl-ds: oklch(.62 .19 251.1);--color-pikl-ds-bg: oklch(.62 .19 251.1 / .161);--color-pikl-ds-border: oklch(.62 .19 251.1 / .502);--color-white-alpha-5: oklch(1 0 0 / .05);--color-white-alpha-10: oklch(1 0 0 / .1);--color-white-alpha-20: oklch(1 0 0 / .2);--color-white-alpha-30: oklch(1 0 0 / .3);--color-shadow: oklch(.147 .004 49.25 / .02);--color-highlight: oklch(1 0 0);--color-success-fg: oklch(.741 .222 144.737);--color-failure-fg: oklch(.655 .232 28.997);--color-warning-fg: oklch(.765 .175 62.574);--color-informational-fg: oklch(.603 .218 257.424);--color-special-fg: oklch(.603 .195 309.108);--color-success-bg-alpha: oklch(.744 .224 144.612 / .122);--color-failure-bg-alpha: oklch(.655 .232 29.178 / .122);--color-warning-bg-alpha: oklch(.765 .175 62.574 / .122);--color-informational-bg-alpha: oklch(.605 .217 257.214 / .122);--color-special-bg-alpha: oklch(.601 .196 309.226 / .122);--color-success-text: oklch(.512 .163 144.52);--color-failure-text: oklch(.553 .225 27.269);--color-warning-text: oklch(.552 .192 35.326);
107--color-informational-text: oklch(.463 .241 263.288);--color-special-text: oklch(.553 .161 324.469);--color-success-border: oklch(.741 .222 144.737);--color-failure-border: oklch(.655 .232 28.997);--color-warning-border: oklch(.87 .11 74.759);--color-informational-border: oklch(.783 .114 251.286);--color-special-border: oklch(.752 .121 310.907)}@font-face{font-family:Geist;font-style:normal;font-weight:400 600;font-display:fallback;src:url(/fonts/geist-latin.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--font-sans: "Geist", sans-serif;--font-weight-regular: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-smoothing-webkit: antialiased;--font-smoothing-moz: grayscale;--type-h1: 40px;--type-h2: 24px;--type-h2nc: 18px;--type-h3: 18px;--type-body: 18px}@media(min-width:810px)and (max-width:1199px){:root{--type-h1: 28px;--type-h2: 18px;--type-h2nc: 18px;--type-h3: 16px;--type-body: 16px}}@media(max-width:809px){:root{--type-h1: 28px;--type-h2: 18px;--type-h2nc: 18px;--type-h3: 16px;--type-body: 16px}}:root{--space-1: 4px;--space-1-5: 6px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--space-16: 64px;--space-20: 80px;--space-24: 96px;--space-32: 128px;--radius-btn: 20px;--card-padding: 4px;--radius: 40px;--radius-pill: 99px;--button-height: 48px;--button-padding-inline: 16px;--button-gap: var(--space-2);--shadow-ring: oklch(.147 .004 49.25 / .08);--shadow-ring-hover: oklch(.147 .004 49.25 / .18);--shadow-ring-pressed: var(--shadow-ring);--shadow-layer: oklch(.147 .004 49.25 / .06);--shadow-ring-inverted: rgba(250, 250, 249, .16);--shadow-ring-inverted-hover: rgba(250, 250, 249, .28);--shadow-with-ring-inverted: var(--shadow-ring-inverted) 0px 0px 0px 1px, var(--shadow-layer) 0px 1px 1px -.5px, var(--shadow-layer) 0px 3px 3px -1.5px, var(--shadow-layer) 0px 6px 6px -3px;--shadow-btn-hover-inverted: var(--shadow-ring-inverted-hover) 0px 0px 0px 1px, var(--shadow-layer) 0px 2px 2px -1px, var(--shadow-layer) 0px 6px 6px -3px, var(--shadow-layer) 0px 12px 12px -6px;--shadow-btn-pressed-inverted: var(--shadow-ring-inverted) 0px 0px 0px 1px, var(--shadow-layer) 0px 1px 1px -.5px;--shadow-card-outline: oklch(.147 .004 49.25 / .08);--shadow-card-outline-hover: oklch(.147 .004 49.25 / .18);--inner-frame-border: var(--shadow-ring);--inner-frame-light: var(--shadow-ring);--avatar-border: var(--shadow-ring);--shadow-with-ring: var(--shadow-ring) 0px 0px 0px 1px, var(--shadow-layer) 0px 1px 1px -.5px, var(--shadow-layer) 0px 3px 3px -1.5px, var(--shadow-layer) 0px 6px 6px -3px;--shadow-card: var(--shadow-ring) 0px 1px 0px 0px, var(--shadow-layer) 0px 1px 1px -.5px, var(--shadow-layer) 0px 3px 3px -1.5px, var(--shadow-layer) 0px 6px 6px -3px, var(--shadow-layer) 0px 12px 12px -6px;--shadow-mockup: var(--shadow-layer) 0px 1px 1px -.5px, var(--shadow-layer) 0px 3px 3px -1.5px, var(--shadow-layer) 0px 6px 6px -3px, var(--shadow-layer) 0px 12px 12px -6px;--shadow-inset-highlight: inset 0px 1px 2px 0px oklch(1 0 0);--shadow-btn-hover: var(--shadow-ring-hover) 0px 0px 0px 1px, var(--shadow-layer) 0px 2px 2px -1px, var(--shadow-layer) 0px 6px 6px -3px, var(--shadow-layer) 0px 12px 12px -6px;--shadow-btn-pressed: var(--shadow-ring-pressed) 0px 0px 0px 1px, var(--shadow-layer) 0px 1px 1px -.5px;--shadow-mockup-hover: var(--shadow-layer) 0px 2px 2px -1px, var(--shadow-layer) 0px 6px 6px -3px, var(--shadow-layer) 0px 12px 12px -6px, var(--shadow-layer) 0px 20px 20px -10px;--shadow-card-hover: var(--shadow-ring-hover) 0px 1px 0px 0px, var(--shadow-layer) 0px 2px 2px -1px, var(--shadow-layer) 0px 6px 6px -3px, var(--shadow-layer) 0px 12px 12px -6px, var(--shadow-layer) 0px 20px 20px -10px;--shadow-card-pressed: var(--shadow-ring-pressed) 0px 1px 0px 0px, var(--shadow-layer) 0px 1px 1px -.5px, var(--shadow-layer) 0px 2px 2px -1px;--shadow-card-opacity: 1}@media(max-width:809px){:root{--radius: 32px}}@supports not (corner-shape: superellipse(1.4)){:root{--radius-btn: 16px;--radius: 32px}@media(max-width:809px){:root{--radius: 24px}}}:root{--page-outer-padding: var(--space-8);--content-indent: var(--page-outer-padding);--card-header-padding: var(--space-8)}@media(min-width:810px)and (max-width:1199px){:root{--page-outer-padding: var(--space-6);--card-header-padding: var(--space-6)}}@media(max-width:809px){:root{--page-outer-padding: var(--space-5);--card-header-padding: var(--space-5)}}:root{--anim-enter-duration: .8s;--anim-enter-easing: cubic-bezier(.23, 1, .32, 1);--anim-enter-rest-opacity: .001;--anim-interactive-duration: .15s;--anim-interactive-easing: cubic-bezier(.2, 0, 0, 1)}@media(prefers-color-scheme:dark){:root:not([data-theme=light]){--color-bg: rgb(0, 0, 0);--color-bg-secondary: rgb(12, 10, 9);--color-bg-secondary-hover: rgb(41, 37, 36);--color-bg-secondary-pressed:rgb(12, 10, 9);--about-card-surface: var(--color-bg-secondary);--color-bg-inverted: rgb(245, 245, 244);--color-bg-primary: oklch(.985 .001 106.423);--color-bg-alpha: rgba(255, 255, 255, .1);--color-bg-behind: rgb(0, 0, 0);--color-bg-browser: rgba(28, 25, 23, .5);--color-text-primary: rgb(250, 250, 249);--color-text-secondary: rgb(168, 162, 158);--color-text-muted: rgb(87, 83, 78);--color-text-primary-inverted: rgb(12, 10, 9);--color-text-title: rgb(0, 0, 0);--color-text-disabled: rgb(87, 83, 78);--color-text-secondary-alpha: rgba(250, 250, 249, .6);--color-text-secondary-inverted: rgb(87, 83, 78);--color-fg-primary: rgb(168, 162, 158);--color-fg-secondary: rgb(87, 83, 78);--color-fg-primary-alpha: rgba(250, 250, 249, .9);--color-fg-secondary-alpha: rgba(250, 250, 249, .4);--color-fg-secondary-inverted: rgb(168, 162, 158);--color-fg-tertiary-alpha: rgba(250, 250, 249, .2);--color-fg-muted-alpha: rgba(250, 250, 249, .6);--color-border: rgb(28, 25, 23);--color-border-hover: rgb(68, 64, 60);--color-border-bg: rgb(41, 37, 36);--color-border-bg-hover: rgb(68, 64, 60);--color-border-inverted: rgb(231, 229, 228);--color-border-secondary-alpha: rgba(250, 250, 249, .08);--color-focus-ring: rgb(250, 250, 249);--color-bd: rgb(22, 188, 187);--color-bd-bg: rgba(22, 188, 187, .2);--color-bd-border: rgba(22, 188, 187, .75);--color-hm: rgb(255, 67, 83);--color-hm-bg: rgba(255, 67, 83, .2);--color-hm-border: rgba(255, 67, 83, .75);--color-pikl: rgb(214, 38, 111);--color-pikl-bg: rgba(214, 38, 111, .2);--color-pikl-border: rgba(212, 37, 110, .75);--color-wren: rgb(0, 173, 84);--color-wren-bg: rgba(37, 177, 98, .2);--color-wren-border: rgba(37, 177, 98, .75);--color-wren-text: rgb(28, 122, 67);--color-pikl-ds: rgb(10, 132, 255);--color-pikl-ds-bg: rgba(10, 132, 255, .2);--color-pikl-ds-border: rgba(10, 132, 255, .75);--color-shadow: rgba(12, 10, 9, .1);--color-highlight: rgba(255, 255, 255, .12);--shadow-ring: rgba(250, 250, 249, .16);--shadow-ring-hover: rgba(250, 250, 249, .28);--shadow-ring-inverted: oklch(.147 .004 49.25 / .08);--shadow-ring-inverted-hover: oklch(.147 .004 49.25 / .18);--shadow-card-outline: oklch(1 0 0 / .16);--shadow-card-outline-hover: oklch(1 0 0 / .32);--shadow-inset-highlight: inset 0px 1px 2px 0px rgba(255, 255, 255, .12);--color-success-fg: rgb(50, 215, 75);--color-failure-fg: rgb(255, 69, 58);
107--color-warning-fg: rgb(255, 159, 11);--color-informational-fg: rgb(11, 132, 255);--color-special-fg: rgb(178, 96, 234);--color-success-bg-alpha: rgba(51, 215, 75, .16);--color-failure-bg-alpha: rgba(255, 66, 56, .2);--color-warning-bg-alpha: rgba(255, 157, 10, .2);--color-informational-bg-alpha: rgba(10, 133, 255, .2);--color-special-bg-alpha: rgba(179, 97, 234, .2);--color-success-text: rgb(50, 222, 75);--color-failure-text: rgb(255, 104, 97);--color-warning-text: rgb(255, 179, 63);--color-informational-text: rgb(65, 156, 255);--color-special-text: rgb(207, 146, 248);--color-success-border: rgb(29, 175, 51);--color-failure-border: rgb(204, 10, 0);--color-warning-border: rgb(255, 159, 11);--color-informational-border: rgb(11, 132, 255);--color-special-border: rgb(178, 96, 234)}}:root[data-theme=dark]{color-scheme:dark;--color-bg: rgb(0, 0, 0);--color-bg-secondary: rgb(12, 10, 9);--color-bg-secondary-hover: rgb(41, 37, 36);--color-bg-secondary-pressed:rgb(12, 10, 9);--about-card-surface: var(--color-bg-secondary);--color-bg-inverted: rgb(245, 245, 244);--color-bg-primary: oklch(.985 .001 106.423);--color-bg-alpha: rgba(255, 255, 255, .1);--color-bg-behind: rgb(0, 0, 0);--color-bg-browser: rgba(28, 25, 23, .5);--color-text-primary: rgb(250, 250, 249);--color-text-secondary: rgb(168, 162, 158);--color-text-muted: rgb(87, 83, 78);--color-text-primary-inverted: rgb(12, 10, 9);--color-text-title: rgb(0, 0, 0);--color-text-disabled: rgb(87, 83, 78);--color-text-secondary-alpha: rgba(250, 250, 249, .6);--color-text-secondary-inverted: rgb(87, 83, 78);--color-fg-primary: rgb(168, 162, 158);--color-fg-secondary: rgb(87, 83, 78);--color-fg-primary-alpha: rgba(250, 250, 249, .9);--color-fg-secondary-alpha: rgba(250, 250, 249, .4);--color-fg-secondary-inverted: rgb(168, 162, 158);--color-fg-tertiary-alpha: rgba(250, 250, 249, .2);--color-fg-muted-alpha: rgba(250, 250, 249, .6);--color-border: rgb(28, 25, 23);--color-border-hover: rgb(68, 64, 60);--color-border-alpha: rgba(250, 250, 249, .12);--color-border-bg: rgb(41, 37, 36);--color-border-bg-hover: rgb(68, 64, 60);--color-border-alpha-hover: rgba(250, 250, 249, .24);--color-border-inverted: rgb(231, 229, 228);--color-border-secondary-alpha: rgba(250, 250, 249, .08);--color-focus-ring: rgb(250, 250, 249);--color-bd: rgb(22, 188, 187);--color-bd-bg: rgba(22, 188, 187, .2);--color-bd-border: rgba(22, 188, 187, .75);--color-hm: rgb(255, 67, 83);--color-hm-bg: rgba(255, 67, 83, .2);--color-hm-border: rgba(255, 67, 83, .75);--color-pikl: rgb(214, 38, 111);--color-pikl-bg: rgba(214, 38, 111, .2);--color-pikl-border: rgba(212, 37, 110, .75);--color-wren: rgb(0, 173, 84);--color-wren-bg: rgba(37, 177, 98, .2);--color-wren-border: rgba(37, 177, 98, .75);--color-wren-text: rgb(28, 122, 67);--color-pikl-ds: rgb(10, 132, 255);--color-pikl-ds-bg: rgba(10, 132, 255, .2);--color-pikl-ds-border: rgba(10, 132, 255, .75);--color-shadow: rgba(12, 10, 9, .1);--color-highlight: rgba(255, 255, 255, .12);--shadow-ring: rgba(250, 250, 249, .16);--shadow-ring-hover: rgba(250, 250, 249, .28);--shadow-ring-inverted: oklch(.147 .004 49.25 / .08);--shadow-ring-inverted-hover: oklch(.147 .004 49.25 / .18);--shadow-card-outline: oklch(1 0 0 / .16);--shadow-card-outline-hover: oklch(1 0 0 / .32);--shadow-inset-highlight: inset 0px 1px 2px 0px rgba(255, 255, 255, .12);--color-success-fg: rgb(50, 215, 75);--color-failure-fg: rgb(255, 69, 58);--color-warning-fg: rgb(255, 159, 11);--color-informational-fg: rgb(11, 132, 255);--color-special-fg: rgb(178, 96, 234);--color-success-bg-alpha: rgba(51, 215, 75, .16);--color-failure-bg-alpha: rgba(255, 66, 56, .2);--color-warning-bg-alpha: rgba(255, 157, 10, .2);--color-informational-bg-alpha: rgba(10, 133, 255, .2);--color-special-bg-alpha: rgba(179, 97, 234, .2);--color-success-text: rgb(50, 222, 75);--color-failure-text: rgb(255, 104, 97);--color-warning-text: rgb(255, 179, 63);--color-informational-text: rgb(65, 156, 255);--color-special-text: rgb(207, 146, 248);--color-success-border: rgb(29, 175, 51);--color-failure-border: rgb(204, 10, 0);
107--color-warning-border: rgb(255, 159, 11);--color-informational-border: rgb(11, 132, 255);--color-special-border: rgb(178, 96, 234)}:root[data-theme=light]{color-scheme:light}[data-project=bella-duke]{--color-selection-bg: var(--color-bd-bg)}[data-project=homemove]{--color-selection-bg: var(--color-hm-bg)}[data-project=pikl]{--color-selection-bg: var(--color-pikl-bg)}[data-project=wren]{--color-selection-bg: var(--color-wren-bg)}[data-project=pikl-ds]{--color-selection-bg: var(--color-pikl-ds-bg)}</style>
108  <link rel="stylesheet" href="../vendor/photoswipe/photoswipe.css?v=1">
109  <link rel="stylesheet" href="../components.css?v=284">
110  <link rel="stylesheet" href="../subpage.css?v=20" />
111  
111<script src="../vendor/motion.min.js?v=1" defer></script>
111
112  
112<script src="../animations.js?v=skel4" defer></script>
112
113  
113<script src="../labelled-section.js?v=1" defer></script>
113
114  
114<script src="../contact-button-group.js?v=1" defer></script>
114
115  
115<script src="../next-project-card.js?v=1" defer></script>
115
116  
116<script src="../card-blur.js?v=22" defer></script>
116
117  
117<script src="../next-steps-cta.js?v=1" defer></script>
117
118  
118<script>
119    (function(){function l(){var s=document.createElement("script");s.src="../book-call.js?v=35";document.head.appendChild(s)}if("requestIdleCallback"in window){requestIdleCallback(l,{timeout:3000})}else{window.addEventListener("load",l)}})();
120  </script>
120
121
122  <!-- ThumbHash placeholders -->
123  
123<script type="module" src="../thumbhash-loader.js?v=skel10"></script>
123
124
125  <!-- iOS-style 60% corner smoothing (squircles) on safe rounded elements -->
126
127  <!-- PhotoSwipe lightbox (single-image mode); stylesheet is loaded earlier
128       in <head> so components.css can win cascade conflicts against
129       .pswp__button defaults when the close button is styled as a .btn. -->
130  
130<script type="module" src="../photoswipe-init.js?v=1"></script>
130
131
132  <!-- Phone-overlay swipe forwarding: lets a swipe that starts on the
133       phone scroll the laptop underneath while keeping the phone tappable
134       for the lightbox. Auto-binds to any .case-mockup-wrap that has both a
135       .case-mockup-scroll and a .mockup--mobile, so future projects with
136       this layout get it for free. -->
137  
137<script type="module" src="../mockup-swipe-forward.js?v=1"></script>
137
138
139  <!-- Local dev only: rewrite Cloudflare image URLs to originals (no-op in prod) -->
140
141  <!-- Speculation Rules — prerender same-origin page navigations on link
142       hover/touch (eagerness: moderate), so project pages (and back-to-home)
143       open near-instantly. Progressive enhancement: unsupported browsers
144       ignore the block and links navigate normally. Safe with the entrance
145       cascade — runEntranceCascade() holds the reveal until the page is first
146       visible, so a prerendered page plays its entrance on activation, not in
147       the background. Cloudflare's injected Speed Brain only *prefetches*
148       conservatively; this upgrades the six site pages to full prerender. -->
149  
149<script type="speculationrules">
150  {
151    "prerender": [{
152      "where": { "href_matches": ["/", "/bella-duke{/}?", "/homemove{/}?", "/pikl-dashboard{/}?", "/pikl-design-system{/}?", "/wren-kitchens{/}?"] },
153      "eagerness": "moderate"
154    }]
155  }
156  </script>
156
157
158</head>
159<body data-project="homemove">
160
161<div class="case-page">
162
163  <!-- Back button -->
164  <a class="btn home-btn" href="../" aria-label="Back to all work" data-stagger-item>
165    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
166      <path d="M5.5 12.002H19"/>
167      <path d="M10.9999 18.002C10.9999 18.002 4.99998 13.583 4.99997 12.0019C4.99996 10.4208 11 6.00195 11 6.00195"/>
168    </svg>
169    <span class="btn__label">All work</span>
170  </a>
171
172  <!-- Hero area -->
173  <div class="case-hero-area">
174
175    <!-- Shader background -->
176    <div class="case-shader" aria-hidden="true">
177      <div id="case-shader-gradient" style="width: 100%; height: 100%;"></div>
178    </div>
179
180    <!-- ===== HERO ===== -->
181    <header class="case-hero" data-stagger>
182
183      <!-- Page title -->
184      <h1 class="case-title">Orchestrated Clarity In Chaos</h1>
185
186      <!-- Company badge row -->
187      <div class="case-company">
188        <!-- Homemove wordmark SVG -->
189        <div class="case-logo-wrap">
190        <svg class="case-logo" xmlns="http://www.w3.org/2000/svg" width="116" height="18" fill="none" viewBox="0 0 116 18" aria-label="Homemove">
191          <path d="M 32.118 8.38 C 31.609 8.095 31.026 7.953 30.37 7.953 C 29.713 7.953 29.179 8.093 28.703 8.372 C 28.408 8.545 28.164 8.767 27.973 9.039 L 27.973 4.652 L 25.822 4.652 L 25.822 17.084 L 27.973 17.084 L 27.973 11.845 C 27.973 11.451 28.049 11.112 28.203 10.827 C 28.356 10.542 28.569 10.321 28.843 10.162 C 29.116 10.003 29.428 9.924 29.778 9.924 C 30.129 9.924 30.454 10.003 30.723 10.162 C 30.991 10.32 31.201 10.542 31.355 10.827 C 31.508 11.112 31.585 11.451 31.585 11.845 L 31.585 17.084 L 33.735 17.084 L 33.735 11.336 C 33.735 10.668 33.593 10.08 33.308 9.57 C 33.024 9.061 32.627 8.665 32.118 8.38 Z M 42.358 8.744 C 41.647 8.339 40.853 8.137 39.978 8.137 C 39.102 8.137 38.292 8.34 37.581 8.744 C 36.869 9.149 36.303 9.703 35.882 10.403 C 35.46 11.104 35.249 11.903 35.249 12.8 C 35.249 13.698 35.46 14.498 35.882 15.198 C 36.303 15.899 36.872 16.452 37.589 16.857 C 38.306 17.262 39.102 17.464 39.978 17.464 C 40.854 17.464 41.631 17.262 42.342 16.857 C 43.053 16.451 43.623 15.899 44.05 15.198 C 44.477 14.498 44.69 13.698 44.69 12.8 C 44.69 11.902 44.479 11.09 44.058 10.395 C 43.636 9.7 43.07 9.149 42.359 8.744 Z M 42.214 14.298 C 42.001 14.708 41.711 15.028 41.344 15.258 C 40.977 15.488 40.553 15.603 40.072 15.603 C 39.59 15.603 39.146 15.488 38.775 15.258 C 38.402 15.028 38.11 14.708 37.896 14.298 C 37.683 13.887 37.576 13.425 37.576 12.91 C 37.576 12.395 37.683 11.922 37.896 11.522 C 38.11 11.122 38.402 10.805 38.775 10.57 C 39.147 10.334 39.579 10.216 40.072 10.216 C 40.564 10.216 40.977 10.334 41.344 10.57 C 41.711 10.805 42.001 11.122 42.214 11.522 C 42.427 11.922 42.534 12.384 42.534 12.91 C 42.534 13.435 42.427 13.887 42.214 14.298 Z M 58.186 8.563 C 57.687 8.279 57.121 8.136 56.487 8.136 C 55.775 8.136 55.146 8.314 54.599 8.67 C 54.246 8.899 53.951 9.203 53.712 9.579 C 53.517 9.242 53.243 8.953 52.891 8.711 C 52.333 8.328 51.687 8.136 50.954 8.136 C 50.287 8.136 49.707 8.295 49.214 8.613 C 48.892 8.82 48.64 9.099 48.459 9.447 L 48.459 8.333 L 46.439 8.333 L 46.439 17.267 L 48.59 17.267 L 48.59 12.028 C 48.59 11.634 48.661 11.295 48.803 11.01 C 48.946 10.726 49.148 10.504 49.411 10.345 C 49.674 10.187 49.975 10.107 50.314 10.107 C 50.653 10.107 50.968 10.187 51.225 10.345 C 51.482 10.504 51.682 10.726 51.824 11.01 C 51.967 11.295 52.038 11.634 52.038 12.028 L 52.038 17.267 L 54.189 17.267 L 54.189 12.028 C 54.189 11.634 54.259 11.295 54.402 11.01 C 54.544 10.726 54.747 10.504 55.009 10.345 C 55.272 10.187 55.573 10.107 55.912 10.107 C 56.274 10.107 56.58 10.187 56.832 10.345 C 57.083 10.504 57.28 10.726 57.423 11.01 C 57.565 11.295 57.636 11.634 57.636 12.028 L 57.636 17.267 L 59.787 17.267 L 59.787 11.519 C 59.787 10.852 59.645 10.263 59.36 9.754 C 59.075 9.245 58.684 8.848 58.186 8.563 Z M 69.164 9.434 C 68.791 9.029 68.34 8.711 67.809 8.481 C 67.278 8.251 66.663 8.136 65.962 8.136 C 65.13 8.136 64.378 8.339 63.705 8.744 C 63.032 9.149 62.501 9.699 62.112 10.394 C 61.724 11.09 61.529 11.886 61.529 12.784 C 61.529 13.681 61.721 14.437 62.104 15.149 C 62.487 15.86 63.023 16.424 63.713 16.84 C 64.402 17.256 65.207 17.464 66.126 17.464 C 66.739 17.464 67.3 17.371 67.809 17.185 C 68.318 16.999 68.758 16.741 69.13 16.413 C 69.503 16.085 69.776 15.707 69.952 15.28 L 68.211 14.426 C 68.014 14.777 67.743 15.056 67.399 15.264 C 67.054 15.472 66.635 15.576 66.143 15.576 C 65.65 15.576 65.215 15.461 64.837 15.231 C 64.46 15.001 64.175 14.675 63.984 14.254 C 63.866 13.996 63.793 13.714 63.764 13.408 L 70.198 13.408 C 70.241 13.288 70.272 13.145 70.288 12.981 C 70.304 12.817 70.313 
19112.659 70.313 12.505 C 70.313 11.903 70.214 11.342 70.017 10.822 C 69.82 10.302 69.535 9.839 69.164 9.434 Z M 64.86 10.345 C 65.21 10.121 65.609 10.009 66.058 10.009 C 66.507 10.009 66.931 10.124 67.265 10.354 C 67.599 10.584 67.84 10.896 67.987 11.29 C 68.055 11.47 68.096 11.661 68.113 11.864 L 63.906 11.864 C 63.943 11.673 63.995 11.495 64.064 11.331 C 64.244 10.898 64.51 10.57 64.86 10.345 Z M 83.781 8.563 C 83.283 8.279 82.717 8.136 82.082 8.136 C 81.371 8.136 80.741 8.314 80.194 8.67 C 79.842 8.899 79.546 9.203 79.308 9.579 C 79.112 9.242 78.839 8.953 78.487 8.711 C 77.929 8.328 77.283 8.136 76.55 8.136 C 75.882 8.136 75.302 8.295 74.81 8.613 C 74.487 8.82 74.236 9.099 74.054 9.447 L 74.054 8.333 L 72.035 8.333 L 72.035 17.267 L 74.186 17.267 L 74.186 12.028 C 74.186 11.634 74.257 11.295 74.399 11.01 C 74.541 10.726 74.744 10.504 75.007 10.345 C 75.269 10.187 75.57 10.107 75.91 10.107 C 76.249 10.107 76.564 10.187 76.821 10.345 C 77.078 10.504 77.278 10.726 77.42 11.01 C 77.562 11.295 77.633 11.634 77.633 12.028 L 77.633 17.267 L 79.784 17.267 L 79.784 12.028 C 79.784 11.634 79.855 11.295 79.998 11.01 C 80.14 10.726 80.342 10.504 80.605 10.345 C 80.868 10.187 81.169 10.107 81.508 10.107 C 81.869 10.107 82.176 10.187 82.427 10.345 C 82.679 10.504 82.876 10.726 83.019 11.01 C 83.161 11.295 83.232 11.634 83.232 12.028 L 83.232 17.267 L 85.383 17.267 L 85.383 11.519 C 85.383 10.852 85.24 10.263 84.956 9.754 C 84.671 9.245 84.28 8.848 83.782 8.563 Z M 94.234 8.744 C 93.523 8.339 92.729 8.137 91.854 8.137 C 90.978 8.137 90.168 8.34 89.457 8.744 C 88.745 9.149 88.179 9.703 87.758 10.403 C 87.336 11.104 87.125 11.903 87.125 12.8 C 87.125 13.698 87.336 14.498 87.758 15.198 C 88.179 15.899 88.748 16.452 89.465 16.857 C 90.182 17.262 90.978 17.464 91.854 17.464 C 92.73 17.464 93.507 17.262 94.218 16.857 C 94.929 16.451 95.499 15.899 95.926 15.198 C 96.353 14.498 96.566 13.698 96.566 12.8 C 96.566 11.902 96.355 11.09 95.934 10.395 C 95.512 9.7 94.946 9.149 94.235 8.744 Z M 94.09 14.298 C 93.877 14.708 93.586 15.028 93.22 15.258 C 92.853 15.488 92.429 15.603 91.947 15.603 C 91.466 15.603 91.022 15.488 90.651 15.258 C 90.278 15.028 89.986 14.708 89.772 14.298 C 89.559 13.887 89.452 13.425 89.452 12.91 C 89.452 12.395 89.559 11.922 89.772 11.522 C 89.986 11.122 90.278 10.805 90.651 10.57 C 91.023 10.334 91.455 10.216 91.947 10.216 C 92.44 10.216 92.853 10.334 93.22 10.57 C 93.586 10.805 93.876 11.122 94.09 11.522 C 94.303 11.922 94.41 12.384 94.41 12.91 C 94.41 13.435 94.303 13.887 94.09 14.298 Z M 101.747 14.175 L 99.636 8.344 L 97.288 8.344 L 100.802 17.278 L 102.706 17.278 L 106.219 8.344 L 103.872 8.344 Z M 115.426 10.822 C 115.229 10.302 114.944 9.839 114.573 9.434 C 114.2 9.029 113.749 8.711 113.218 8.481 C 112.687 8.251 112.071 8.136 111.371 8.136 C 110.539 8.136 109.787 8.339 109.114 8.744 C 108.441 9.149 107.91 9.699 107.521 10.394 C 107.133 11.09 106.938 11.886 106.938 12.784 C 106.938 13.681 107.13 14.437 107.513 15.149 C 107.896 15.86 108.432 16.424 109.122 16.84 C 109.811 17.256 110.616 17.464 111.535 17.464 C 112.148 17.464 112.709 17.371 113.218 17.185 C 113.727 16.999 114.167 16.741 114.539 16.413 C 114.911 16.085 115.185 15.707 115.36 15.28 L 113.62 14.426 C 113.423 14.777 113.152 15.056 112.807 15.264 C 112.463 15.472 112.044 15.576 111.552 15.576 C 111.059 15.576 110.624 15.461 110.246 15.231 C 109.869 15.001 109.584 14.675 109.393 14.254 C 109.275 13.996 109.202 13.714 109.173 13.408 L 115.607 13.408 C 115.651 13.288 115.681 13.145 115.697 12.981 C 115.713 12.817 115.722 12.659 115.722 12.505 C 115.722 11.903 115.624 11.342 115.426 10.822 Z M 110.269 10.345 C 110.619 10.121 111.018 10.009 111.467 10.009 C 111.916 10.009 112.34 10.124 112.674 10.354 C 113.008 10.584 113.248 10.896 113.396 11.29 C 113.464 11.47 113.505 11.661 113.521 11.864 L 109.315 11.864 C 109.351 11.673 109.404 11.495 109.472 11.331 C 109.653 10.898 109.918 10.57 110.269 10.345 Z M 15.148 2.906 L 9.688 0.307 C 8.885 -0.076 7.963 -0.076 7.16 0.307 L 1.7 2.906 C 0.671 3.396 0.006 4.454 0.006 5.6 L 0.006 15.76
1915 C 0.006 16.338 0.471 16.804 1.045 16.804 C 1.618 16.804 2.083 16.338 2.083 15.765 L 2.083 15.208 C 2.264 14.315 2.864 13.479 3.783 12.847 C 4.229 12.54 4.719 12.303 5.227 12.144 C 5.215 12.283 5.207 12.423 5.206 12.565 C 5.19 13.84 5.78 15.174 6.747 16.046 C 7.465 16.693 8.332 17.037 9.223 17.037 C 9.357 17.037 9.492 17.03 9.627 17.014 C 10.649 16.894 11.488 16.323 11.93 15.446 C 12.4 14.51 12.35 13.377 11.796 12.413 C 11.028 11.076 9.538 10.309 8.301 10.006 C 9.006 9.143 9.986 8.489 10.944 8.197 C 12.037 7.864 13.612 8.018 14.755 9.45 C 14.759 9.454 14.762 9.457 14.765 9.461 L 14.765 15.765 C 14.765 16.338 15.23 16.804 15.803 16.804 C 16.377 16.804 16.842 16.338 16.842 15.765 L 16.842 5.6 C 16.842 4.454 16.177 3.396 15.148 2.906 Z M 10.302 14.11 C 10.504 14.461 10.534 14.869 10.381 15.173 C 10.254 15.426 10.022 15.573 9.692 15.612 C 9.142 15.676 8.71 15.404 8.444 15.164 C 7.916 14.688 7.58 13.937 7.589 13.252 C 7.591 13.031 7.615 12.814 7.658 12.601 C 7.749 12.612 7.839 12.627 7.928 12.644 C 8.786 12.811 9.853 13.328 10.302 14.11 Z M 10.425 6.325 C 8.727 6.843 6.946 8.187 5.992 9.997 C 4.831 10.142 3.679 10.572 2.692 11.25 C 2.512 11.374 2.338 11.506 2.17 11.646 L 2.17 5.715 C 2.17 5.366 2.37 5.045 2.68 4.897 L 8.14 2.298 C 8.376 2.186 8.647 2.186 8.883 2.298 L 14.342 4.897 C 14.652 5.045 14.852 5.366 14.852 5.715 L 14.852 6.864 C 13.524 6.082 11.932 5.865 10.425 6.325 Z" fill="currentColor" />
192        </svg>
193        </div>
194        <span class="case-company-text">Top 5% Global PropTech Startup, Raised &pound;2M</span>
195      </div>
196
197      <!-- Tags -->
198      <div class="case-tags" aria-label="Project type">
199        <span class="case-tag">
200          <svg class="tag-icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M14.25,2H3.75c-1.517,0-2.75,1.233-2.75,2.75V13.25c0,1.517,1.233,2.75,2.75,2.75H14.25c1.517,0,2.75-1.233,2.75-2.75V4.75c0-1.517-1.233-2.75-2.75-2.75ZM4,6c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Zm3,0c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"/></svg>
201          Web App
202        </span>
203        <span class="case-tag">
204          <svg class="tag-icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><circle cx="12" cy="8" r="1" fill="none"/><path d="M17.53,5.792l-.988-.397h-.002s-1.041-.42-1.041-.42v-1.475c0-.414-.336-.75-.75-.75s-.75,.336-.75,.75v.872L7.03,1.566c-.384-.156-.822,.032-.976,.416-.155,.384,.031,.821,.416,.976l.53,.213v12.829h3.75v-3.5c0-.69,.56-1.25,1.25-1.25s1.25,.56,1.25,1.25v3.5h2c.965,0,1.75-.785,1.75-1.75V7.19c.082,.029,.167,.048,.25,.048,.297,0,.579-.177,.696-.47,.155-.384-.031-.821-.416-.976Zm-5.53,3.208c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"/><path d="M5.5,14.5v-1.75c0-.414-.336-.75-.75-.75s-.75,.336-.75,.75v1.75h-1.25c-.138,0-.25-.112-.25-.25v-5.157l4.5-1.812v-1.617L.47,8.292c-.384,.155-.57,.592-.416,.976,.118,.292,.399,.47,.696,.47,.083,0,.167-.019,.25-.048v4.56c0,.965,.785,1.75,1.75,1.75H7v-1.5h-1.5Z"/></svg>
205          Proptech
206        </span>
207      </div>
208
209    </header><!-- /.case-hero -->
210
211    <!-- ===== HERO MOCKUP ===== -->
212    <div class="case-mockup-wrap" data-stagger-item>
213      <div class="case-mockup-scroll">
214        <div class="mockup-group">
215          <div class="mockup">
216            <span class="thumb-wrap" data-thumbhash="/PcFBYAtqrKXl3gPXJWWwkHPoKcI"><picture><source type="image/avif" srcset="/homemove-mockup-768.avif 768w, /homemove-mockup-1280.avif 1280w, /homemove-mockup-2880.avif 2880w" sizes="(max-width: 767px) 85vw, 1280px"><source type="image/webp" srcset="/homemove-mockup-768.webp 768w, /homemove-mockup-1280.webp 1280w, /homemove-mockup-2880.webp 2880w" sizes="(max-width: 767px) 85vw, 1280px"><img src="/homemove-mockup-1280.png" srcset="/homemove-mockup-768.png 768w, /homemove-mockup-1280.png 1280w, /homemove-mockup.png 2880w" sizes="(max-width: 767px) 85vw, 1280px" width="2880" height="2048" alt="Homemove project — Orchestrated Clarity in Chaos" fetchpriority="high" decoding="async"></picture></span>
217          </div>
218        </div>
219      </div>
220      <div class="mockup mockup--mobile">
221        <span class="thumb-wrap" data-thumbhash="+RcGDATHtndnh4dZOviXgHYGyA=="><picture><source type="image/avif" srcset="/homemove-mobile-mockup-375.avif 375w, /homemove-mobile-mockup-1125.avif 1125w" sizes="(max-width: 661px) 22vw, 333px"><source type="image/webp" srcset="/homemove-mobile-mockup-375.webp 375w, /homemove-mobile-mockup-1125.webp 1125w" sizes="(max-width: 661px) 22vw, 333px"><img src="/homemove-mobile-mockup-375.png" srcset="/homemove-mobile-mockup-375.png 375w, /homemove-mobile-mockup.png 1125w" sizes="(max-width: 661px) 22vw, 333px" alt="Homemove mobile app welcome screen" width="1125" height="2148" fetchpriority="low" decoding="async"></picture></span>
222      </div>
223    </div>
224
225  </div><!-- /.case-hero-area -->
226
227  <!-- ===== MAIN CONTENT ===== -->
228  <main class="case-content" data-stagger>
229
230    <!-- ===== ROLES ===== -->
231    <div class="case-roles case-narrow" role="region" aria-label="Project roles">
232
233      <div class="case-role">
234        <div class="case-role-label">
235          <svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
236            <path d="M2.60518 13.1674C3.69058 10.7157 6.14168 9 8.99999 9C11.7634 9 14.1462 10.6037 15.2822 12.9257C15.3564 13.0774 15.4289 13.2326 15.4797 13.3894C15.8649 14.5805 15.1811 15.8552 13.9874 16.2313C12.705 16.6354 11.0072 17 8.99999 17C6.99283 17 5.29503 16.6354 4.01259 
23616.2313C2.74425 15.8317 2.05162 14.4186 2.60518 13.1674Z"/>
237            <path d="M9 7.50049C10.7952 7.50049 12.25 6.04543 12.25 4.25049C12.25 2.45554 10.7952 1.00049 9 1.00049C7.20482 1.00049 5.75 2.45554 5.75 4.25049C5.75 6.04543 7.20482 7.50049 9 7.50049Z"/>
238          </svg>
239          Responsibilities
240        </div>
241        <p class="case-role-value">UI Design &middot; UX Design &middot; User Research &middot; End-To-End Ownership</p>
242      </div>
243
244      <div class="case-role">
245        <div class="case-role-label">
246          <svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
247            <path fill-rule="evenodd" clip-rule="evenodd" d="M0.554137 13.5756C1.34525 11.476 3.36866 9.97803 5.74997 9.97803C8.13128 9.97803 10.1547 11.476 10.9458 13.5756C11.3059 14.5316 10.7272 15.5154 9.84596 15.8103C8.82613 16.1509 7.42657 16.477 5.75097 16.477C4.0754 16.477 2.67527 16.1511 1.65458 15.8105C0.771586 15.5163 0.194851 14.5312 0.554137 13.5756Z"/>
248            <path d="M12.5523 13.9774C13.9847 13.9162 15.1901 13.6251 16.096 13.3225C16.9772 13.0276 17.5559 12.0438 17.1958 11.0878C16.4047 8.98817 14.3813 7.49023 12 7.49023C10.5581 7.49023 9.24737 8.03945 8.26202 8.9389C10.147 9.65833 11.6398 11.1634 12.3495 13.0469C12.4675 13.3603 12.5329 13.6726 12.5523 13.9774Z"/>
249            <path d="M5.75 8.50049C6.99267 8.50049 8 7.49361 8 6.25049C8 5.00736 6.99267 4.00049 5.75 4.00049C4.50733 4.00049 3.5 5.00736 3.5 6.25049C3.5 7.49361 4.50733 8.50049 5.75 8.50049Z"/>
250            <path d="M12 6.00049C13.2427 6.00049 14.25 4.99361 14.25 3.75049C14.25 2.50736 13.2427 1.50049 12 1.50049C10.7573 1.50049 9.75 2.50736 9.75 3.75049C9.75 4.99361 10.7573 6.00049 12 6.00049Z"/>
251          </svg>
252          Team
253        </div>
254        <p class="case-role-value">1 Head of Design &middot; 1 Head of Operations &middot; 2 Full Stack Developers</p>
255      </div>
256
257    </div><!-- /.case-roles -->
258
259    <!-- ===== CAR CARD ===== -->
260    <div class="case-car case-narrow" role="region" aria-label="Context, Action, Results">
261
262      <!-- Context -->
263      <labelled-section label="Context">
264        <svg slot="icon" width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
265          <path d="M15.487,5.427l-3.914-3.914c-.331-.331-.77-.513-1.237-.513H4.75c-1.517,0-2.75,1.233-2.75,2.75V14.25c0,1.517,1.233,2.75,2.75,2.75H13.25c1.517,0,2.75-1.233,2.75-2.75V6.664c0-.467-.182-.907-.513-1.237Zm-9.737,.573h2c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75h-2c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75Zm6.5,7.5H5.75c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h6.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm0-3H5.75c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75h6.5c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Zm2.182-4h-2.932c-.55,0-1-.45-1-1V2.579l.013-.005,3.922,3.921-.002,.005Z"/>
266        </svg>
267        <div class="case-car-body-card">
268          <p class="case-car-body">Homemove is a PropTech startup consolidating the entire home-moving process into one platform. The existing product surfaced everything a mover could do, with no logic about what they should do first. I joined as the sole designer to fix that.</p>
269        </div>
270      </labelled-section>
271
272      <!-- Action -->
273      <labelled-section label="Action">
274        <svg slot="icon" width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
275          <path d="M16.2675 8.4546C15.8398 8.4097 15.5088 8.76952 15.4971 9.18312C15.3994 12.7256 12.545 15.5 8.99995 15.5C6.83525 15.5 4.87116 14.4272 3.67296 12.6987L5.12976 12.9004C5.53016 12.9531 5.91885 12.6704 5.97545 12.2598C6.03205 11.8496 5.74495 11.4712 5.33485 11.4141L2.39055 11.007C1.98915 10.9528 1.60246 11.2364 1.54486 11.647L1.13666 14.5918C1.08006 15.002 1.36615 15.3809 1.77725 15.4375C1.81145 15.4424 1.84655 15.4448 1.88075 15.4448C2.24895 15.4448 2.57025 15.1734 2.62295 14.7979L2.73796 13.9667C4.23026 15.852 6.51045 16.9999 8.99995 16.9999C13.3633 16.9999 16.876 13.5849 16.9971 9.22499C17.0078 8.81089 16.6816 8.4658 16.2675 8.4546Z"/>
276          <path d="M1.73242 9.54688C1.73922 9.54738 1.74611 9.54736 1.75291 9.54736C2.15821 9.54736 2.49121 9.22507 2.50291 8.81787C2.59961 5.27537 5.45411 2.5 9.00001 2.5C11.1629 2.5 13.1295 3.57317 14.3272 5.30127L12.8701 5.09961C12.4678 5.04981 12.081 5.33013 12.0244 5.74023C11.9678 6.15033 12.2549 6.52884 12.665 6.58594L15.6093 6.99304C15.6435 6.99744 15.6786 6.99988 15.7128 6.99988C16.081 6.99988 16.4023 6.72841 16.455 6.35291L16.8632 3.40808C16.9198 2.99788 16.6337 2.61898 16.2226 2.56238C15.8183 2.50328 15.4345 2.79143 15.3769 3.20203L15.2616 4.03552C13.7688 2.14982 11.4876 0.999878 8.99991 0.999878C4.63561 0.999878 1.12292 4.41596 1.00282 8.77686C0.992119 9.19096 1.31832 9.53568 1.73242 9.54688Z"/>
277        </svg>
278        <div class="case-car-body-card">
279          <p class="case-car-body">I built an Object Map using OOUX to model core entities and their dependencies before touching a single screen. That structural foundation drove a fundamental shift: from a service-first dashboard to a timeline-first task hierarchy.</p>
280        </div>
281      </labelled-section>
282
283      <!-- Results -->
284      <labelled-section label="Results">
285        <svg slot="icon" width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
286          <path d="m15.25,15.5H4.75c-1.5166,0-2.75-1.2334-2.75-2.75V2.75c0-.4141.3359-.75.75-.75s.75.3359.75.75v10c0,.6895.5605,1.25,1.25,1.25h10.5c.4141,0,.75.3359.75.75s-.3359.75-.75.75Z"/>
287          <rect x="10.5" y="3.5" width="3.5" height="8.5" rx="1" ry="1"/>
288          <rect x="5.5" y="7" width="3.5" height="5" rx="1" ry="1"/>
289        </svg>
290        <div class="case-car-body-card">
291          <p class="case-car-body">Users and stakeholders confirmed the new task hierarchy was significantly clearer than the original</p>
292          <p class="case-car-body">A data-backed artefact resolved a critical stakeholder conflict and became the team's design standard</p>
293          <p class="case-car-body">Developer alignment improved through a shared object language across the team</p>
294        </div>
295      </labelled-section>
296
297    </div><!-- /.case-car -->
298
299    <!-- ===== OUTCOME IMAGERY ===== -->
300    <section class="case-images" aria-label="Outcome imagery">
301
302      <!-- 1 — Tasks -->
303      <figure class="image-container">
304        <div class="image-container__media">
305          <span class="thumb-wrap" data-thumbhash="/fcBBYCQVyWXmohlZLdojGDLNo+r"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove/tasks-light.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/homemove/tasks-light.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/homemove/tasks-light.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/homemove/tasks-light.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Task list with integrated booking options for guided home selling" width="2880" height="2048" loading="lazy" decoding="async"></span>
306        </div>
307        <figcaption class="image-container__caption">
308          <span class="image-container__text">Task list with integrated booking options for guided home selling</span>
309          <span class="image-container__meta">
310            <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
311            <span class="image-container__number" aria-hidden="true"></span>
312          </span>
313        </figcaption>
314      </figure>
315
316      <!-- 2 — Chat -->
317      <figure class="image-container">
318        <div class="image-container__media">
319          <span class="thumb-wrap" data-thumbhash="/AcCBYJek0eHiXkPptOl03CZAooY"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove/chat-light.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/homemove/chat-light.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/homemove/chat-light.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/homemove/chat-light.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="In-app messaging with home moving partners for helpful, human support" width="2880" height="2048" loading="lazy" decoding="async"></span>
320        </div>
321        <figcaption class="image-container__caption">
322          <span class="image-container__text">In-app messaging with home moving partners for helpful, human support</span>
323          <span class="image-container__meta">
324            <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
325            <span class="image-container__number" aria-hidden="true"></span>
326          </span>
327        </figcaption>
328      </figure>
329
330      <!-- 3 — Events -->
331      <figure class="image-container">
332        <div class="image-container__media">
333          <span class="thumb-wrap" data-thumbhash="/fcBBYBQUgmXrKpEdKV4mkDbdVz+"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove/events-light.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/homemove/events-light.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/homemove/events-light.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/homemove/events-light.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Event dashboard showing upcoming appointments across services" width="2880" height="2048" loading="lazy" decoding="async"></span>
334        </div>
335        <figcaption class="image-container__caption">
336          <span class="image-container__text">Event dashboard showing upcoming appointments across services</span>
337          <span class="image-container__meta">
338            <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
339            <span class="image-container__number" aria-hidden="true"></span>
340          </span>
341        </figcaption>
342      </figure>
343
344      <!-- 4 — Documents -->
345      <figure class="image-container">
346        <div class="image-container__media">
347          <span class="thumb-wrap" data-thumbhash="/fcBBYAd0DRP+4YYZGeZbUC9FvvJ"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove/documents-light.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/homemove/documents-light.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/homemove/documents-light.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/homemove/documents-light.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Centralised document hub with filters for status and service type" width="2880" height="2048" loading="lazy" decoding="async"></span>
348        </div>
349        <figcaption class="image-container__caption">
350          <span class="image-container__text">Centralised document hub with filters for status and service type</span>
351          <span class="image-container__meta">
352            <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
353            <span class="image-container__number" aria-hidden="true"></span>
354          </span>
355        </figcaption>
356      </figure>
357
358      <!-- 5 — The mobile app, composited into a single image so the set gets one
359           frame, one caption and one image number. Placed after all four desktop
360           screens rather than paired with each one: the desktop shots are the
361           primary artefact and stay full width, and this makes the responsive
362           point once instead of repeating it per screen.
363
364           Three screens, not four — Chat is left out because its list runs to
365           two threads and stops, so it carries a large empty area the others
366           don't; it still has its own full-width desktop screenshot above.
367
368           It is deliberately ONE image rather than a grid of separate ones. As a
369           grid the block came out 1,131px tall against the desktop screens' 984px
370           — the supporting detail outsized the primary artefact — and the gaps
371           were CSS-driven, so they collapsed as the columns narrowed. Composited,
372           the spacing, corner radii and hairline strokes are part of the asset
373           and hold at every width. Built by scripts/build-mobile-screens.py. -->
374      <figure class="image-container image-container--screen-row">
375        <div class="image-container__media">
376          <span class="thumb-wrap" data-thumbhash="/PeBA4A3ZqmBiaCFgAB5eVdYeIeAiDg="><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove/mobile-screens.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/homemove/mobile-screens.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/homemove/mobile-screens.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/homemove/mobile-screens.png 2880w" sizes="(max-width: 809px) 1000px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Homemove mobile app — three screens side by side: the task list filtered to to-do, upcoming events grouped by month, and the document hub showing paperwork ready to sign" width="2880" height="1659" loading="lazy" decoding="async"></span>
377        </div>
378        <figcaption class="image-container__caption">
379          <span class="image-container__text">The same screens in the pocket app — tasks, events and documents</span>
380          <span class="image-container__meta">
381            <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
382            <span class="image-container__number" aria-hidden="true"></span>
383          </span>
384        </figcaption>
385      </figure>
386
387      <!-- The same screens again, one per row, for phones and small tablets.
388           Below 810px the composited row above is swapped out for these: a wide
389           row has to scroll sideways at that width, and a single caption at the
390           bottom of it ends up a long way from the first screen. Each screen
391           gets its own frame, caption and number instead.
392
393           Exactly one of the two is ever in the layout — the hidden one is
394           display:none, so it generates no boxes and takes no part in the image
395           counter, and its lazy images never enter the viewport so they are not
396           fetched. The trade is that this block and the composite carry the same
397           content twice: edit a caption here and check the one above too. -->
398      <div class="screen-row-stack">
399
400        <!-- Tasks -->
401        <figure class="image-container">
402          <div class="image-container__media">
403            <span class="thumb-wrap" data-thumbhash="/PcBDAD2NqhGiYQbOVj4koKPuQ=="><img src="/cdn-cgi/image/format=auto,width=1125,quality=85/homemove/tasks-mobile.png" srcset="/cdn-cgi/image/format=auto,width=375,quality=85/homemove/tasks-mobile.png 375w, /cdn-cgi/image/format=auto,width=640,quality=85/homemove/tasks-mobile.png 640w, /cdn-cgi/image/format=auto,width=1125,quality=85/homemove/tasks-mobile.png 1125w" sizes="(max-width: 607px) calc(100vw - 48px), 560px" alt="Homemove mobile app — the task list, filtered to to-do, pending and complete" width="1125" height="2088" loading="lazy" decoding="async"></span>
404          </div>
405          <figcaption class="image-container__caption">
406            <span class="image-container__text">The task list, in the pocket app</span>
407            <span class="image-container__meta">
408              <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
409              <span class="image-container__number" aria-hidden="true"></span>
410            </span>
411          </figcaption>
412        </figure>
413
414        <!-- Events -->
415        <figure class="image-container">
416          <div class="image-container__media">
417            <span class="thumb-wrap" data-thumbhash="/PcBBADRCbkFrJMcWinmkQB8nQ=="><img src="/cdn-cgi/image/format=auto,width=1125,quality=85/homemove/events-mobile.png" srcset="/cdn-cgi/image/format=auto,width=375,quality=85/homemove/events-mobile.png 375w, /cdn-cgi/image/format=auto,width=640,quality=85/homemove/events-mobile.png 640w, /cdn-cgi/image/format=auto,width=1125,quality=85/homemove/events-mobile.png 1125w" sizes="(max-width: 607px) calc(100vw - 48px), 560px" alt="Homemove mobile app — upcoming events grouped by month, with mortgage, valuation and conveyancing appointments" width="1125" height="2088" loading="lazy" decoding="async"></span>
418          </div>
419          <figcaption class="image-container__caption">
420            <span class="image-container__text">Appointments, month by month</span>
421            <span class="image-container__meta">
422              <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
423              <span class="image-container__number" aria-hidden="true"></span>
424            </span>
425          </figcaption>
426        </figure>
427
428        <!-- Documents -->
429        <figure class="image-container">
430          <div class="image-container__media">
431            <span class="thumb-wrap" data-thumbhash="/PcBDADWV6gGqXJriUrHkCBKDQ=="><img src="/cdn-cgi/image/format=auto,width=1125,quality=85/homemove/documents-mobile.png" srcset="/cdn-cgi/image/format=auto,width=375,quality=85/homemove/documents-mobile.png 375w, /cdn-cgi/image/format=auto,width=640,quality=85/homemove/documents-mobile.png 640w, /cdn-cgi/image/format=auto,width=1125,quality=85/homemove/documents-mobile.png 1125w" sizes="(max-width: 607px) calc(100vw - 48px), 560px" alt="Homemove mobile app — the document hub, showing paperwork flagged urgent and ready to sign" width="1125" height="2088" loading="lazy" decoding="async"></span>
432          </div>
433          <figcaption class="image-container__caption">
434            <span class="image-container__text">Paperwork, sorted by urgency</span>
435            <span class="image-container__meta">
436              <svg class="image-container__icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><path d="M13.194,8.384c-1.072-1.072-2.816-1.072-3.889,0L3.067,14.623c.214,.078,.442,.127,.683,.127H14.25c1.105,0,2-.896,2-2v-1.311l-3.056-3.056Z"/><path d="M14.25,15.5H3.75c-1.517,0-2.75-1.233-2.75-2.75V5.25c0-1.517,1.233-2.75,2.75-2.75H14.25c1.517,0,2.75,1.233,2.75,2.75v7.5c0,1.517-1.233,2.75-2.75,2.75ZM3.75,4c-.689,0-1.25,.561-1.25,1.25v7.5c0,.689,.561,1.25,1.25,1.25H14.25c.689,0,1.25-.561,1.25-1.25V5.25c0-.689-.561-1.25-1.25-1.25H3.75Z"/><circle cx="5.75" cy="7.25" r="1.25"/></svg>
437              <span class="image-container__number" aria-hidden="true"></span>
438            </span>
439          </figcaption>
440        </figure>
441
442      </div><!-- /.screen-row-stack -->
443
444    </section><!-- /.case-images -->
445
446    <!-- ===== NEXT PROJECT ===== -->
447    <section class="case-next case-narrow" aria-label="Next project">
448      <labelled-section label="Next project">
449        <next-project-card
450          href="../pikl-dashboard/"
451          title="Replaced guesswork with guided workflows">
452          <div class="mockup">
453            <span class="thumb-wrap" data-thumbhash="/QcCBYAymHlwqIpRpzmGdlDDw/f+"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/pikl-dashboard-mockup.png" srcset="/cdn-cgi/image/format=auto,width=1280,quality=85/pikl-dashboard-mockup.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/pikl-dashboard-mockup.png 2880w" sizes="(min-width: 1280px) 1216px, 85vw" alt="Pikl Dashboard — Replaced Guesswork With Guided Workflows" width="2880" height="2048" loading="lazy" decoding="async"></span>
454          </div>
455        </next-project-card>
456      </labelled-section>
457    </section>
458
459    <!-- ===== NEXT STEPS CTA ===== -->
460    <section class="case-cta case-narrow" aria-label="Get in touch">
461      <labelled-section label="Next steps">
462        <next-steps-cta></next-steps-cta>
463      </labelled-section>
464    </section>
465
466    <!-- ===== FOOTER ===== -->
467    <footer class="case-footer case-narrow">
468      <p class="copyright">&copy; 2026 John Miller</p>
469    </footer>
470
471  </main><!-- /.case-content -->
472
473</div><!-- /.case-page -->
474
475<!-- Shader initialization -->
476<script type="module">
477  import {
478    ShaderMount,
479    brandColor,
480    cardBg,
481    buildShaderColors,
482    siteFragmentShader,
483    createShaderUniforms,
484    SHADER_MAX_PIXELS,
485    getCardScale,
486    getHeroScale,
487    getHeroSpeed,
488    onBreakpointChange,
489    shaderFrameOffset,
490    averageColor,
491    createFadeColorSampler,
492    createShaderVisibilityGate,
493  } from '../shader-base.js?v=1';
494
495  // Brand stops use `brandColor()` so light-mode pastels are auto-darkened
496  // in dark mode. `cardBg()` is white in light, near-black in dark. Wrapped
497  // in builder functions so the matchMedia change listener below can
498  // re-evaluate brandColor/cardBg with the fresh isDark() result when the
499  // user toggles OS theme mid-session.
500  const buildHeroColors = () => buildShaderColors({ brand: [brandColor(0.899, 0.050, 279.1), brandColor(0.915, 0.044, 21.5)], bg: cardBg() });
501  const buildPiklColors = () => buildShaderColors({ brand: [brandColor(0.896, 0.064, 350.7)], bg: cardBg() });
502
503  // Tint the next-card's bottom fade with its own shader palette, mirroring the
504  // homepage cards. Re-run on theme change so it tracks light/dark.
505  const applyNextFade = () =>
506    document.getElementById('next-card-shader')?.closest('.next-card')
507      ?.style.setProperty('--card-fade-color', averageColor(buildPiklColors()));
508
509  const prefersReducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
510
511  // Mount registry — each entry pairs a ShaderMount with its colour builder
512  // so the scheme-change listener can repush fresh u_colors to all mounts.
513  const mounts = [];
514  let fadeSampler = null;   // live --card-fade-color sampler for the next-card
515
516  // Lightbox shader-pause integration ('site:pause-shaders' on open,
517  // 'site:resume-shaders' on destroy, from photoswipe-init.js) is handled
518  // inside each mount's visibility gate (createShaderVisibilityGate), which
519  // also pauses a shader whenever it's scrolled off-screen — see
520  // shader-base.js. Resuming through the gate re-checks visibility, so a
521  // lightbox closed mid-article doesn't restart an off-screen shader.
522  const speedFor = () => (prefersReducedMotion ? 0 : getHeroSpeed());
523
524  async function initShaders() {
525    // Yield frames between compiles so the entrance animation's
526    // main-thread paint (filter blur) isn't blocked by GL compilation.
527    const yieldFrames = () =>
528      new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
529
530    await yieldFrames();
531
532    let heroMount = null;
533    let nextMount = null;
534    let heroGate = null;
535    let nextGate = null;
536
537    // Hero shader — container opacity fade is handled in homemove.css
538    const heroEl = document.getElementById('case-shader-gradient');
539    if (heroEl) {
540      const heroColors = buildHeroColors();
541      try {
542        heroMount = new ShaderMount(heroEl, siteFragmentShader, {
543          ...createShaderUniforms(),
544          u_scale: getHeroScale(),
545          u_colors: heroColors,
546          u_colorsCount: heroColors.length,
547        }, undefined, prefersReducedMotion ? 0 : getHeroSpeed(), shaderFrameOffset('gradient-homemove'));
548        heroMount.setMaxPixelCount(SHADER_MAX_PIXELS);
549        heroGate = createShaderVisibilityGate(heroEl, heroMount, speedFor);
550        mounts.push({ mount: heroMount, build: buildHeroColors });
551        await yieldFrames();
552      } catch (e) { heroMount = null; }
553    }
554
555    // Next-card shader (Pikl dashboard colors from homepage)
556    const nextCardShader = document.getElementById('next-card-shader');
557    if (nextCardShader) {
558      try {
559        const piklColors = buildPiklColors();
560        nextMount = new ShaderMount(nextCardShader, siteFragmentShader, {
561          ...createShaderUniforms(),
562          u_colors: piklColors,
563          u_colorsCount: piklColors.length,
564        }, undefined, prefersReducedMotion ? 0 : getHeroSpeed(), shaderFrameOffset('gradient-pikl-dashboard'));
565        nextMount.setMaxPixelCount(SHADER_MAX_PIXELS);
566        nextGate = createShaderVisibilityGate(nextCardShader, nextMount, speedFor);
567        mounts.push({ mount: nextMount, build: buildPiklColors });
568        applyNextFade();
569        const nextCard = nextCardShader.closest('.next-card');
570        if (nextCard && !prefersReducedMotion) {
571          fadeSampler = createFadeColorSampler(nextMount, nextCard, { getSpeed: getHeroSpeed, isVisible: () => nextGate.visible });
572          fadeSampler.start();
573        }
574      } catch (e) { nextMount = null; }
575    }
576
577    onBreakpointChange(() => {
578      if (heroMount) {
579        heroMount.setUniforms({ u_scale: getHeroScale() });
580        heroGate.apply();
581      }
582      if (nextMount) {
583        nextMount.setUniforms({ u_scale: getCardScale() });
584        nextGate.apply();
585      }
586    });
587  }
588
589  // React to OS theme changes without requiring a page refresh — rebuilds
590  // each mount's u_colors via its colour builder so brandColor/cardBg are
591  // re-evaluated with the new isDark() result. URL params (?dark / ?light)
592  // override OS, so the rebuild stays locked if a param is present.
593  matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
594    for (const entry of mounts) {
595      const colors = entry.build();
596      entry.mount.setUniforms({ u_colors: colors, u_colorsCount: colors.length });
597    }
598    applyNextFade();
599    fadeSampler?.snap();
600  });
601
602  initShaders();
603</script>
603
604
605</body>
606</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.