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>Turned Subscription Friction Into Trust · Bella & Duke · John Miller</title> 17 <meta name="description" content="How I reframed Bella & Duke's subscription portal using OOUX â from a bare account area to a design organised around the pet routines customers plan their lives around." /> 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="/bella-duke-mockup-1280.avif" 35 imagesrcset="/bella-duke-mockup-768.avif 768w, /bella-duke-mockup-1280.avif 1280w, /bella-duke-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
121<script src="../bella-duke-paw.js?v=18" defer></script>
121 122 123 <!-- ThumbHash placeholders --> 124
124<script type="module" src="../thumbhash-loader.js?v=skel10"></script>
124 125 126 <!-- iOS-style 60% corner smoothing (squircles) on safe rounded elements --> 127 128 <!-- PhotoSwipe lightbox (single-image mode); stylesheet is loaded earlier 129 in <head> so components.css can win cascade conflicts against 130 .pswp__button defaults when the close button is styled as a .btn. --> 131
131<script type="module" src="../photoswipe-init.js?v=1"></script>
131 132 133 <!-- Phone-overlay swipe forwarding: lets a swipe that starts on the 134 phone scroll the laptop underneath while keeping the phone tappable 135 for the lightbox. Auto-binds to any .case-mockup-wrap that has both a 136 .case-mockup-scroll and a .mockup--mobile, so future projects with 137 this layout get it for free. --> 138
138<script type="module" src="../mockup-swipe-forward.js?v=1"></script>
138 139 140 <!-- Local dev only: rewrite Cloudflare image URLs to originals (no-op in prod) --> 141 142 <!-- Speculation Rules â prerender same-origin page navigations on link 143 hover/touch (eagerness: moderate), so project pages (and back-to-home) 144 open near-instantly. Progressive enhancement: unsupported browsers 145 ignore the block and links navigate normally. Safe with the entrance 146 cascade â runEntranceCascade() holds the reveal until the page is first 147 visible, so a prerendered page plays its entrance on activation, not in 148 the background. Cloudflare's injected Speed Brain only *prefetches* 149 conservatively; this upgrades the six site pages to full prerender. --> 150
150<script type="speculationrules"> 151 { 152 "prerender": [{ 153 "where": { "href_matches": ["/", "/bella-duke{/}?", "/homemove{/}?", "/pikl-dashboard{/}?", "/pikl-design-system{/}?", "/wren-kitchens{/}?"] }, 154 "eagerness": "moderate" 155 }] 156 } 157 </script>
157 158 159</head> 160<body data-project="bella-duke"> 161 162<div class="case-page"> 163 164 <!-- Back button --> 165 <a class="btn home-btn" href="../" aria-label="Back to all work" data-stagger-item> 166 <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"> 167 <path d="M5.5 12.002H19"/> 168 <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"/> 169 </svg> 170 <span class="btn__label">All work</span> 171 </a> 172 173 <!-- Hero area --> 174 <div class="case-hero-area"> 175 176 <!-- Shader background --> 177 <div class="case-shader" aria-hidden="true"> 178 <div id="case-shader-gradient" style="width: 100%; height: 100%;"></div> 179 </div> 180 181 <!-- ===== HERO ===== --> 182 <header class="case-hero" data-stagger> 183 184 <!-- Page title --> 185 <h1 class="case-title">Turned Subscription Friction Into Trust</h1> 186 187 <!-- Company badge row --> 188 <div class="case-company"> 189 <!-- Bella+Duke wordmark SVG --> 190 <div class="case-logo-wrap"> 191 <svg class="case-logo" xmlns="http://www.w3.org/2000/svg" width="115.81" height="16" fill="none" viewBox="0 0 130.29 18" aria-label="Bella and Duke"> 192 <path d="M68.2988 5.34431C68.2988 4.96858 68.6048 4.66396 68.9815 4.66396C69.3587 4.66396 69.6647 4.96858 69.6647 5.34431C69.6647 5.72004 69.3587 5.93463 68.9815 5.93463C68.6048 5.93463 68.2988 5.72004 68.2988 5.34431ZM35.1021 15.8684H39.0295V0.304389H35.1021V15.8684ZM28.184 9.33148C28.184 9.75392 28.1616 10.1319 28.0947 10.5099H18.41C18.8563 11.7773 20.0166 12.6221 21.4671 12.6221C22.4936 12.6221 23.4085 12.133 24.0333 11.3326L27.0459 13.5115C25.5954 15.3125 23.5871 16.1352 21.3779 16.1352C17.4282 16.1352 14.4379 13.1558 14.4379 9.33148C14.4379 5.52946 17.3612 2.52781 21.3109 2.52781C25.2607 2.52781 28.184 5.41828 28.184 9.33148ZM24.4127 7.99743C23.9441 6.68561 22.806 5.88517 21.4002 5.88517C19.9943 5.88517 18.8563 6.70786 18.3877 7.99743H24.4127ZM29.3447 15.8684H33.2721V0.304389H29.3447V15.8684ZM130.197 10.5099H120.512C120.958 11.7773 122.118 12.6221 123.569 12.6221C124.596 12.6221 125.51 12.133 126.135 11.3326L129.148 13.5115C127.697 15.3125 125.689 16.1352 123.48 16.1352C119.53 16.1352 116.54 13.1558 116.54 9.33148C116.54 5.52946 119.463 2.52781 123.413 2.52781C127.362 2.52781 130.286 5.41828 130.286 9.33148C130.286 9.75392 130.263 10.1319 130.197 10.5099ZM126.515 7.99743C126.046 6.68561 124.908 5.88517 123.502 5.88517C122.096 5.88517 120.958 6.70786 120.489 7.99743H126.515ZM69.2897 1.85526C70.2535 2.18618 71.0704 2.83122 71.6185 3.66734C71.6605 3.73177 71.7591 3.71592 71.7801 3.64181C72.1363 2.36841 72.4911 1.10133 72.537 0.936646C72.6154 0.656472 72.7054 0.302297 72.3531 0.0741432C71.7415 -0.235429 70.494 0.446596 69.2533 1.70696C69.2065 1.75441 69.2267 1.83361 69.2897 1.85526ZM13.813 9.33148C13.813 13.2669 10.9343 16.1352 7.18543 16.1352C5.69031 16.1352 4.57457 15.6905 3.70428 14.9567V15.8684H0V0.304389H3.90511V3.50611C4.7754 2.88356 5.86885 2.52781 7.18543 2.52781C10.9343 2.52781 13.813 5.41828 13.813 9.33148ZM9.86322 9.2648C9.86322 7.39713 8.502 6.06307 6.73911 6.06307C4.95394 6.06307 3.5927 7.39713 3.5927 9.2648C3.5927 11.1325 4.95394 12.4665 6.73911 12.4665C8.502 12.4665 9.86322 11.1325 9.86322 9.2648ZM63.2597 14.9468C62.661 14.9468 62.1758 14.9687 62.1758 15.5652C62.1758 16.1617 62.661 16.6452 63.2597 16.6452C63.8584 16.6452 64.3435 16.1617 64.3435 15.5652C64.3435 14.9687 63.8584 14.9468 63.2597 14.9468ZM99.468 10.4877C99.468 11.755 98.7094 12.4888 97.5492 12.4888C96.411 12.4888 95.7192 11.755 95.7192 10.4877V2.79463H91.7918V11.1769C91.7918 14.2453 93.8447 16.1352 96.6788 16.1352C97.884 16.1352 98.955 15.7349 99.6467 14.979V15.8684H103.395V2.79463H99.468V10.4877ZM71.0177 6.96787H74.385V11.032H67.3963V15.9036C67.3963 17.0353 66.4954 17.984 65.3593 17.9996C65.0485 18.0039 64.7528 17.945 64.4867 17.8345C64.422 17.8076 64.4125 17.72 64.4695 17.6792C65.046 17.2663 65.4257 16.5669 65.4257 15.7731C65.4257 14.529 64.8304 13.5599 63.4024 13.5196C63.3531 13.5182
19263.3133 13.4792 63.3133 13.4302L63.3128 12.6054C63.3128 11.1378 62.4793 10.4531 61.4541 10.4531C60.9227 10.4531 60.3913 10.6761 60.0527 11.032H56.3194V6.96787H63.3094L63.3098 0.686849C63.3098 0.42002 63.3724 0.195575 63.5648 0.0843497C63.9844 -0.158404 64.7095 0.136024 65.58 0.797682C66.45 1.45934 67.3967 3.02926 67.3967 4.93117C67.3967 6.24453 66.7525 7.37874 65.868 7.37874C65.5843 7.37874 65.4038 7.32195 65.2808 7.2896C65.1047 7.24323 64.9363 7.26491 64.8304 7.41273C64.6808 7.62118 64.7387 7.87828 65.0061 8.00874C65.2774 8.14095 65.6044 8.18527 65.9485 8.18527C66.5198 8.18527 67.0585 7.91655 67.4091 7.49925C67.4443 7.45738 67.5098 7.45738 67.545 7.49925C67.8955 7.91655 68.4343 8.18527 69.0055 8.18527C69.9818 8.18527 70.7511 7.67475 71.0177 6.96787ZM64.6997 5.34761C64.6997 5.75086 65.0275 5.98117 65.4325 5.98117C65.8371 5.98117 66.1654 5.75086 66.1654 5.34761C66.1654 4.94433 65.8371 4.61741 65.4325 4.61741C65.0275 4.61741 64.6997 4.94433 64.6997 5.34761ZM66.1654 12.7316C66.1654 12.3283 65.8371 12.0014 65.4325 12.0014C65.028 12.0014 64.6997 12.3283 64.6997 12.7316C64.6997 13.1348 65.028 13.3651 65.4325 13.3651C65.8371 13.3651 66.1654 13.1348 66.1654 12.7316ZM117.298 2.79463H112.612L109.108 8.1086V0.304389H105.225V15.8684H109.108V9.99851L112.723 15.8684H117.588L112.969 8.95353L117.298 2.79463ZM86.1681 0.304389H90.0732V15.8684H86.3691V14.9567C85.4987 15.6905 84.3831 16.1352 82.8878 16.1352C79.1391 16.1352 76.2604 13.2669 76.2604 9.33148C76.2604 5.41828 79.1391 2.52781 82.8878 2.52781C84.2044 2.52781 85.2981 2.88356 86.1681 3.50611V0.304389ZM86.4805 9.2648C86.4805 7.39713 85.1194 6.06307 83.3344 6.06307C81.5713 6.06307 80.2101 7.39713 80.2101 9.2648C80.2101 11.1325 81.5713 12.4665 83.3344 12.4665C85.1194 12.4665 86.4805 11.1325 86.4805 9.2648ZM50.3657 2.79463H54.0698V15.8684H50.3657V14.9345C49.5176 15.6905 48.402 16.1352 46.8844 16.1352C43.1357 16.1352 40.257 13.2669 40.257 9.33148C40.257 5.41828 43.1357 2.52781 46.8844 2.52781C48.2683 2.52781 49.4284 2.9058 50.3657 3.72846V2.79463ZM50.4771 9.2648C50.4771 7.39713 49.116 6.06307 47.331 6.06307C45.5678 6.06307 44.2067 7.39713 44.2067 9.2648C44.2067 11.1325 45.5678 12.4665 47.331 12.4665C49.116 12.4665 50.4771 11.1325 50.4771 9.2648ZM61.2437 14.8319C61.0993 14.8588 60.9497 14.8732 60.7958 14.8732C60.1941 14.8732 59.6507 14.6573 59.2624 14.3102C59.2217 14.2736 59.1578 14.2837 59.127 14.329C58.7575 14.8703 58.2531 15.3989 58.0551 15.8152C57.6844 16.595 58.1005 17.5423 58.9071 17.8772C59.7141 18.2122 60.6818 17.8396 60.9767 17.0282C61.158 16.5301 61.1593 15.6405 61.3465 14.9418C61.3633 14.8788 61.308 14.8199 61.2437 14.8319ZM61.3581 12.0479C60.981 12.0479 60.675 12.3525 60.675 12.7283C60.675 13.104 60.981 13.3186 61.3581 13.3186C61.7353 13.3186 62.0408 13.104 62.0408 12.7283C62.0408 12.3525 61.7353 12.0479 61.3581 12.0479Z" fill="currentColor" /> 193 </svg> 194 </div> 195 <span class="case-company-text">Leading UK Raw Pet Food Provider, £26M Turnover</span> 196 </div> 197 198 <!-- Tags --> 199 <div class="case-tags" aria-label="Project type"> 200 <span class="case-tag"> 201 <svg class="tag-icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18" aria-hidden="true"><ellipse cx="6.25" cy="4.5" rx="2.25" ry="3"/><ellipse cx="2.75" cy="8.75" rx="1.5" ry="2"/><ellipse cx="11.75" cy="4.5" rx="2.25" ry="3"/><ellipse cx="15.25" cy="8.75" rx="1.5" ry="2"/><path d="m9,8c-2.29,0-5,3.0488-5,5.625,0,.6406.2852,1.2632.7842,1.709.4814.4292,1.1162.666,1.7871.666.8203,0,1.2764-.2812,1.6094-.4868.2656-.1641.4258-.2632.8193-.2632s.5537.0991.8193.2632c.333.2056.7891.4868,1.6094.4868.6709,0,1.3057-.2368,1.7881-.667.498-.4448.7832-1.0674.7832-1.708,0-2.5762-2.71-5.625-5-5.625Z"/></svg> 202 Petcare 203 </span> 204 </div> 205 206 </header><!-- /.case-hero --> 207 208 <!-- ===== HERO MOCKUP ===== --> 209 <div class="case-mockup-wrap" data-stagger-item> 210 <div class="case-mockup-scroll"> 211 <div class="mockup-group"> 212 <div class="mockup"> 213 <span class="thumb-wrap" data-thumbhash="OQgGBYCXeWV8Zod3iDiZX/pXqn+j"><picture><source type="image/avif" srcset="/bella-duke-mockup-768.avif 768w, /bella-duke-mockup-1280.avif 1280w, /bella-duke-mockup-2880.avif 2880w" sizes="(max-width: 767px) 85vw, 1280px"><source type="image/webp" srcset="/bella-duke-mockup-768.webp 768w, /bella-duke-mockup-1280.webp 1280w, /bella-duke-mockup-2880.webp 2880w" sizes="(max-width: 767px) 85vw, 1280px"><img src="/bella-duke-mockup-1280.png" srcset="/bella-duke-mockup-768.png 768w, /bella-duke-mockup-1280.png 1280w, /bella-duke-mockup.png 2880w" sizes="(max-width: 767px) 85vw, 1280px" width="2880" height="2048" alt="Bella and Duke project â Turned Subscription Friction Into Trust" fetchpriority="high" decoding="async"></picture></span> 214 </div> 215 </div> 216 </div> 217 <div class="mockup mockup--mobile">
218 <span class="thumb-wrap" data-thumbhash="OfgFAwCVeQh3fXqVhHwPfIQ="><picture><source type="image/avif" srcset="/bella-duke-mobile-mockup-375.avif 375w, /bella-duke-mobile-mockup-1125.avif 1125w" sizes="(max-width: 661px) 22vw, 333px"><source type="image/webp" srcset="/bella-duke-mobile-mockup-375.webp 375w, /bella-duke-mobile-mockup-1125.webp 1125w" sizes="(max-width: 661px) 22vw, 333px"><img src="/bella-duke-mobile-mockup-375.png" srcset="/bella-duke-mobile-mockup-375.png 375w, /bella-duke-mobile-mockup.png 1125w" sizes="(max-width: 661px) 22vw, 333px" alt="Bella and Duke mobile app home screen" width="1125" height="2446" fetchpriority="low" decoding="async"></picture></span> 219 </div> 220 </div> 221 222 </div><!-- /.case-hero-area --> 223 224 <!-- ===== MAIN CONTENT ===== --> 225 <main class="case-content" data-stagger> 226 227 <!-- ===== ROLES ===== --> 228 <div class="case-roles case-narrow" role="region" aria-label="Project roles"> 229 230 <div class="case-role"> 231 <div class="case-role-label"> 232 <svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 233 <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
23316.2313C2.74425 15.8317 2.05162 14.4186 2.60518 13.1674Z"/> 234 <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"/> 235 </svg> 236 Responsibilities 237 </div> 238 <p class="case-role-value">UI Design · UX Design · User Research · Strategy & Concept</p> 239 </div> 240 241 <div class="case-role"> 242 <div class="case-role-label"> 243 <svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 244 <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"/> 245 <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"/> 246 <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"/> 247 <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"/> 248 </svg> 249 Team 250 </div> 251 <p class="case-role-value">1 Head of Product · 2 Full Stack Developers · CRM & Customer Care</p> 252 </div> 253 254 </div><!-- /.case-roles --> 255 256 <!-- ===== CAR CARD ===== --> 257 <div class="case-car case-narrow" role="region" aria-label="Context, Action, Results"> 258 259 <!-- Context --> 260 <labelled-section label="Context"> 261 <svg slot="icon" width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 262 <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"/> 263 </svg> 264 <div class="case-car-body-card"> 265 <p class="case-car-body">Bella & Duke sells raw pet food by subscription, and customers love it. The portal was the problem: a bare account area where managing deliveries, feeding amounts, pricing and multiple pets around real life bred confusion and eroded trust.</p> 266 </div> 267 </labelled-section> 268 269 <!-- Action --> 270 <labelled-section label="Action"> 271 <svg slot="icon" width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 272 <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"/> 273 <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"/> 274 </svg> 275 <div class="case-car-body-card"> 276 <p class="case-car-body">As Growth Product Designer I pulled usability tests, support data, surveys and community insight into one prioritised set of problems, then built an OOUX object model to anchor the work. I led much of the strategic framing: away from isolated portal sections, towards the pet routines customers actually plan around.</p> 277 </div> 278 </labelled-section> 279 280 <!-- Results --> 281 <labelled-section label="Results"> 282 <svg slot="icon" width="18" height="18" viewBox="0 0 18 18" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 283 <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"/> 284 <rect x="10.5" y="3.5" width="3.5" height="8.5" rx="1" ry="1"/> 285 <rect x="5.5" y="7" width="3.5" height="5" rx="1" ry="1"/> 286 </svg> 287 <div class="case-car-body-card"> 288 <p class="case-car-body">Targeted the confusion behind 25 to 35% of Customer Care contacts with clearer subscription flows</p> 289 <p class="case-car-body">An OOUX object model gave the team a shared language for messy multi-pet subscription logic</p> 290 <p class="case-car-body">Turned a transactional account area into a retention strategy the business could build on</p> 291 </div> 292 </labelled-section> 293 294 </div><!-- /.case-car --> 295 296 <!-- ===== OUTCOME IMAGERY ===== --> 297 <section class="case-images" aria-label="Outcome imagery"> 298 299 300 <!-- 1 â Home dashboard with --> 301 <figure class="image-container"> 302 <div class="image-container__media">
303 <span class="thumb-wrap" data-thumbhash="OQgGBYCXeWV8Zod3iDiZX/pXqn+j"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/home.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/home.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/home.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/home.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Home dashboard with upcoming deliveries and what's inside each box" width="2880" height="2048" loading="lazy" decoding="async"></span> 304 </div> 305 <figcaption class="image-container__caption"> 306 <span class="image-container__text">Home dashboard with upcoming deliveries and what's inside each box</span> 307 <span class="image-container__meta"> 308 <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> 309 <span class="image-container__number" aria-hidden="true"></span> 310 </span> 311 </figcaption> 312 </figure> 313 314 <!-- 2 â Dispatch state with --> 315 <figure class="image-container"> 316 <div class="image-container__media"> 317 <span class="thumb-wrap" data-thumbhash="eggCBYBWinePeIiTWsdna8eCf0v4"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/home-dispatched.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/home-dispatched.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/home-dispatched.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/home-dispatched.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Dispatch state with live courier tracking once an order is on its way" width="2880" height="2048" loading="lazy" decoding="async"></span> 318 </div> 319 <figcaption class="image-container__caption"> 320 <span class="image-container__text">Dispatch state with live courier tracking once an order is on its way</span> 321 <span class="image-container__meta"> 322 <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> 323 <span class="image-container__number" aria-hidden="true"></span> 324 </span> 325 </figcaption> 326 </figure> 327 328 <!-- 0 â Interactive paw navigation demo --> 329 <figure class="image-container paw-demo-figure"> 330 <div class="image-container__media"> 331 <div class="paw-demo" data-paw-demo> 332 <!-- Vertical seam the paw emerges from: a faint full-height divider in 333 the gutter. The paw's arm is hard-clipped at this line (the JS aligns 334 the clip and this line to the paw's shoulder), so the trailing end 335 always reads as a clean straight cut â no soft gradient to colour- 336 match against the background, so no hairline is possible. --> 337 <span class="paw-demo__seam" data-paw-seam aria-hidden="true"></span> 338 <div class="paw-demo__stage"> 339 <!-- One shared hover/press fill that slides between rows (JS moves it 340 to the link the paw is on); sits behind the link content. -->
341 <span class="paw-demo__pill" data-paw-pill aria-hidden="true"></span> 342 <ul class="paw-demo__links" role="presentation"> 343 <li class="paw-demo__item" role="presentation"> 344 <button class="paw-demo__link" data-paw-link type="button"> 345 <span class="paw-demo__icon" data-paw-icon><svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> 346<path d="M16.7949 2.03536C16.3235 1.68613 15.6791 1.68613 15.2077 2.03536L3.92767 10.3909C3.13513 10.9783 2.66797 11.9056 2.66797 12.8917V24.4401C2.66797 27.1409 4.85604 29.329 7.55686 29.329H24.4457C27.1465 29.329 29.3346 27.1409 29.3346 24.4401V12.8917C29.3346 11.9056 28.8678 10.9785 28.0753 10.3911L16.7949 2.03536Z" fill="#003A3A" fill-opacity="0.4"/> 347<path d="M25.7799 8.69375L23.1133 6.71844V4.88802C23.1133 4.15165 23.7103 3.55469 24.4466 3.55469C25.183 3.55469 25.7799 4.15165 25.7799 4.88802V8.69375Z" fill="#003A3A"/> 348<path d="M15.9978 29.329V21.329C15.9978 19.3653 14.406 17.7734 12.4423 17.7734C10.4786 17.7734 8.88672 19.3653 8.88672 21.329V29.329H15.9978Z" fill="#003A3A"/> 349<path fill-rule="evenodd" clip-rule="evenodd" d="M17.7734 17.3333C17.7734 16.597 18.3704 16 19.1068 16H21.7734C22.5098 16 23.1068 16.597 23.1068 17.3333C23.1068 18.0697 22.5098 18.6667 21.7734 18.6667H19.1068C18.3704 18.6667 17.7734 18.0697 17.7734 17.3333Z" fill="#003A3A"/> 350</svg></span> 351 <span class="paw-demo__label">Home</span> 352 </button> 353 </li> 354 <li class="paw-demo__item" role="presentation"> 355 <button class="paw-demo__link" data-paw-link type="button"> 356 <span class="paw-demo__icon paw-demo__icon--pets" data-paw-icon></span> 357 <span class="paw-demo__label">Pets</span> 358 </button> 359 </li> 360 <li class="paw-demo__item" role="presentation"> 361 <button class="paw-demo__link" data-paw-link type="button"> 362 <span class="paw-demo__icon" data-paw-icon><svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> 363<g> 364<path opacity="0.4" d="M15.9996 30.2218C8.15761 30.2218 1.77734 23.8415 1.77734 15.9996C1.77734 8.15761 8.15761 1.77734 15.9996 1.77734C23.8415 1.77734 30.2218 8.15761 30.2218 15.9996C30.2218 23.8415 23.8415 30.2218 15.9996 30.2218Z" fill="#003A3A"/> 365<path d="M21.1485 19.1212C20.8748 18.8474 20.4979 18.7283 20.1121 18.7852C17.3886 19.2101 14.5797 19.2083 11.8668 18.7834C11.4863 18.7266 11.1183 18.8457 10.8499 19.1123C10.5761 19.3843 10.4463 19.7808 10.5068 20.1754C10.9157 22.8617 13.2766 24.8883 15.9983 24.8883C18.7201 24.8883 21.0828 22.8599 21.4899 20.1701C21.5485 19.7861 21.4205 19.3932 21.1485 19.1212Z" fill="#003A3A"/> 366<path d="M12.8872 15.9983C12.151 15.9983 11.5538 15.4011 11.5538 14.6649C11.5538 14.1744 11.1554 13.776 10.6649 13.776C10.1744 13.776 9.77604 14.1744 9.77604 14.6649C9.77604 15.4011 9.17889 15.9983 8.44271 15.9983C7.70653 15.9983 7.10938 15.4011 7.10938 14.6649C7.10938 12.704 8.70404 11.1094 10.6649 11.1094C12.6258 11.1094 14.2205 12.704 14.2205 14.6649C14.2205 15.4011 13.6233 15.9983 12.8872 15.9983Z" fill="#003A3A"/> 367<path d="M23.5551 15.9983C22.8189 15.9983 22.2218 15.4011 22.2218 14.6649C22.2218 14.1744 21.8234 13.776 21.3329 13.776C20.8424 13.776 20.444 14.1744 20.444 14.6649C20.444 15.4011 19.8469 15.9983 19.1107 15.9983C18.3745 15.9983 17.7773 15.4011 17.7773 14.6649C17.7773 12.704 19.372 11.1094 21.3329 11.1094C23.2938 11.1094 24.8885 12.704 24.8885 14.6649C24.8885 15.4011 24.2913 15.9983 23.5551 15.9983Z" fill="#003A3A"/> 368</g> 369</svg></span> 370 <span class="paw-demo__label">Benefits</span> 371 </button> 372 </li> 373 <li class="paw-demo__item" role="presentation"> 374 <button class="paw-demo__link" data-paw-link type="button"> 375 <span class="paw-demo__icon paw-demo__icon--account" data-paw-icon></span> 376 <span class="paw-demo__label">Account</span> 377 </button> 378 </li> 379 </ul> 380 <div class="paw-demo__paw" data-paw aria-hidden="true"> 381 <!-- Sticker outline (Figma node 3518:4900, layer "Outline": #F1EEE6, 382 2pt). Implemented as a dilate filter on the whole paw+arm wrapper 383 (.paw-demo__bones) rather than a static stroked path: the filter 384 re-outlines the LIVE composite of both bones every frame, so it 385 follows every reach/stretch/flare/lift/press 1:1 automatically â 386 a single union path could not track two independently-animated 387 bones, and stroking each bone separately would draw the cream 388 edge straight through the wrist join. Two radii: desktop 2.75px 389 for the 2Ã paw; mobile 2px for the 1Ã paw. NOTE the mobile radius 390 is NOT a strict half of desktop (a proportional ~1.4px washes out
391 under anti-aliasing and the cream edge â only ever visible where 392 the paw overlaps a darker nav icon â disappears entirely). A 393 sticker outline needs a roughly fixed perceptual weight, so the 394 1à radius is kept heavier than a literal scale-down so it still 395 reads when the paw crosses an icon. --> 396 <svg class="paw-demo__defs" width="0" height="0" aria-hidden="true" focusable="false"> 397 <defs> 398 <filter id="paw-outline" x="-50%" y="-120%" width="260%" height="360%" color-interpolation-filters="sRGB"> 399 <feMorphology in="SourceAlpha" operator="dilate" radius="2.75" result="spread"/> 400 <feFlood flood-color="#F1EEE6" result="cream"/> 401 <feComposite in="cream" in2="spread" operator="in" result="outline"/> 402 <feMerge><feMergeNode in="outline"/><feMergeNode in="SourceGraphic"/></feMerge> 403 </filter> 404 <filter id="paw-outline-m" x="-50%" y="-120%" width="260%" height="360%" color-interpolation-filters="sRGB"> 405 <feMorphology in="SourceAlpha" operator="dilate" radius="2" result="spread"/> 406 <feFlood flood-color="#F1EEE6" result="cream"/> 407 <feComposite in="cream" in2="spread" operator="in" result="outline"/> 408 <feMerge><feMergeNode in="outline"/><feMergeNode in="SourceGraphic"/></feMerge> 409 </filter> 410 </defs> 411 </svg> 412 <div class="paw-demo__paw-rig" data-paw-rig> 413 <div class="paw-demo__bones"> 414 <!-- Paw bone: the chosen "right" silhouette (2026-06-21) â a single 415 solid four-toe paw (on-brand single colour, no separate claws). 416 Reprocessed from the Figma portrait source in two steps so it 417 drops into the rig with the SAME scaling behaviour as the previous 418 paw: (1) inner transform rotates it 90° + scales it into the pad 419 zone (toes pointing right); (2) outer transform fits its bounding 420 box to the previous paw's exact envelope (x 25.87â47.52, y 421 0.03â14.33 in viewBox units) so the wrist-pivot lift-scale (pawS, 422 about 62% 52%) grows it identically â origin sits ~18% inside the 423 paw, not at its edge. Rides on the arm's end; never stretches. 424 This is the UNTOUCHED Figma paw shape (node 3466:4781): toes, pad 425 AND base exactly as drawn. The wrist join is kept clean by making 426 the ARM follow the paw (its top/bottom edges fan to meet the paw's 427 base at every size â see flareArmPath + per-pose flare in 428 bella-duke-paw.js), NOT by reshaping the paw's base. --> 429 <svg class="paw-demo__bone paw-demo__bone--paw" data-paw-hand viewBox="0 0 48 15" fill="none" xmlns="http://www.w3.org/2000/svg"> 430 <g transform="translate(-10.345 -2.4818) scale(1.21629 1.1683) translate(48.69 2.05) scale(0.457) rotate(90)"> 431 <path d="M20.3027 6.89906C20.8385 6.493 21.1028 6.37858 21.7548 6.28066C22.4068 6.18274 23.0104 6.45638 23.0104 6.45638C28.4218 9.08156 26.3574 18.5488 26.0032 23.4611C27.9904 26.6818 26.521 31.3461 25.845 34.7081C25.1691 38.07 20.5369 41.3982 14.8158 41.3982C9.09476 41.3982 4.07567 36.8105 3.78661 34.7081C3.49756 32.6057 3.42472 31.6475 3.22715 29.9561C3.02959 28.2647 2.45225 27.0578 2.0379 25.6598C0.799201 21.4806 -1.33278 14.6073 2.02712 10.8492C2.73253 10.0602 3.75477 9.36702 4.83183 9.76791C5.14186 7.10741 6.38911 4.51491 9.09476 3.60078L10.1947 3.39839C11.1392 3.43292 11.6408 3.85318 12.1126 4.62849
431C12.6543 3.72711 13.4821 2.89002 14.5237 2.60151C17.3986 1.80523 19.5787 4.35402 20.3027 6.89906Z" fill="#003A3A"/> 432 </g> 433 </svg> 434 <!-- Arm bone: stretches length-wise from the shoulder (left edge). Drawn over the wrist join. --> 435 <svg class="paw-demo__bone paw-demo__bone--arm" data-paw-arm viewBox="0 0 48 15" fill="none" xmlns="http://www.w3.org/2000/svg"> 436 <path d="M29.4442 1.93415C33.8087 1.52629 33.2937 4.32184 33.2937 7.82202C33.2937 11.3222 33.2374 14.3659 29.4442 13.7099C26.1099 13.1332 3.35844 13.1092 -0.0296516 13.1333C-0.0284386 9.52802 0.0238152 5.9189 -0.0327606 2.31392C3.6204 2.23012 20.4248 2.77702 29.4442 1.93415Z" fill="#003A3A"/> 437 </svg> 438 </div><!-- /.paw-demo__bones --> 439 </div> 440 </div> 441 </div> 442 </div> 443 </div> 444 <figcaption class="image-container__caption"> 445 <span class="image-container__text">A playful paw guides you through the redesigned portal<span class="paw-demo-caption__interactive"> â hover in to take the lead</span></span> 446 <span class="image-container__meta"> 447 <!-- Hugeicons "CursorPointer01Icon" (free library) â only on the paw-demo 448 figure. Original image icon's overrides retained on the root: the 449 image-container__icon class, 24px viewBox, fill:none, stroke:currentColor, 450 1.5 weight, round caps/joins. The pointer outline inherits the stroke; 451 the two ripple arcs are filled (fill:currentColor, stroke:none) as 452 Hugeicons draws them. --> 453 <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> 454 <span class="image-container__number" aria-hidden="true"></span> 455 </span> 456 </figcaption> 457 </figure> 458 459 <!-- 3 â Changing what's inside --> 460 <figure class="image-container"> 461 <div class="image-container__media"> 462 <span class="thumb-wrap" data-thumbhash="OggCBYAJoYZ0mzYqi2GIpfl5qo/V"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/change-whats-inside.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/change-whats-inside.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/change-whats-inside.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/change-whats-inside.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Changing what's inside upcoming deliveries â recipes and pack quantities" width="2880" height="2048" loading="lazy" decoding="async"></span> 463 </div> 464 <figcaption class="image-container__caption"> 465 <span class="image-container__text">Changing what's inside upcoming deliveries â recipes and pack quantities</span> 466 <span class="image-container__meta"> 467 <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>
468 <span class="image-container__number" aria-hidden="true"></span> 469 </span> 470 </figcaption> 471 </figure> 472 473 <!-- 4 â Pet profiles capturing --> 474 <figure class="image-container"> 475 <div class="image-container__media"> 476 <span class="thumb-wrap" data-thumbhash="eggCDYBzBH+Eamdas8pHk2drYLYG"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/pet-profile.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/pet-profile.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/pet-profile.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/pet-profile.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Pet profiles capturing breed, age and feeding plan per dog" width="2880" height="2048" loading="lazy" decoding="async"></span> 477 </div> 478 <figcaption class="image-container__caption"> 479 <span class="image-container__text">Pet profiles capturing breed, age and feeding plan per dog</span> 480 <span class="image-container__meta"> 481 <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> 482 <span class="image-container__number" aria-hidden="true"></span> 483 </span> 484 </figcaption> 485 </figure> 486 487 <!-- 5 â Member benefits â --> 488 <figure class="image-container"> 489 <div class="image-container__media"> 490 <span class="thumb-wrap" data-thumbhash="sBgKFYL7lYR6dodqhvhGhCV4UJQI"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/benefits.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/benefits.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/benefits.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/benefits.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Member benefits â 24/7 video vet access and partner discounts" width="2880" height="2048" loading="lazy" decoding="async"></span> 491 </div> 492 <figcaption class="image-container__caption"> 493 <span class="image-container__text">Member benefits â 24/7 video vet access and partner discounts</span> 494 <span class="image-container__meta"> 495 <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> 496 <span class="image-container__number" aria-hidden="true"></span> 497 </span> 498 </figcaption> 499 </figure> 500 501 <!-- The Pets profile + Benefits screens as they appear in the phone app, 502 composited into a single image so the pair gets one frame, one caption 503 and one image number. Matches the mobile row on the Homemove page; both 504 assets come out of scripts/build-mobile-screens.py, which bakes the 505 spacing, corner radii and hairline strokes in so they hold at every 506 width rather than collapsing as a CSS grid's columns narrow. The image 507 has no background of its own â it is transparent, so the page shows
508 through inside the frame and it works in dark mode too. --> 509 <figure class="image-container image-container--screen-row"> 510 <div class="image-container__media"> 511 <span class="thumb-wrap" data-thumbhash="sxiGA4A1fIB4h0twlQJXGQhHiHdzikg="><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/mobile-screens.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/mobile-screens.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/mobile-screens.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/mobile-screens.png 2880w" sizes="(max-width: 809px) 700px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Bella and Duke mobile app â two screens side by side: Daisy's pet profile with breed, age and feeding plan, and the Benefits screen with 24/7 video vet access" width="2880" height="1856" loading="lazy" decoding="async"></span> 512 </div> 513 <figcaption class="image-container__caption"> 514 <span class="image-container__text">Pet profiles and member benefits, managed on the go in the phone app</span> 515 <span class="image-container__meta"> 516 <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> 517 <span class="image-container__number" aria-hidden="true"></span> 518 </span> 519 </figcaption> 520 </figure> 521 522 <!-- The same screens again, one per row, for phones and small tablets. 523 Below 810px the composited row above is swapped out for these: a wide 524 row has to scroll sideways at that width, and a single caption at the 525 bottom of it ends up a long way from the first screen. Each screen 526 gets its own frame, caption and number instead. 527 528 Exactly one of the two is ever in the layout â the hidden one is 529 display:none, so it generates no boxes and takes no part in the image 530 counter, and its lazy images never enter the viewport so they are not 531 fetched. The trade is that this block and the composite carry the same 532 content twice: edit a caption here and check the one above too. --> 533 <div class="screen-row-stack"> 534 535 <!-- Pet profile --> 536 <figure class="image-container"> 537 <div class="image-container__media"> 538 <span class="thumb-wrap" data-thumbhash="OggCAwD1dShHfYaFB45U8wg="><img src="/cdn-cgi/image/format=auto,width=1206,quality=85/bella-duke/pet-profile-mobile.png" srcset="/cdn-cgi/image/format=auto,width=640,quality=85/bella-duke/pet-profile-mobile.png 640w, /cdn-cgi/image/format=auto,width=1206,quality=85/bella-duke/pet-profile-mobile.png 1206w" sizes="(max-width: 607px) calc(100vw - 48px), 560px" alt="Bella and Duke mobile app â Daisy's pet profile with breed, age and feeding plan" width="1206" height="2622" loading="lazy" decoding="async"></span> 539 </div> 540 <figcaption class="image-container__caption"> 541 <span class="image-container__text">The pet profile and feeding plan, managed on the go in the phone app</span> 542 <span class="image-container__meta"> 543 <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>
544 <span class="image-container__number" aria-hidden="true"></span> 545 </span> 546 </figcaption> 547 </figure> 548 549 <!-- Benefits --> 550 <figure class="image-container"> 551 <div class="image-container__media"> 552 <span class="thumb-wrap" data-thumbhash="KxkSGwSYefd3d4h2WHSANAc="><img src="/cdn-cgi/image/format=auto,width=1206,quality=85/bella-duke/benefits-mobile.png" srcset="/cdn-cgi/image/format=auto,width=640,quality=85/bella-duke/benefits-mobile.png 640w, /cdn-cgi/image/format=auto,width=1206,quality=85/bella-duke/benefits-mobile.png 1206w" sizes="(max-width: 607px) calc(100vw - 48px), 560px" alt="Bella and Duke mobile app â Benefits screen with 24/7 video vet access" width="1206" height="2622" loading="lazy" decoding="async"></span> 553 </div> 554 <figcaption class="image-container__caption"> 555 <span class="image-container__text">Member benefits and partner codes, a tap away in your pocket</span> 556 <span class="image-container__meta"> 557 <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> 558 <span class="image-container__number" aria-hidden="true"></span> 559 </span> 560 </figcaption> 561 </figure> 562 563 </div><!-- /.screen-row-stack --> 564 565 <!-- 6 â Account profile with --> 566 <figure class="image-container"> 567 <div class="image-container__media"> 568 <span class="thumb-wrap" data-thumbhash="OggCBYCEE3+Ta2dssstXk/tMdr+E"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/account.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/account.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/account.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/account.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Account profile with personal details and contact options" width="2880" height="2048" loading="lazy" decoding="async"></span> 569 </div> 570 <figcaption class="image-container__caption"> 571 <span class="image-container__text">Account profile with personal details and contact options</span> 572 <span class="image-container__meta"> 573 <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> 574 <span class="image-container__number" aria-hidden="true"></span> 575 </span> 576 </figcaption> 577 </figure> 578 579 <!-- 7 â Plan & billing --> 580 <figure class="image-container"> 581 <div class="image-container__media"> 582 <span class="thumb-wrap" data-thumbhash="9jgKJYq5dnaGeHhvg5d3k0Z4YIMH"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/account-subscription.png" srcset="/cdn-cgi/image/format=auto,width=768,quality=85/bella-duke/account-subscription.png 768w, /cdn-cgi/image/format=auto,width=1280,quality=85/bella-duke/account-subscription.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/bella-duke/account-subscription.png 2880w" sizes="(max-width: 809px) 600px, (max-width: 1199px) calc(100vw - 48px), 1280px" alt="Plan & billing â subscription, payment method and renewal" width="2880" height="2048" loading="lazy" decoding="async"></span> 583 </div>
584 <figcaption class="image-container__caption"> 585 <span class="image-container__text">Plan & billing â subscription, payment method and renewal</span> 586 <span class="image-container__meta"> 587 <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> 588 <span class="image-container__number" aria-hidden="true"></span> 589 </span> 590 </figcaption> 591 </figure> 592 593 </section><!-- /.case-images --> 594 595 <!-- ===== NEXT PROJECT ===== --> 596 <section class="case-next case-narrow" aria-label="Next project"> 597 <labelled-section label="Next project"> 598 <next-project-card 599 href="../homemove/" 600 title="Orchestrated clarity in chaos"> 601 <div class="mockup"> 602 <span class="thumb-wrap" data-thumbhash="/PcFBYAtqrKXl3gPXJWWwjHPoKcI"><img src="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove-mockup.png" srcset="/cdn-cgi/image/format=auto,width=1280,quality=85/homemove-mockup.png 1280w, /cdn-cgi/image/format=auto,width=2880,quality=85/homemove-mockup.png 2880w" sizes="(min-width: 1280px) 1216px, 85vw" alt="Homemove â Orchestrated Clarity In Chaos" width="2880" height="2048" loading="lazy" decoding="async"></span> 603 </div> 604 </next-project-card> 605 </labelled-section> 606 </section> 607 608 <!-- ===== NEXT STEPS CTA ===== --> 609 <section class="case-cta case-narrow" aria-label="Get in touch"> 610 <labelled-section label="Next steps"> 611 <next-steps-cta></next-steps-cta> 612 </labelled-section> 613 </section> 614 615 <!-- ===== FOOTER ===== --> 616 <footer class="case-footer case-narrow"> 617 <p class="copyright">© 2026 John Miller</p> 618 </footer> 619 620 </main><!-- /.case-content --> 621 622</div><!-- /.case-page --> 623 624<!-- Shader initialization -->
625<script type="module"> 626 import { 627 ShaderMount, 628 brandColor, 629 cardBg, 630 buildShaderColors, 631 siteFragmentShader, 632 createShaderUniforms, 633 SHADER_MAX_PIXELS, 634 getCardScale, 635 getHeroScale, 636 getHeroSpeed, 637 onBreakpointChange, 638 shaderFrameOffset, 639 averageColor, 640 createFadeColorSampler, 641 createShaderVisibilityGate, 642 } from '../shader-base.js?v=1'; 643 644 // Brand stops use `brandColor()` so light-mode pastels are auto-darkened 645 // in dark mode. `cardBg()` is white in light, near-black in dark. Wrapped 646 // in builder functions so the matchMedia change listener below can 647 // re-evaluate brandColor/cardBg with the fresh isDark() result when the 648 // user toggles OS theme mid-session. 649 const buildHeroColors = () => buildShaderColors({ brand: [brandColor(0.9, 0.045, 194.8), brandColor(0.9, 0.045, 158.1)], bg: brandColor(0.959, 0.009, 84.6) }); 650 const buildNextColors = () => buildShaderColors({ brand: [brandColor(0.899, 0.050, 279.1), brandColor(0.915, 0.044, 21.5)], bg: cardBg() }); 651 652 // Tint the next-card's bottom fade with its own shader palette, mirroring the 653 // homepage cards. Re-run on theme change so it tracks light/dark. 654 const applyNextFade = () => 655 document.getElementById('next-card-shader')?.closest('.next-card') 656 ?.style.setProperty('--card-fade-color', averageColor(buildNextColors())); 657 658 const prefersReducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches; 659 660 // Mount registry â each entry pairs a ShaderMount with its colour builder 661 // so the scheme-change listener can repush fresh u_colors to all mounts. 662 const mounts = []; 663 let fadeSampler = null; // live --card-fade-color sampler for the next-card 664 665 // Lightbox shader-pause integration ('site:pause-shaders' on open, 666 // 'site:resume-shaders' on destroy, from photoswipe-init.js) is handled 667 // inside each mount's visibility gate (createShaderVisibilityGate), which 668 // also pauses a shader whenever it's scrolled off-screen â see 669 // shader-base.js. Resuming through the gate re-checks visibility, so a 670 // lightbox closed mid-article doesn't restart an off-screen shader. 671 const speedFor = () => (prefersReducedMotion ? 0 : getHeroSpeed()); 672 673 async function initShaders() { 674 // Yield frames between compiles so the entrance animation's 675 // main-thread paint (filter blur) isn't blocked by GL compilation. 676 const yieldFrames = () => 677 new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); 678 679 await yieldFrames(); 680 681 let heroMount = null; 682 let nextMount = null; 683 let heroGate = null; 684 let nextGate = null; 685 686 // Hero shader â container opacity fade is handled in homemove.css 687 const heroEl = document.getElementById('case-shader-gradient'); 688 if (heroEl) { 689 const heroColors = buildHeroColors(); 690 try { 691 heroMount = new ShaderMount(heroEl, siteFragmentShader, { 692 ...createShaderUniforms(), 693 u_scale: getHeroScale(), 694 u_colors: heroColors, 695 u_colorsCount: heroColors.length, 696 }, undefined, prefersReducedMotion ? 0 : getHeroSpeed(), shaderFrameOffset('gradient-bella-duke')); 697 heroMount.setMaxPixelCount(SHADER_MAX_PIXELS); 698 heroGate = createShaderVisibilityGate(heroEl, heroMount, speedFor); 699 mounts.push({ mount: heroMount, build: buildHeroColors }); 700 await yieldFrames(); 701 } catch (e) { heroMount = null; } 702 } 703 704 // Next-card shader (Homemove colors from homepage) 705 const nextCardShader = document.getElementById('next-card-shader'); 706 if (nextCardShader) { 707 try { 708 const nextColors = buildNextColors(); 709 nextMount = new ShaderMount(nextCardShader, siteFragmentShader, { 710 ...createShaderUniforms(), 711 u_colors: nextColors, 712 u_colorsCount: nextColors.length, 713 }, undefined, prefersReducedMotion ? 0 : getHeroSpeed(), shaderFrameOffset('gradient-homemove')); 714 nextMount.setMaxPixelCount(SHADER_MAX_PIXELS); 715 nextGate = createShaderVisibilityGate(nextCardShader, nextMount, speedFor); 716 mounts.push({ mount: nextMount, build: buildNextColors }); 717 applyNextFade(); 718 const nextCard = nextCardShader.closest('.next-card'); 719 if (nextCard && !prefersReducedMotion) { 720 fadeSampler = createFadeColorSampler(nextMount, nextCard, { getSpeed: getHeroSpeed, isVisible: () => nextGate.visible }); 721 fadeSampler.start(); 722 } 723 } catch (e) { nextMount = null; } 724 } 725 726 onBreakpointChange(() => { 727 if (heroMount) { 728 heroMount.setUniforms({ u_scale: getHeroScale() }); 729 heroGate.apply(); 730 } 731 if (nextMount) { 732 nextMount.setUniforms({ u_scale: getCardScale() }); 733 nextGate.apply(); 734 } 735 }); 736 } 737 738 // React to OS theme changes without requiring a page refresh â rebuilds 739 // each mount's u_colors via its colour builder so brandColor/cardBg are 740 // re-evaluated with the new isDark() result. URL params (?dark / ?light) 741 // override OS, so the rebuild stays locked if a param is present. 742 matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { 743 for (const entry of mounts) { 744 const colors = entry.build(); 745 entry.mount.setUniforms({ u_colors: colors, u_colorsCount: colors.length }); 746 } 747 applyNextFade(); 748 fadeSampler?.snap(); 749 }); 750 751 initShaders(); 752</script>
752 753 754</body> 755</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.