1<!DOCTYPE html><html lang="en" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- Mark that JS is available, as early as possible (before first paint). 2 Scroll-reveal initial states (opacity:0 / translateY) are scoped to 3 `.js` so that when JS is blocked/fails, content stays fully visible 4 instead of being permanently hidden. Must NOT be deferred. -->
4<script>document.documentElement.classList.add("js");</script>
4<link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><!-- The site is a fixed light design: declare that so browsers don't 5 auto-darken form controls/scrollbars, and tint mobile browser chrome 6 to match the white site header. --><meta name="color-scheme" content="light"><meta name="theme-color" content="#ffffff"><!-- Feed discovery: announce the RSS feed to readers and aggregators. --><link rel="alternate" type="application/rss+xml" title="Property Hub" href="/rss.xml"><!-- Consent Mode defaults must exist before either CookieYes or GTM. GTM 7 can then load immediately and send consent-safe cookieless pings while 8 advertising, analytics and behavioural vendors remain denied. -->
8<script> 9 (function () { 10 window.dataLayer = window.dataLayer || []; 11 window.gtag = window.gtag || function () { window.dataLayer.push(arguments); }; 12 13 window.gtag("consent", "default", { 14 ad_storage: "denied", 15 ad_user_data: "denied", 16 ad_personalization: "denied", 17 analytics_storage: "denied", 18 functionality_storage: "denied", 19 personalization_storage: "denied", 20 security_storage: "granted", 21 wait_for_update: 2000, 22 }); 23 window.gtag("set", "ads_data_redaction", true); 24 25 function cookieYesCategories() { 26 var match = document.cookie.match(/(?:^|;\s*)cookieyes-consent=([^;]*)/); 27 if (!match) return {}; 28 return decodeURIComponent(match[1]).split(",").reduce(function (values, item) { 29 var separator = item.indexOf(":"); 30 if (separator === -1) return values; 31 values[item.slice(0, separator)] = item.slice(separator + 1); 32 return values; 33 }, {}); 34 } 35 36 function isGranted(value) { 37 return /^(yes|true)$/i.test(value || ""); 38 } 39 40 function updateGoogleConsent() { 41 var categories = cookieYesCategories(); 42 var analytics = isGranted(categories.analytics); 43 var advertising = isGranted(categories.advertisement); 44 var functional = isGranted(categories.functional); 45 46 window.gtag("consent", "update", { 47 ad_storage: advertising ? "granted" : "denied", 48 ad_user_data: advertising ? "granted" : "denied", 49 ad_personalization: advertising ? "granted" : "denied", 50 analytics_storage: analytics ? "granted" : "denied", 51 functionality_storage: functional ? "granted" : "denied", 52 personalization_storage: functional ? "granted" : "denied", 53 }); 54 } 55 56 updateGoogleConsent(); 57 document.addEventListener("cookie_consent_update", updateGoogleConsent); 58 })(); 59 </script>
59<!-- CookieYes â deferred and non-blocking. -->
59<script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/b895e82d8e1c52eed0a7eb64/script.js" defer></script>
59<!-- Google Tag Manager â load immediately on the canonical production host. 60 Consent Mode above controls storage/vendor activation. The hostname 61 guard excludes localhost and every Netlify deploy-preview hostname. -->
61<script> 62 (function () { 63 if (!/^(www\.)?propertyhub\.net$/i.test(window.location.hostname)) return; 64 (function (w, d, s, l, i) { 65 w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); 66 var f = d.getElementsByTagName(s)[0]; 67 var j = d.createElement(s); 68 var dl = l != "dataLayer" ? "&l=" + l : ""; 69 j.async = true; 70 j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; 71 f.parentNode.insertBefore(j, f); 72 })(window, document, "script", "dataLayer", "GTM-PQS7STW"); 73 })(); 74 </script>
74<!-- End Google Tag Manager --><!-- SEO --><title>About Property Hub | Where Serious Investors Are Built</title><meta name="description" content="Learn about Property Hub - education for people who want to invest themselves, and a done-with-you service for people who'd rather not."><link rel="canonical" href="https://propertyhub.net/about/"><!-- Open Graph --><meta property="og:title" content="About Property Hub | Where Serious Investors Are Built"><meta property="og:description" content="Learn about Property Hub - education for people who want to invest themselves, and a done-with-you service for people who'd rather not."><meta property="og:image" content="https://assets.propertyhub.net/homepage/hero-manchester-dusk-1400w.webp"><meta property="og:url" content="https://propertyhub.net/about/"><meta property="og:type" content="website"><meta property="og:site_name" content="Property Hub"><!-- Twitter --><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="About Property Hub | Where Serious Investors Are Built"><meta name="twitter:description" content="Learn about Property Hub - education for people who want to invest themselves, and a done-with-you service for people who'd rather not."><meta name="twitter:image" content="https://assets.propertyhub.net/homepage/hero-manchester-dusk-1400w.webp"><!-- Article-specific --><!-- JSON-LD -->
74<script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"Property Hub","url":"https://propertyhub.net","description":"Where serious investors are built. Education and done-with-you property investment services.","address":{"@type":"PostalAddress","streetAddress":"1st Floor B, 117-119 Portland Street","addressLocality":"Manchester","postalCode":"M1 6ED","addressCountry":"GB"}}</script>
74<!-- Fonts --><meta name="generator" content="Astro v6.3.7"><link rel="preconnect" href="https://assets.propertyhub.net"><link rel="preload" href="/fonts/roboto-latin-variable.woff2" as="font" type="font/woff2" crossorigin><!-- Hero/LCP image preload (per-page, optional) --><!-- Sitewide structured data (Organization + optional Article/BreadcrumbList). 75 Kept at the end of <head>, separate from the per-page JSON-LD above, 76 so multiple ld+json blocks coexist without duplication. --><link rel="stylesheet" href="/_astro/Layout.Cd2MzMcy.css"> 77<style>.logo-stack{display:grid;line-height:0}.logo-stack .logo-face{grid-area:1 / 1;transition:opacity .5s ease}.logo-stack .logo-standard{opacity:0}.logo-stack .logo-invest{opacity:1}.logo-stack.is-armed .logo-standard{opacity:1;transition:none}.logo-stack.is-armed .logo-invest{opacity:0;transition:none}.logo-stack.is-playing .logo-standard{opacity:0}.logo-stack.is-playing .logo-invest{opacity:1}@media(prefers-reduced-motion:reduce){.logo-stack .logo-face{transition:none}}.nav-serif,.nav-serif a,.nav-serif button{font-family:var(--font-sans);font-weight:500;letter-spacing:0}.nav-dropdown a{font-family:var(--font-sans);letter-spacing:0}.nav-dropdown-panel{opacity:0;visibility:hidden}.nav-dropdown-root:hover .nav-dropdown-panel,.nav-dropdown-root:focus-within .nav-dropdown-panel,.nav-dropdown-root.is-open .nav-dropdown-panel{opacity:1;visibility:visible}.nav-dropdown-root.is-closed .nav-dropdown-panel{opacity:0;visibility:hidden}.nav-dropdown-label{font-family:var(--font-sans);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.18em;color:var(--color-text-tertiary, #8A929C)}.nav-cta{font-family:var(--font-sans);font-weight:500;letter-spacing:.01em}.btn.nav-cta{transition-duration:.15s}.menu-icon{position:relative;display:block;width:24px;height:24px}.menu-bar{position:absolute;left:3px;right:3px;height:2px;border-radius:1px;background:currentColor;transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .15s ease}.menu-bar--top{top:6px}.menu-bar--mid{top:11px}.menu-bar--bot{top:16px}#mobile-menu-btn.is-open .menu-bar--top{transform:translateY(5px) rotate(45deg)}#mobile-menu-btn.is-open .menu-bar--mid{opacity:0;transform:scaleX(.4)}#mobile-menu-btn.is-open .menu-bar--bot{transform:translateY(-5px) rotate(-45deg)}@media(prefers-reduced-motion:reduce){.menu-bar{transition:none}} 78.footer-label[data-astro-cid-sz7xmlte]{font-family:var(--font-sans);font-weight:600;text-transform:uppercase;letter-spacing:.18em}.footer-link[data-astro-cid-sz7xmlte]{font-family:var(--font-sans);font-weight:400;letter-spacing:0;touch-action:manipulation}a[data-astro-cid-sz7xmlte].footer-link:active{color:#fff} 79.timeline-pulse[data-astro-cid-kh7btl4r]{animation:timeline-pulse 2.4s cubic-bezier(.16,1,.3,1) infinite}@keyframes timeline-pulse{0%,to{box-shadow:0 0 0 5px #ff51512e}50%{box-shadow:0 0 0 10px #ff51510f}}.timeline-track[data-astro-cid-kh7btl4r]::-webkit-scrollbar{display:none}.timeline-track[data-astro-cid-kh7btl4r]{scrollbar-width:none}.hero-about[data-astro-cid-kh7btl4r]:before{content:"";position:absolute;inset:0;background:radial-gradient(ellipse 80% 70% at 5% 20%,rgba(255,81,81,.04) 0%,transparent 60%),radial-gradient(ellipse 60% 80% at 90% 80%,rgba(201,169,110,.035) 0%,transparent 50%);pointer-events:none}.stat-card[data-astro-cid-kh7btl4r]:after{content:"";position:absolute;bottom:0;left:50%;transform:translate(-50%);width:32px;height:2px;background:var(--color-coral);border-radius:1px;opacity:0;transition:opacity .25s cubic-bezier(.16,1,.3,1)}.stat-card[data-astro-cid-kh7btl4r]:hover{border-color:var(--color-border);box-shadow:var(--shadow-card)}.stat-card[data-astro-cid-kh7btl4r]:hover:after{opacity:1}.reveal[data-astro-cid-kh7btl4r]{opacity:0;transform:translateY(20px);transition:opacity .4s cubic-bezier(.16,1,.3,1),transform .4s cubic-bezier(.16,1,.3,1)}.reveal[data-astro-cid-kh7btl4r].visible{opacity:1;transform:translateY(0)}.reveal-stagger[data-astro-cid-kh7btl4r]>[data-astro-cid-kh7btl4r]{opacity:0;transform:translateY(20px);transition:opacity .4s cubic-bezier(.16,1,.3,1),transform .4s cubic-bezier(.16,1,.3,1)}.reveal-stagger[data-astro-cid-kh7btl4r].visible>[data-astro-cid-kh7btl4r]:nth-child(1){transition-delay:0ms;opacity:1;transform:translateY(0)}.reveal-stagger[data-astro-cid-kh7btl4r].visible>[data-astro-cid-kh7btl4r]:nth-child(2){transition-delay:80ms;opacity:1;transform:translateY(0)}.reveal-stagger[data-astro-cid-kh7btl4r].visible>[data-astro-cid-kh7btl4r]:nth-child(3){transition-delay:.16s;opacity:1;transform:translateY(0)}.reveal-stagger[data-astro-cid-kh7btl4r].visible>[data-astro-cid-kh7btl4r]:nth-child(4){transition-delay:.24s;opacity:1;transform:translateY(0)} 80</style></head> <body class="font-sans text-text-primary antialiased bg-background " data-astro-cid-sckkx6r4> <!-- Keyboard users can bypass the header nav; visually hidden until focused. 81 Target: the page's main landmark (id "main-content" â pages and 82 sub-layouts all carry it). Styles in global.css (.skip-link). -->
82 <a href="#main-content" class="skip-link" data-astro-cid-sckkx6r4>Skip to content</a> <!-- Google Tag Manager (noscript) --> <noscript> <iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PQS7STW" height="0" width="0" style="display:none;visibility:hidden" data-astro-cid-sckkx6r4></iframe> </noscript> <!-- End Google Tag Manager (noscript) --> <!-- HEADER --><header class="fixed top-0 left-0 right-0 z-[99] bg-white/95 backdrop-blur-[10px] transition-shadow duration-250" id="site-header" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);"> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between h-[88px]"> <a href="/" class="flex-shrink-0"> <img src="/property-hub-logo.svg" alt="Property Hub" class="h-[68px] w-auto"> </a> <!-- Desktop nav --> <nav class="hidden xl:flex items-center gap-6 nav-serif whitespace-nowrap"> <!-- Start Here (standalone) --> <a href="/learn/" class="text-[16px] hover:text-text-primary transition-colors duration-150 text-charcoal-soft font-normal">Start Here</a> <!-- Investing Guides (dropdown) --> <div class="relative group nav-dropdown-root"> <button id="nav-dd-guides-btn" aria-controls="nav-dd-guides" class="nav-dropdown-btn flex items-center gap-1.5 text-[16px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral transition-colors duration-150 hover:text-text-primary text-charcoal-soft font-normal" aria-expanded="false" aria-haspopup="true"> 83Investing Guides 84<svg class="w-3 h-3 opacity-50 group-hover:opacity-100 transition-opacity duration-150" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg> </button> <div id="nav-dd-guides" class="nav-dropdown-panel absolute top-full left-0 pt-3 transition-[opacity,visibility] duration-250" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);"> <div class="nav-dropdown bg-white rounded-lg shadow-card border border-border-light py-2 min-w-[230px]"> <a href="/guides/getting-started/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Getting Started</a> <a href="/guides/buying-sourcing/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Buying & Sourcing</a> <a href="/guides/financing-tax/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Financing & Tax</a> <a href="/guides/strategy-portfolio/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Strategy & Portfolio</a> <a href="/guides/lettings-landlord/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Lettings & Being a Landlord</a> <a href="/guides/market-view/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Market View & Timing</a> </div> </div> </div> <!-- Build Your Portfolio (dropdown) --> <div class="relative group nav-dropdown-root"> <button id="nav-dd-portfolio-btn" aria-controls="nav-dd-portfolio" class="nav-dropdown-btn flex items-center gap-1.5 text-[16px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral transition-colors duration-150 hover:text-text-primary text-charcoal-soft font-normal" aria-expanded="false" aria-haspopup="true"> 85Build Your Portfolio 86<svg class="w-3 h-3 opacity-50 group-hover:opacity-100 transition-opacity duration-150" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg> </button> <div id="nav-dd-portfolio" class="nav-dropdown-panel absolute top-full left-0 pt-3 transition-[opacity,visibility] duration-250" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);"> <div class="nav-dropdown bg-white rounded-lg shadow-card border border-border-light py-2 min-w-[230px]"> <a href="/invest/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Invest With Us</a> <a href="/invest/book-a-consultation/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Book a Consultation</a> <a href="/invest/case-studies/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Past Deals & Case Studies</a> <a href="/investor-stories/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Investor Stories</a> <a href="/invest/expats/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">For Expats</a> </div> </div> </div> <!-- Resources (dropdown) --> <div class="relative group nav-dropdown-root"> <button id="nav-dd-resources-btn" aria-controls="nav-dd-resources" class="nav-dropdown-btn flex items-center gap-1.5 text-[16px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral transition-colors duration-150 hover:text-text-primary text-charcoal-soft font-normal" aria-expanded="false" aria-haspopup="true"> 87Resources 88<svg class="w-3 h-3 opacity-50 group-hover:opacity-100 transition-opacity duration-150" fill="none" stroke="currentColor" viewBox="0 0 24 24">
88<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg> </button> <div id="nav-dd-resources" class="nav-dropdown-panel absolute top-full left-0 pt-3 transition-[opacity,visibility] duration-250" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);"> <div class="nav-dropdown bg-white rounded-lg shadow-card border border-border-light py-2 min-w-[210px]"> <a href="/news/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Latest News</a> <a href="/pulse/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Newsletter</a> <a href="/free-tools/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Investor Toolkit</a> <a href="https://quiz.propertyhub.net/" target="_blank" rel="noopener noreferrer" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Investment Readiness Quiz</a> <a href="https://podcast.propertyhub.net/" target="_blank" rel="noopener noreferrer" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Podcast</a> <a href="https://www.youtube.com/propertyhubuk" target="_blank" rel="noopener noreferrer" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">YouTube</a> <a href="/books/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Books</a> <a href="/university/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Free Courses</a> <a href="/webinar/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Webinar</a> <a href="/ai/" class="block px-4 py-2 text-[13px] text-text-secondary hover:text-text-primary hover:bg-background transition-colors duration-150">Rob AI</a> </div> </div> </div> <!-- CTA --> <a href="/free-tools/" class="btn btn--primary btn--xs nav-cta" data-analytics-cta="true" data-cta-name="get_free_toolkit" data-cta-location="header" data-business-area="tools">Get the free toolkit</a> </nav> <div class="flex items-center gap-3"> <!-- Search button --> <button id="search-toggle" class="p-2 text-text-tertiary hover:text-text-primary transition-colors duration-150" aria-label="Search"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg> </button> <!-- Mobile menu button (hamburger morphs to cross when open) --> <button id="mobile-menu-btn" class="xl:hidden p-2 text-text-secondary" aria-label="Open menu" aria-expanded="false" aria-controls="mobile-menu"> <span class="menu-icon" aria-hidden="true"> <span class="menu-bar menu-bar--top"></span> <span class="menu-bar menu-bar--mid"></span> <span class="menu-bar menu-bar--bot"></span> </span> </button> </div> </div> <!-- Mobile menu --> <div id="mobile-menu" class="hidden xl:hidden bg-white border-t border-border-light px-4 pb-4 max-h-[80vh] overflow-y-auto"> <!-- Start Here (standalone) --> <a href="/learn/" class="nav-serif block py-3 text-[18px] font-normal text-text-primary border-b border-border-light">Start Here</a> <!-- Investing Guides --> <div class="border-b border-border-light"> <button class="mobile-dropdown-trigger nav-serif w-full flex items-center justify-between py-3 text-[18px] font-normal text-text-primary" aria-expanded="false"> 89Investing Guides 90<svg class="mobile-chevron w-4 h-4 text-text-tertiary transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg> </button> <div class="mobile-dropdown hidden pl-4 pb-3 space-y-1"> <a href="/guides/getting-started/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Getting Started</a> <a href="/guides/buying-sourcing/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Buying & Sourcing</a> <a href="/guides/financing-tax/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Financing & Tax</a>
90 <a href="/guides/strategy-portfolio/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Strategy & Portfolio</a> <a href="/guides/lettings-landlord/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Lettings & Being a Landlord</a> <a href="/guides/market-view/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Market View & Timing</a> </div> </div> <!-- Build Your Portfolio --> <div class="border-b border-border-light"> <button class="mobile-dropdown-trigger nav-serif w-full flex items-center justify-between py-3 text-[18px] font-normal text-text-primary" aria-expanded="false"> 91Build Your Portfolio 92<svg class="mobile-chevron w-4 h-4 text-text-tertiary transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg> </button> <div class="mobile-dropdown hidden pl-4 pb-3 space-y-1"> <a href="/invest/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Invest With Us</a> <a href="/invest/book-a-consultation/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Book a Consultation</a> <a href="/invest/case-studies/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Past Deals & Case Studies</a> <a href="/investor-stories/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Investor Stories</a> <a href="/invest/expats/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">For Expats</a> </div> </div> <!-- Resources --> <div class="border-b border-border-light"> <button class="mobile-dropdown-trigger nav-serif w-full flex items-center justify-between py-3 text-[18px] font-normal text-text-primary" aria-expanded="false"> 93Resources 94<svg class="mobile-chevron w-4 h-4 text-text-tertiary transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg> </button> <div class="mobile-dropdown hidden pl-4 pb-3 space-y-1"> <a href="/news/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Latest News</a> <a href="/pulse/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Newsletter</a> <a href="/free-tools/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Investor Toolkit</a> <a href="https://quiz.propertyhub.net/" target="_blank" rel="noopener noreferrer" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Investment Readiness Quiz</a> <a href="https://podcast.propertyhub.net/" target="_blank" rel="noopener noreferrer" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Podcast</a> <a href="https://www.youtube.com/propertyhubuk" target="_blank" rel="noopener noreferrer" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">YouTube</a> <a href="/books/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Books</a> <a href="/university/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Free Courses</a> <a href="/webinar/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Webinar</a> <a href="/ai/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Rob AI</a> </div> </div> <div class="pt-3"> <a href="/free-tools/" class="btn btn--primary btn--xs nav-cta w-full justify-center py-3" data-analytics-cta="true" data-cta-name="get_free_toolkit" data-cta-location="mobile_menu" data-business-area="tools">Get the free toolkit</a> </div> <div class="mt-5 pt-5 border-t border-border-light"> <div class="grid grid-cols-3 gap-2 mb-5"> <a href="/about/" class="text-center py-2 text-[13px] font-medium text-text-secondary hover:text-coral transition-colors duration-150">About</a> <a href="/contact/" class="text-center py-2 text-[13px] font-medium text-text-secondary hover:text-coral transition-colors duration-150">Contact</a> <a href="/jobs/" class="text-center py-2 text-[13px] font-medium text-text-secondary hover:text-coral transition-colors duration-150">Jobs</a> </div> <div class="flex items-center justify-center gap-5"> <a href="https://www.facebook.com/propertyhubuk/" target="_blank" rel="noopener noreferrer" class="text-text-tertiary hover:text-text-primary transition-colors duration-150" aria-label="Facebook"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
94<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"></path></svg> </a> <a href="https://www.instagram.com/propertyhubuk/" target="_blank" rel="noopener noreferrer" class="text-text-tertiary hover:text-text-primary transition-colors duration-150" aria-label="Instagram"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"></path></svg> </a> <a href="https://www.linkedin.com/company/thepropertyhub" target="_blank" rel="noopener noreferrer" class="text-text-tertiary hover:text-text-primary transition-colors duration-150" aria-label="LinkedIn"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"></path></svg> </a> <a href="https://www.youtube.com/propertyhubuk" target="_blank" rel="noopener noreferrer" class="text-text-tertiary hover:text-text-primary transition-colors duration-150" aria-label="YouTube"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path></svg> </a> </div> </div> </div> </header> <!-- Search overlay (outside header â header's backdrop-filter creates a containing block that traps fixed children) --> <div id="search-overlay" role="dialog" aria-modal="true" aria-label="Search Property Hub" class="hidden fixed inset-0 z-[100] bg-charcoal/50 backdrop-blur-sm overflow-y-auto"> <div class="max-w-2xl mx-auto mt-24 mb-12 px-4"> <div class="bg-white rounded-[12px] shadow-elevated overflow-hidden"> <div class="flex items-center gap-3 p-6 border-b border-border-light focus-within:border-coral transition-colors duration-150"> <svg class="w-5 h-5 text-text-tertiary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
94<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg> <input id="search-input" type="text" aria-label="Search Property Hub" placeholder="Search Property Hub..." autocomplete="off" class="w-full text-[18px] font-light text-text-primary placeholder:text-text-tertiary outline-none"> <button id="search-close" class="p-1 text-text-tertiary hover:text-text-primary transition-colors duration-150" aria-label="Close search"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg> </button> </div> <div id="search-results" aria-live="polite" class="max-h-[60vh] overflow-y-auto"></div> </div> </div> </div>
94<script type="module" src="/_astro/Header.astro_astro_type_script_index_0_lang.PZKu0m98.js"></script>
94 <main id="main-content" class="pt-16 sm:pt-20" data-astro-cid-kh7btl4r> <!-- HERO SECTION --> <section class="pt-28 lg:pt-36 pb-16 lg:pb-24 bg-background-warm relative overflow-hidden surface-editorial" data-astro-cid-kh7btl4r> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-kh7btl4r> <div class="lg:grid lg:grid-cols-2 lg:gap-16 items-center" data-astro-cid-kh7btl4r> <!-- Left: hero video --> <div class="mb-10 lg:mb-0 reveal" data-reveal data-astro-cid-kh7btl4r> <div class="rounded-[16px] overflow-hidden shadow-card" data-astro-cid-kh7btl4r> <div style="position:relative; width:1px; min-width:100%; padding-bottom:56.25%;" data-astro-cid-kh7btl4r> <iframe allow="autoplay" class="spotlightr" allowtransparency="true" style="width:1px; min-width:100%; height:100%; position:absolute;" allowfullscreen="true" src="https://videos.cdn.spotlightr.com/watch/MjAwMTk4Mw==?fallback=true" frameborder="0" scrolling="no" name="videoPlayer" title="About Property Hub" data-astro-cid-kh7btl4r></iframe> </div> </div> </div> <!-- Right: headline + intro text --> <div class="reveal" data-reveal style="transition-delay: 0.15s;" data-astro-cid-kh7btl4r> <div class="w-16 h-px bg-coral mb-8" data-astro-cid-kh7btl4r></div> <h1 class="font-serif text-[36px] sm:text-[44px] lg:text-[52px] text-text-primary leading-[1.05] tracking-[-0.02em]" data-astro-cid-kh7btl4r> 95Building a property portfolio takes two things most people don't have: <span class="text-coral italic" data-astro-cid-kh7btl4r>knowledge, and time.</span> </h1> <p class="mt-6 sm:mt-8 text-[18px] text-text-secondary font-light leading-[1.7]" data-astro-cid-kh7btl4r> 96That's why we built two sides to Property Hub: education for people who want to do it themselves, and a done-with-you service for people who'd rather not. 97</p> </div> </div> </div> </section> <!-- WHAT WE BELIEVE --> <section class="py-24 lg:py-32 bg-surface relative overflow-hidden surface-editorial" data-astro-cid-kh7btl4r> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-kh7btl4r> <div class="lg:grid lg:grid-cols-[390px_1fr] lg:gap-8" data-astro-cid-kh7btl4r> <div class="reveal" data-reveal data-astro-cid-kh7btl4r> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mb-3" data-astro-cid-kh7btl4r>Our philosophy</p> <h2 class="font-serif text-[32px] sm:text-[40px] lg:text-[52px] text-text-primary leading-[1.1] tracking-[-0.02em]" data-astro-cid-kh7btl4r> 98What we believe 99</h2> </div> <div class="mt-6 lg:mt-0 reveal" data-reveal style="transition-delay: 0.1s;" data-astro-cid-kh7btl4r> <div class="space-y-5" data-astro-cid-kh7btl4r> <p class="text-[18px] text-text-primary font-medium leading-[1.7]" data-astro-cid-kh7btl4r> 100True property wealth isn't built from rental income – it's built from owning quality assets long enough for compounding to do the work. 101</p> <p class="text-[18px] text-text-secondary font-light leading-[1.7]" data-astro-cid-kh7btl4r> 102This belief shapes everything we do. Our <a href="/books/" class="text-coral hover:underline transition-colors duration-150" data-astro-cid-kh7btl4r>books</a>, <a href="https://podcast.propertyhub.net/" target="_blank" rel="noopener noreferrer" class="text-coral hover:underline transition-colors duration-150" data-astro-cid-kh7btl4r>podcast</a> and <a href="/youtube/" class="text-coral hover:underline transition-colors duration-150" data-astro-cid-kh7btl4r>YouTube</a> channel exist to help people set clearer goals, choose the right strategy, and build a portfolio that performs over time. <a href="/invest/" class="text-coral hover:underline transition-colors duration-150" data-astro-cid-kh7btl4r>Our investment service</a> is designed for people who understand this but don't have time to execute it themselves. 103</p> <p class="text-[18px] text-text-secondary font-light leading-[1.7]" data-astro-cid-kh7btl4r> 104We're not for everyone. If you enjoy the hands-on side of property, you'll find our approach frustrating. If you're looking for a quick flip, we're the wrong partner. But if you want to build long-term financial security, independence and a legacy, you're in the right place. 105</p> </div> </div> </div> </div> </section> <!-- HOW WE WORK â dark section --> <section class="py-24 lg:py-32 bg-charcoal relative overflow-hidden" data-astro-cid-kh7btl4r> <!-- Radial glows --> <div class="absolute inset-0 pointer-events-none" style="background: radial-gradient(ellipse at 15% 80%, rgba(255, 81, 81, 0.06), transparent 50%), radial-gradient(ellipse at 85% 20%, rgba(201, 169, 110, 0.04), transparent 50%);" data-astro-cid-kh7btl4r></div> <!-- Grain --> <div class="absolute inset-0 pointer-events-none" style="background-image: var(--grain); background-repeat: repeat; background-size: 256px 256px; opacity: 0.04; mix-blend-mode: soft-light;" data-astro-cid-kh7btl4r></div> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-kh7btl4r> <div class="text-center mb-14 reveal" data-reveal data-astro-cid-kh7btl4r> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mb-3" data-astro-cid-kh7btl4r>Our process</p> <h2 class="font-serif text-[32px] sm:text-[40px] lg:text-[52px] text-white leading-[1.1] tracking-[-0.02em]" data-astro-cid-kh7btl4r>
106How we work 107</h2> </div> <div class="grid grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8 reveal-stagger" data-reveal-stagger data-astro-cid-kh7btl4r> <div class="text-center" data-astro-cid-kh7btl4r> <h3 class="text-[20px] sm:text-[24px] font-medium text-white mb-2" data-astro-cid-kh7btl4r>Search the market</h3> <div class="w-8 h-[2px] bg-coral mx-auto mb-5" data-astro-cid-kh7btl4r></div> <p class="text-[16px] text-white/70 font-light leading-[1.7]" data-astro-cid-kh7btl4r>We use our exclusive relationships with developers – FTSE-listed housebuilders and major regional players – to secure properties before they reach the open market.</p> </div> <div class="text-center" data-astro-cid-kh7btl4r> <h3 class="text-[20px] sm:text-[24px] font-medium text-white mb-2" data-astro-cid-kh7btl4r>Negotiate the deal</h3> <div class="w-8 h-[2px] bg-coral mx-auto mb-5" data-astro-cid-kh7btl4r></div> <p class="text-[16px] text-white/70 font-light leading-[1.7]" data-astro-cid-kh7btl4r>Because we're buying at volume and can move quickly, we negotiate discounts that aren't available to individual buyers – securing an average 11% off the market value.</p> </div> <div class="text-center" data-astro-cid-kh7btl4r> <h3 class="text-[20px] sm:text-[24px] font-medium text-white mb-2" data-astro-cid-kh7btl4r>Present our research</h3> <div class="w-8 h-[2px] bg-coral mx-auto mb-5" data-astro-cid-kh7btl4r></div> <p class="text-[16px] text-white/70 font-light leading-[1.7]" data-astro-cid-kh7btl4r>When a development matches our criteria, we share it with clients: location, pricing, rental projections, the full picture. You do your own research and due diligence.</p> </div> <div class="text-center" data-astro-cid-kh7btl4r> <h3 class="text-[20px] sm:text-[24px] font-medium text-white mb-2" data-astro-cid-kh7btl4r>Own the process</h3> <div class="w-8 h-[2px] bg-coral mx-auto mb-5" data-astro-cid-kh7btl4r></div> <p class="text-[16px] text-white/70 font-light leading-[1.7]" data-astro-cid-kh7btl4r>If you decide to proceed, we coordinate the financing and legals and get it ready for tenants to move in.</p> </div> </div> <!-- Quote --> <div class="mt-16 pt-14 text-center border-t border-white/10 reveal" data-reveal data-astro-cid-kh7btl4r> <p class="max-w-4xl mx-auto text-[20px] sm:text-[28px] font-medium text-white leading-[1.2]" data-astro-cid-kh7btl4r> 108We do the work, but you're in control: you pay us nothing until we've found you something you love. 109</p> <div class="mt-8 flex justify-center" data-astro-cid-kh7btl4r> <div class="w-12 h-[2px] bg-coral" data-astro-cid-kh7btl4r></div> </div> </div> </div> </section> <!-- WHAT WE'VE BUILT â timeline --> <section class="py-24 lg:py-32 bg-background-warm relative overflow-hidden surface-editorial" data-astro-cid-kh7btl4r> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-kh7btl4r> <div class="max-w-[720px] mb-16 lg:mb-20 reveal" data-reveal data-astro-cid-kh7btl4r> <div class="w-16 h-px bg-coral mb-8" data-astro-cid-kh7btl4r></div> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mb-3" data-astro-cid-kh7btl4r>Track record</p> <h2 class="font-serif text-[32px] sm:text-[44px] lg:text-[52px] text-text-primary leading-[1.05] tracking-[-0.02em] mb-6" data-astro-cid-kh7btl4r> 110What we've built 111</h2> <p class="text-[18px] text-text-primary font-medium leading-[1.7] mb-4" data-astro-cid-kh7btl4r> 112We've been teaching property investment for as long as we've been doing it. Most people who benefit from our expertise will never pay us anything – and that's fine. 113</p> <p class="text-[18px] text-text-secondary font-light leading-[1.7]" data-astro-cid-kh7btl4r> 114But it does mean that when someone works with us, they've usually spent months or years understanding our approach. They know whether we're right for them before the first conversation. 115</p> </div> <!-- Horizontal timeline --> <div class="relative reveal" data-reveal style="transition-delay: 0.15s;" data-astro-cid-kh7btl4r> <!-- Scroll container for mobile, static grid on desktop --> <div class="timeline-track overflow-x-auto lg:overflow-visible pb-6 lg:pb-0" data-astro-cid-kh7btl4r> <div class="timeline-inner relative min-w-[900px] lg:min-w-0 lg:grid lg:grid-cols-7 flex" data-astro-cid-kh7btl4r> <!-- Timeline axis --> <div class="absolute left-0 right-0 top-[62px] h-px bg-border-light" aria-hidden="true" data-astro-cid-kh7btl4r></div> <div class="absolute left-0 top-[62px] h-px bg-coral" aria-hidden="true" style="width: 100%; opacity: 0.25;" data-astro-cid-kh7btl4r></div> <!-- Milestone: 2013 â PH founded --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>2013</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3 h-3 rounded-full bg-coral shadow-[0_0_0_4px_rgba(255,81,81,0.15)]" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>Property Hub founded</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>Rob Bence & Rob Dix start teaching what they've learned investing their own money.</p> </div> <!-- Milestone: 2013 â podcast --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>2013</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3 h-3 rounded-full bg-coral shadow-[0_0_0_4px_rgba(255,81,81,0.15)]" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>The Property Podcast launches</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>Now the UK's #1 property podcast, 100k+ downloads a month.</p> </div> <!-- Milestone: 2014 â Invest --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>2014</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3 h-3 rounded-full bg-coral shadow-[0_0_0_4px_rgba(255,81,81,0.15)]" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>Property Hub Invest launches</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>The done-with-you service begins sourcing for the first cohort of clients.</p> </div> <!-- Milestone: 2014 â first book --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>2014</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3 h-3 rounded-full bg-coral shadow-[0_0_0_4px_rgba(255,81,81,0.15)]" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>First book published</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>Now 140,000+ copies of Rob's books sold worldwide.</p> </div> <!-- Milestone: 2018 --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>2018</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3 h-3 rounded-full bg-coral shadow-[0_0_0_4px_rgba(255,81,81,0.15)]" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>Property Pulse newsletter</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>Now read weekly by 70,000+ UK property investors.</p> </div> <!-- Milestone: 2024 â Sunday Times --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>2024</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3 h-3 rounded-full bg-coral shadow-[0_0_0_4px_rgba(255,81,81,0.15)]" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>Sunday Times column</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>Rob Dix becomes a weekly columnist for The Sunday Times.</p> </div> <!-- Milestone: today --> <div class="timeline-node relative flex-1 lg:col-span-1 px-4 lg:px-2 pt-0" data-astro-cid-kh7btl4r> <div class="text-[14px] font-serif text-coral mb-2" data-astro-cid-kh7btl4r>Today</div> <div class="h-[28px] flex items-center justify-start" data-astro-cid-kh7btl4r> <div class="w-3.5 h-3.5 rounded-full bg-coral shadow-[0_0_0_5px_rgba(255,81,81,0.18)] timeline-pulse" data-astro-cid-kh7btl4r></div> </div> <p class="mt-4 text-[14px] font-medium text-text-primary leading-snug" data-astro-cid-kh7btl4r>35+ team in Manchester</p> <p class="mt-1 text-[12px] text-text-tertiary font-light leading-[1.5]" data-astro-cid-kh7btl4r>Acquisitions, legal, research, client support – all under one roof.</p> </div> </div> </div> <!-- Mobile hint --> <p class="text-[12px] text-text-tertiary font-light text-center mt-4 lg:hidden" data-astro-cid-kh7btl4r>← Scroll to explore →</p> </div> </div> </section> <!-- TYPOGRAPHIC MOMENT --> <section class="bg-charcoal relative overflow-hidden" data-astro-cid-kh7btl4r> <div class="absolute inset-0 pointer-events-none" style="background: radial-gradient(ellipse 60% 80% at 20% 50%, rgba(255, 81, 81, 0.09) 0%, transparent 55%), radial-gradient(ellipse 50% 60% at 85% 50%, rgba(201, 169, 110, 0.05) 0%, transparent 50%);" data-astro-cid-kh7btl4r></div> <div class="absolute inset-0 pointer-events-none" style="background-image: var(--grain); background-repeat: repeat; background-size: 256px 256px; opacity: 0.04; mix-blend-mode: soft-light;" data-astro-cid-kh7btl4r></div> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 py-24 lg:py-36 relative z-[1]" data-astro-cid-kh7btl4r> <div class="reveal" data-reveal data-astro-cid-kh7btl4r> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mb-8" data-astro-cid-kh7btl4r>Last year alone</p> <p class="font-serif text-white leading-[0.9] tracking-[-0.04em]" style="font-size: clamp(80px, 14vw, 200px);" data-astro-cid-kh7btl4r> 116£100m+ 117</p> <p class="text-[18px] sm:text-[22px] text-white/70 font-light mt-8 max-w-xl leading-[1.5]" data-astro-cid-kh7btl4r> 118of UK property acquired on behalf of our investors. 119</p> </div> </div> </section> <!-- WHO WE ARE --> <section class="py-24 lg:py-32 bg-surface relative overflow-hidden surface-editorial" data-astro-cid-kh7btl4r> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-kh7btl4r> <div class="max-w-3xl reveal" data-reveal data-astro-cid-kh7btl4r> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mb-3" data-astro-cid-kh7btl4r>The team</p> <h2 class="font-serif text-[32px] sm:text-[40px] lg:text-[52px] text-text-primary leading-[1.1] tracking-[-0.02em]" data-astro-cid-kh7btl4r> 120Who we are 121</h2> <p class="mt-5 text-[18px] text-text-secondary font-light leading-[1.7]" data-astro-cid-kh7btl4r> 122We're a Manchester-based team of 35+ across acquisitions, legal coordination, research and client support. When you work with us, you're working with people who spend their days evaluating developments, negotiating with developers, and handling the complexity so you don't have to. 123</p> </div> <!-- Team carousel --> <div class="mt-14 relative" id="team-carousel" data-astro-cid-kh7btl4r> <div class="overflow-hidden" id="team-track-wrapper" data-astro-cid-kh7btl4r> <div class="flex gap-8 transition-transform duration-500 ease-out" id="team-track" style="will-change: transform;" data-astro-cid-kh7btl4r> <div class="flex-shrink-0 w-[300px] sm:w-[380px]" data-astro-cid-kh7btl4r> <div class="bg-surface rounded-[12px] overflow-hidden border border-border-light transition-[transform,box-shadow,border-color] duration-250 hover:shadow-card hover:-translate-y-1 hover:border-coral/20" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <div class="aspect-[4/3] overflow-hidden bg-background" data-astro-cid-kh7btl4r> <img src="/images/team/rob-bence-headshot.png" alt="Rob Bence" class="w-full h-full object-cover object-[center_20%]" loading="lazy" data-astro-cid-kh7btl4r> </div> <div class="p-5 sm:p-6" data-astro-cid-kh7btl4r> <h3 class="text-lg font-medium text-text-primary" data-astro-cid-kh7btl4r>Rob Bence</h3>
123 <p class="text-sm text-coral font-medium mt-1" data-astro-cid-kh7btl4r>Co-founder</p> <p class="text-sm text-text-secondary font-light leading-relaxed mt-3" data-astro-cid-kh7btl4r>Built Property Hub into a business facilitating over £100m in annual property transactions.</p> </div> </div> </div><div class="flex-shrink-0 w-[300px] sm:w-[380px]" data-astro-cid-kh7btl4r> <div class="bg-surface rounded-[12px] overflow-hidden border border-border-light transition-[transform,box-shadow,border-color] duration-250 hover:shadow-card hover:-translate-y-1 hover:border-coral/20" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <div class="aspect-[4/3] overflow-hidden bg-background" data-astro-cid-kh7btl4r> <img src="/images/team/rob-dix.jpg" alt="Rob Dix" class="w-full h-full object-cover object-[center_20%]" loading="lazy" data-astro-cid-kh7btl4r> </div> <div class="p-5 sm:p-6" data-astro-cid-kh7btl4r> <h3 class="text-lg font-medium text-text-primary" data-astro-cid-kh7btl4r>Rob Dix</h3> <p class="text-sm text-coral font-medium mt-1" data-astro-cid-kh7btl4r>Co-founder & Author</p> <p class="text-sm text-text-secondary font-light leading-relaxed mt-3" data-astro-cid-kh7btl4r>Author of seven best-selling books, co-founder, and an unashamed property geek.</p> </div> </div> </div><div class="flex-shrink-0 w-[300px] sm:w-[380px]" data-astro-cid-kh7btl4r> <div class="bg-surface rounded-[12px] overflow-hidden border border-border-light transition-[transform,box-shadow,border-color] duration-250 hover:shadow-card hover:-translate-y-1 hover:border-coral/20" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <div class="aspect-[4/3] overflow-hidden bg-background" data-astro-cid-kh7btl4r> <img src="/images/team/ben-speaker.jpg" alt="Tracey Jones" class="w-full h-full object-cover object-[center_20%]" loading="lazy" data-astro-cid-kh7btl4r> </div> <div class="p-5 sm:p-6" data-astro-cid-kh7btl4r> <h3 class="text-lg font-medium text-text-primary" data-astro-cid-kh7btl4r>Tracey Jones</h3> <p class="text-sm text-coral font-medium mt-1" data-astro-cid-kh7btl4r>Managing Director</p> <p class="text-sm text-text-secondary font-light leading-relaxed mt-3" data-astro-cid-kh7btl4r>Leads our strategy, performance and delivery - balancing business growth with high-quality execution.</p> </div> </div> </div><div class="flex-shrink-0 w-[300px] sm:w-[380px]" data-astro-cid-kh7btl4r> <div class="bg-surface rounded-[12px] overflow-hidden border border-border-light transition-[transform,box-shadow,border-color] duration-250 hover:shadow-card hover:-translate-y-1 hover:border-coral/20" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <div class="aspect-[4/3] overflow-hidden bg-background" data-astro-cid-kh7btl4r> <img src="/images/team/jenny-portrait.jpg" alt="Sam Smith" class="w-full h-full object-cover object-[center_20%]" loading="lazy" data-astro-cid-kh7btl4r> </div> <div class="p-5 sm:p-6" data-astro-cid-kh7btl4r> <h3 class="text-lg font-medium text-text-primary" data-astro-cid-kh7btl4r>Sam Smith</h3> <p class="text-sm text-coral font-medium mt-1" data-astro-cid-kh7btl4r>Director of Investments & Partnerships</p> <p class="text-sm text-text-secondary font-light leading-relaxed mt-3" data-astro-cid-kh7btl4r>Leads the sourcing and evaluation of new developments, shaping the investment opportunities we bring to market.</p> </div> </div> </div><div class="flex-shrink-0 w-[300px] sm:w-[380px]" data-astro-cid-kh7btl4r> <div class="bg-surface rounded-[12px] overflow-hidden border border-border-light transition-[transform,box-shadow,border-color] duration-250 hover:shadow-card hover:-translate-y-1 hover:border-coral/20" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <div class="aspect-[4/3] overflow-hidden bg-background" data-astro-cid-kh7btl4r> <img src="/images/team/sophie-portrait.jpg" alt="Zoe Cairney" class="w-full h-full object-cover object-[center_20%]" loading="lazy" data-astro-cid-kh7btl4r> </div> <div class="p-5 sm:p-6" data-astro-cid-kh7btl4r> <h3 class="text-lg font-medium text-text-primary" data-astro-cid-kh7btl4r>
123Zoe Cairney</h3> <p class="text-sm text-coral font-medium mt-1" data-astro-cid-kh7btl4r>Sales Progression Manager</p> <p class="text-sm text-text-secondary font-light leading-relaxed mt-3" data-astro-cid-kh7btl4r>Responsible for our client experience: from reservation to completion, keeping purchases moving and on track.</p> </div> </div> </div> </div> </div> <!-- Carousel nav buttons --> <button id="team-prev" class="absolute left-0 top-[140px] sm:top-[180px] -translate-x-3 sm:-translate-x-6 w-12 h-12 sm:w-14 sm:h-14 rounded-full bg-coral flex items-center justify-center text-white hover:bg-coral-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral active:scale-95 transition-[background-color,transform] duration-200 z-10" aria-label="Previous team member" data-astro-cid-kh7btl4r> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" data-astro-cid-kh7btl4r> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" data-astro-cid-kh7btl4r></path> </svg> </button> <button id="team-next" class="absolute right-0 top-[140px] sm:top-[180px] translate-x-3 sm:translate-x-6 w-12 h-12 sm:w-14 sm:h-14 rounded-full bg-coral flex items-center justify-center text-white hover:bg-coral-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral active:scale-95 transition-[background-color,transform] duration-200 z-10" aria-label="Next team member" data-astro-cid-kh7btl4r> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" data-astro-cid-kh7btl4r> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" data-astro-cid-kh7btl4r></path> </svg> </button> </div> </div> </section> <!-- WE'RE GROWING â dark section --> <section class="py-24 lg:py-32 bg-charcoal relative overflow-hidden" data-astro-cid-kh7btl4r> <!-- Radial glows --> <div class="absolute inset-0 pointer-events-none" style="background: radial-gradient(ellipse at 20% 50%, rgba(255, 81, 81, 0.06), transparent 50%), radial-gradient(ellipse at 80% 50%, rgba(201, 169, 110, 0.04), transparent 50%);" data-astro-cid-kh7btl4r></div> <!-- Grain --> <div class="absolute inset-0 pointer-events-none" style="background-image: var(--grain); background-repeat: repeat; background-size: 256px 256px; opacity: 0.04; mix-blend-mode: soft-light;" data-astro-cid-kh7btl4r></div> <div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center relative z-[1] reveal" data-reveal data-astro-cid-kh7btl4r> <h2 class="font-serif text-[32px] sm:text-[40px] text-white leading-[1.1] tracking-[-0.02em]" data-astro-cid-kh7btl4r> 124We're growing. 125</h2> <p class="mt-4 text-[18px] text-white/70 font-light leading-[1.7]" data-astro-cid-kh7btl4r> 126If you're interested in joining a winning team, check out our current vacancies below. 127</p> <a href="/jobs/" class="inline-block mt-8 px-8 py-[14px] bg-coral text-white font-medium text-[16px] rounded-[8px] shadow-coral-cta hover:bg-coral-hover hover:shadow-coral-hover hover:-translate-y-[1px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral transition-[transform,background-color,box-shadow] duration-250" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> 128View our current vacancies 129</a> </div> </section> <!-- WHAT'S NEXT --> <section class="py-24 lg:py-32 bg-background-warm relative overflow-hidden surface-editorial" data-astro-cid-kh7btl4r> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-kh7btl4r> <div class="text-center mb-12 reveal" data-reveal data-astro-cid-kh7btl4r> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mb-3" data-astro-cid-kh7btl4r>Get started</p> <h2 class="font-serif text-[32px] sm:text-[40px] text-text-primary leading-[1.15] tracking-[-0.015em]" data-astro-cid-kh7btl4r> 130What's next? 131</h2> <p class="mt-4 text-[18px] text-text-secondary font-light leading-[1.7] max-w-2xl mx-auto" data-astro-cid-kh7btl4r> 132If you've read this far, you're probably in one of two camps: 133</p> </div> <div class="grid md:grid-cols-2 gap-8 reveal-stagger" data-reveal-stagger data-astro-cid-kh7btl4r> <!-- Learn CTA â light card --> <div class="bg-surface rounded-[12px] border border-border-light p-8 transition-[transform,box-shadow,border-color] duration-250 hover:shadow-card hover:-translate-y-1 hover:border-coral/20" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <div class="text-center" data-astro-cid-kh7btl4r> <img src="/images/pages/property-pulse-laptop.png" alt="Property Pulse newsletter on laptop" class="w-full h-auto mb-4" loading="lazy" data-astro-cid-kh7btl4r> <div class="mt-2" data-astro-cid-kh7btl4r> <svg class="w-8 h-8 mx-auto text-coral" fill="currentColor" viewBox="0 0 24 24" data-astro-cid-kh7btl4r><path d="M12 3L1 9l4 2.18v6L12 21l7-3.82v-6l2-1.09V17h2V9L12 3zm
1336.82 6L12 12.72 5.18 9 12 5.28 18.82 9zM17 15.99l-5 2.73-5-2.73v-3.72L12 15l5-2.73v3.72z" data-astro-cid-kh7btl4r></path></svg> </div> <p class="text-coral text-[12px] font-medium uppercase tracking-[0.12em] mt-3 mb-2" data-astro-cid-kh7btl4r>Free Resources</p> <h3 class="text-[24px] font-medium text-text-primary" data-astro-cid-kh7btl4r>You might want to keep learning</h3> <p class="mt-3 text-[16px] text-text-secondary font-light leading-[1.7]" data-astro-cid-kh7btl4r> 134In which case, our free weekly newsletter is the best place to start. Market commentary, expert insights, and lessons from arranging hundreds of transactions every year. 135</p> <div class="mt-8" data-astro-cid-kh7btl4r> <a href="/pulse/" class="inline-flex items-center px-6 py-3 bg-transparent text-text-primary font-medium text-[16px] rounded-[8px] border-[1.5px] border-border hover:border-text-primary hover:bg-background hover:-translate-y-[1px] transition-[transform,border-color,background-color] duration-250" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> 136Sign up for free 137</a> </div> </div> </div> <!-- Invest CTA â premium dark card --> <div class="bg-charcoal rounded-[16px] p-8 relative overflow-hidden transition-[transform,box-shadow] duration-250 hover:shadow-elevated hover:-translate-y-1" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> <!-- Radial glows --> <div class="absolute inset-0 pointer-events-none" style="background: radial-gradient(ellipse at 15% 80%, rgba(255, 81, 81, 0.06), transparent 50%), radial-gradient(ellipse at 85% 20%, rgba(201, 169, 110, 0.04), transparent 50%);" data-astro-cid-kh7btl4r></div> <div class="absolute inset-0 pointer-events-none" style="background-image: var(--grain); background-repeat: repeat; background-size: 256px 256px; opacity: 0.04; mix-blend-mode: soft-light;" data-astro-cid-kh7btl4r></div> <div class="relative z-[1] text-center" data-astro-cid-kh7btl4r> <img src="/images/pages/ph-invest-laptop.png" alt="Property Hub Invest on laptop" class="w-full h-auto mb-4" loading="lazy" data-astro-cid-kh7btl4r> <div class="mt-2" data-astro-cid-kh7btl4r> <svg class="w-8 h-8 mx-auto text-warm-gold" fill="currentColor" viewBox="0 0 24 24" data-astro-cid-kh7btl4r><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" data-astro-cid-kh7btl4r></path></svg> </div> <p class="text-warm-gold text-[12px] font-medium uppercase tracking-[0.12em] mt-3 mb-2" data-astro-cid-kh7btl4r>Managed Service</p> <h3 class="text-[24px] font-medium text-white" data-astro-cid-kh7btl4r>You might be ready to talk about a portfolio</h3> <p class="mt-3 text-[16px] text-white/75 font-light leading-[1.7]" data-astro-cid-kh7btl4r> 138Book a free strategy call and we'll take the time to figure out together whether this approach makes sense for you and your goals. 139</p> <div class="mt-8" data-astro-cid-kh7btl4r> <a href="/invest/" class="inline-flex items-center px-6 py-3 bg-coral text-white font-medium text-[16px] rounded-[8px] shadow-coral-cta hover:bg-coral-hover hover:shadow-coral-hover hover:-translate-y-[1px] transition-[transform,background-color,box-shadow] duration-250" style="transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);" data-astro-cid-kh7btl4r> 140Book a call 141</a> </div> </div> </div> </div> </div> </section> </main> <footer class="bg-charcoal border-t border-white/10 py-16 lg:py-20 relative overflow-hidden" data-astro-cid-sz7xmlte> <!-- Top accent line --> <div class="absolute top-0 left-0 right-0 h-[2px]" style="background: linear-gradient(90deg, #FF5151 0%, transparent 100%);" data-astro-cid-sz7xmlte></div> <!-- Radial glows --> <div class="absolute inset-0 pointer-events-none" style="background: radial-gradient(ellipse at 15% 50%, rgba(255, 81, 81, 0.06), transparent 50%), radial-gradient(ellipse at 85% 20%, rgba(201, 169, 110, 0.04), transparent 50%);" data-astro-cid-sz7xmlte></div> <!-- Grain --> <div class="absolute inset-0 pointer-events-none" style="background-image: var(--grain); background-repeat: repeat; background-size: 256px 256px; opacity: 0.04; mix-blend-mode: soft-light;" data-astro-cid-sz7xmlte></div> <div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 relative z-[1]" data-astro-cid-sz7xmlte> <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-10 lg:gap-12" data-astro-cid-sz7xmlte> <div data-astro-cid-sz7xmlte> <a href="/" class="inline-block" data-astro-cid-sz7xmlte> <img src="/property-hub-logo.svg" alt="Property Hub" class="h-[56px] w-auto" data-astro-cid-sz7xmlte> </a> <div class="flex gap-4 mt-7" data-astro-cid-sz7xmlte> <a href="https://www.facebook.com/propertyhubuk/" target="_blank" rel="noopener noreferrer" class="text-white/70 hover:text-white active:text-white active:scale-95 [touch-action:manipulation] transition-[transform,color] duration-150" aria-label="Facebook" data-astro-cid-sz7xmlte> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" data-astro-cid-sz7xmlte><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" data-astro-cid-sz7xmlte></path></svg> </a> <a href="https://www.instagram.com/propertyhubuk/" target="_blank" rel="noopener noreferrer" class="text-white/70 hover:text-white active:text-white active:scale-95 [touch-action:manipulation] transition-[transform,color] duration-150" aria-label="Instagram" data-astro-cid-sz7xmlte> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" data-astro-cid-sz7xmlte><path d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.
141047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z" data-astro-cid-sz7xmlte></path></svg> </a> <a href="https://www.linkedin.com/company/thepropertyhub" target="_blank" rel="noopener noreferrer" class="text-white/70 hover:text-white active:text-white active:scale-95 [touch-action:manipulation] transition-[transform,color] duration-150" aria-label="LinkedIn" data-astro-cid-sz7xmlte> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" data-astro-cid-sz7xmlte><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" data-astro-cid-sz7xmlte></path></svg> </a> <a href="https://www.youtube.com/propertyhubuk" target="_blank" rel="noopener noreferrer" class="text-white/70 hover:text-white active:text-white active:scale-95 [touch-action:manipulation] transition-[transform,color] duration-150" aria-label="YouTube" data-astro-cid-sz7xmlte> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" data-astro-cid-sz7xmlte><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" data-astro-cid-sz7xmlte></path></svg> </a> </div> </div> <div data-astro-cid-sz7xmlte> <p class="footer-label text-[11px] text-white/60 mb-5" data-astro-cid-sz7xmlte>Build Your Portfolio</p> <ul class="space-y-3" data-astro-cid-sz7xmlte> <li data-astro-cid-sz7xmlte><a href="/invest/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Invest With Us</a></li> <li data-astro-cid-sz7xmlte><a href="/invest/book-a-consultation/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Book a Consultation</a></li> <li data-astro-cid-sz7xmlte><a href="/invest/case-studies/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Past Deals & Case Studies</a></li> </ul> </div> <div data-astro-cid-sz7xmlte> <p class="footer-label text-[11px] text-white/60 mb-5" data-astro-cid-sz7xmlte>Read & Learn</p> <ul class="space-y-3" data-astro-cid-sz7xmlte> <li data-astro-cid-sz7xmlte><a href="/learn/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Start Here</a></li> <li data-astro-cid-sz7xmlte><a href="/guides/getting-started/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Investing Guides</a></li> <li data-astro-cid-sz7xmlte><a href="/news/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Latest News</a></li> <li data-astro-cid-sz7xmlte><a href="/free-tools/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Investor Toolkit</a></li> </ul> </div> <div data-astro-cid-sz7xmlte> <p class="footer-label text-[11px] text-white/60 mb-5" data-astro-cid-sz7xmlte>Company</p> <ul class="space-y-3" data-astro-cid-sz7xmlte> <li data-astro-cid-sz7xmlte><a href="/about/" class="footer-link text-[13px] hover:text-white transition-colors duration-150 text-coral" data-astro-cid-sz7xmlte>About</a></li> <li data-astro-cid-sz7xmlte><a href="/contact/" class="footer-link text-[13px] hover:text-white transition-colors duration-150 text-white/70" data-astro-cid-sz7xmlte>Contact</a></li> <li data-astro-cid-sz7xmlte><a href="/jobs/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Jobs</a></li> <li data-astro-cid-sz7xmlte><a href="/developers/" class="footer-link text-[13px] hover:text-white transition-colors duration-150 text-white/70" data-astro-cid-sz7xmlte>Developers</a></li> <li data-astro-cid-sz7xmlte><a href="/charities/" class="footer-link text-[13px] text-white/70 hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Charities</a></li> </ul> </div> </div> <!-- Legal strip --> <div class="mt-14 pt-8 border-t border-white/10 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between" data-astro-cid-sz7xmlte> <div class="space-y-1" data-astro-cid-sz7xmlte> <p class="footer-link text-white/60 text-[12px] leading-[1.5]" data-astro-cid-sz7xmlte>Property Hub © 2026 All rights reserved. Property Hub Ltd. Registered in England and Wales.</p> <p class="footer-link text-white/60 text-[12px] leading-[1.5]" data-astro-cid-sz7xmlte>Property Hub Invest Ltd (07495608). 1st Floor B, 117-119 Portland Street, Manchester, M1 6ED.</p> </div> <nav class="flex flex-wrap gap-x-6 gap-y-2 flex-shrink-0" data-astro-cid-sz7xmlte> <a href="/privacy-policy/" class="footer-link text-white/70 text-[13px] hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Privacy Policy</a> <a href="/terms-conditions/" class="footer-link text-white/70 text-[13px] hover:text-white transition-colors duration-150" data-astro-cid-sz7xmlte>Terms & Conditions</a> </nav> </div> </div> </footer>
141<script> 142 (function () { 143 const STORAGE_KEY = 'ph_attribution_v1'; 144 const UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; 145 // Ad-platform click IDs, plus our own short-link id (appended by the /go/* 146 // edge function). Captured + stored like UTMs (first-touch wins within the 147 // session) so they survive navigation before a form is submitted. 148 const CLICK_ID_KEYS = ['gclid', 'gbraid', 'wbraid', 'fbclid', 'msclkid', 'ph_link_id']; 149 150 function readCookie(name) { 151 const prefix = name + '='; 152 const part = document.cookie 153 .split(';') 154 .map((value) => value.trim()) 155 .find((value) => value.indexOf(prefix) === 0); 156 return part ? decodeURIComponent(part.slice(prefix.length)) : ''; 157 } 158 159 function hasAdvertisingConsent() { 160 const consent = readCookie('cookieyes-consent'); 161 return /(?:^|,)advertisement:(?:yes|true)(?:,|$)/i.test(consent); 162 } 163 164 function readStored() { 165 try { 166 const raw = sessionStorage.getItem(STORAGE_KEY); 167 return raw ? JSON.parse(raw) : null; 168 } catch (e) { 169 return null; 170 } 171 } 172 173 function writeStored(data) { 174 try { 175 sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data)); 176 } catch (e) { 177 /* sessionStorage unavailable â fail silently */ 178 } 179 } 180 181 function captureFromUrl() { 182 const params = new URLSearchParams(window.location.search); 183 const stored = readStored() || {}; 184 185 let updated = false; 186 UTM_KEYS.concat(CLICK_ID_KEYS).forEach((key) => { 187 const val = params.get(key); 188 if (val && !stored[key]) { 189 stored[key] = val; 190 if (key === 'fbclid') stored.fbclid_captured_at = Date.now(); 191 updated = true; 192 } 193 }); 194 195 if (!stored.landing_page) { 196 stored.landing_page = window.location.pathname + window.location.search; 197 updated = true; 198 } 199 if (!stored.referrer) { 200 stored.referrer = document.referrer || ''; 201 updated = true; 202 } 203 204 if (updated) writeStored(stored); 205 return stored; 206 } 207 208 function generateSubmissionId() { 209 try { 210 if (window.crypto && typeof window.crypto.randomUUID === 'function') { 211 return window.crypto.randomUUID(); 212 } 213 } catch (e) { 214 /* fall through to manual generation */ 215 } 216 // RFC4122-ish fallback for older browsers. 217 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 218 var r = (Math.random() * 16) | 0; 219 var v = c === 'x' ? r : (r & 0x3) | 0x8; 220 return v.toString(16); 221 }); 222 } 223 224 function injectHidden(form, name, value) { 225 // Set the value on an EXISTING input if there is one, otherwise create it. 226 // The attribution fields are also declared as static empty hidden inputs in 227 // LeadForm so that Netlify registers them in the form's build-time schema â 228 // Netlify only records fields it saw in the static HTML, and silently drops 229 // POSTed fields it didn't (this is why utm_* / referrer / submitted_url were 230 // missing from submissions). So here we must UPDATE the existing input's 231 // value rather than bail, then fall back to creating one for any form that 232 // doesn't declare it statically. 233 const val = value == null ? '' : value; 234 const existing = form.querySelector('input[name="' + name + '"]'); 235 if (existing) { 236 existing.value = val; 237 return; 238 } 239 const input = document.createElement('input'); 240 input.type = 'hidden'; 241 input.name = name; 242 input.value = val; 243 form.appendChild(input); 244 } 245 246 function attachToForms(attribution) { 247 document.querySelectorAll('form[data-ph-form]').forEach((form) => { 248 form.addEventListener( 249 'submit', 250 function () { 251 const fresh = readStored() || attribution; 252 UTM_KEYS.forEach((key) => injectHidden(form, key, fresh[key] || '')); 253 CLICK_ID_KEYS.forEach((key) => injectHidden(form, key, fresh[key] || '')); 254 const advertisingConsent = hasAdvertisingConsent(); 255 const fbp = advertisingConsent ? readCookie('_fbp') : ''; 256 const cookieFbc = advertisingConsent ? readCookie('_fbc') : ''; 257 const derivedFbc = 258 advertisingConsent && fresh.fbclid 259 ? 'fb.1.' + String(fresh.fbclid_captured_at || Date.now()) + '.' + fresh.fbclid 260 : ''; 261 injectHidden(form, 'fbp', fbp); 262 injectHidden(form, 'fbc', cookieFbc || derivedFbc); 263 injectHidden(form, 'advertising_consent', advertisingConsent ? 'true' : 'false'); 264 injectHidden(form, 'landing_page', fresh.landing_page || ''); 265 injectHidden(form, 'referrer', fresh.referrer || ''); 266 injectHidden(form, 'submitted_url', window.location.href); 267 injectHidden(form, 'submission_id', generateSubmissionId()); 268 }, 269 { capture: true } 270 ); 271 }); 272 } 273 274 const attribution = captureFromUrl(); 275 276 if (document.readyState === 'loading') { 277 document.addEventListener('DOMContentLoaded', function () { 278 attachToForms(attribution); 279 }); 280 } else { 281 attachToForms(attribution); 282 } 283 })(); 284</script>
284
284<script> 285 (function () { 286 var PARDOT_HANDLER_URL = 'https://go.propertyhub.net/l/641323/2026-02-16/h8hb71'; 287 288 function postToPardot(form) { 289 try { 290 var emailInput = form.querySelector('input[type="email"], input[name="email"]'); 291 if (!emailInput || !emailInput.value) return; 292 293 var body = new URLSearchParams(); 294 body.append('email', emailInput.value); 295 296 var firstNameInput = form.querySelector('input[name="first_name"]'); 297 if (firstNameInput && firstNameInput.value) { 298 body.append('first_name', firstNameInput.value); 299 } 300 var lastNameInput = form.querySelector('input[name="last_name"]'); 301 if (lastNameInput && lastNameInput.value) { 302 body.append('last_name', lastNameInput.value); 303 } 304 305 // Fire-and-forget. mode:'no-cors' avoids CORS preflight; credentials:'include' 306 // ensures the Pardot tracking cookie travels with the request so Pardot can 307 // stitch the anonymous visitor to this prospect's email. 308 fetch(PARDOT_HANDLER_URL, { 309 method: 'POST', 310 body: body, 311 mode: 'no-cors', 312 credentials: 'include', 313 }); 314 } catch (e) { 315 /* never block the real form submission */ 316 } 317 } 318 319 // Some forms are not investor lead-gen (e.g. the developer/B2B enquiry form) 320 // and must NOT push contacts into the investor Pardot/Salesforce. Opt out 321 // with data-no-pardot on the form. 322 document.querySelectorAll('form[data-ph-form]:not([data-no-pardot])').forEach(function (form) { 323 form.addEventListener( 324 'submit', 325 function () { 326 postToPardot(form); 327 }, 328 { capture: true } 329 ); 330 }); 331 })(); 332</script>
332
332<script type="module" src="/_astro/PhoneCountryInput.astro_astro_type_script_index_0_lang.CZn1wpMa.js"></script>
332
332<script> 333 (function () { 334 function appendToUrl(rawUrl, params) { 335 try { 336 const url = new URL(rawUrl, window.location.origin); 337 params.forEach(([key, value]) => { 338 if (value && !url.searchParams.has(key)) { 339 url.searchParams.set(key, value); 340 } 341 }); 342 const isAbsolute = /^https?:\/\//i.test(rawUrl); 343 return isAbsolute ? url.toString() : url.pathname + url.search + url.hash; 344 } catch (e) { 345 return rawUrl; 346 } 347 } 348 349 function buildRedirectUrl(form, formData) { 350 // The browser-visible success destination is deliberately separate from 351 // the form action: Netlify requires form actions to remain root-relative, 352 // while Invest submissions redirect to an external Calendly URL. 353 const action = form.dataset.successUrl || form.getAttribute('action') || '/'; 354 const params = []; 355 // Never put submitted form fields in a redirect URL. URLs are copied 356 // into browser history, request logs and analytics page locations. Only 357 // the approved campaign fields are safe to carry to the destination. 358 ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach((key) => { 359 const val = formData.get(key); 360 if (val) params.push([key, String(val)]); 361 }); 362 return appendToUrl(action, params); 363 } 364 365 function showThankYou(form) { 366 const selector = form.dataset.successTarget; 367 const target = selector 368 ? document.querySelector(selector) 369 : (form.parentElement && form.parentElement.querySelector('[data-thank-you]')) || null; 370 form.hidden = true; 371 if (target) { 372 target.hidden = false; 373 target.scrollIntoView({ behavior: 'smooth', block: 'center' }); 374 } 375 } 376 377 const TRACKED_OUTCOMES = new Set([ 378 'generate_lead', 379 'developer_lead', 380 'subscriber_signup', 381 'webinar_registration', 382 ]); 383 384 function safeValue(formData, key) { 385 const value = formData.get(key); 386 return typeof value === 'string' ? value : ''; 387 } 388 389 function formLocation(form) { 390 if (form.dataset.formLocation) return form.dataset.formLocation; 391 const section = form.closest('section[id]'); 392 return section ? section.id : 'inline'; 393 } 394 395 function trackingPayload(form, formData, eventName) { 396 const advertisingConsent = safeValue(formData, 'advertising_consent') === 'true'; 397 return { 398 event: eventName, 399 analytics_event: safeValue(formData, 'analytics_event'), 400 submission_id: safeValue(formData, 'submission_id'), 401 event_id: safeValue(formData, 'submission_id'), 402 form_name: form.getAttribute('name') || '', 403 form_type: safeValue(formData, 'form_type'), 404 form_location: formLocation(form), 405 form_label: safeValue(formData, 'form_label'), 406 signup_type: safeValue(formData, 'signup_type'), 407 asset_id: safeValue(formData, 'asset_id'), 408 asset_name: safeValue(formData, 'asset_name'), 409 asset_type: safeValue(formData, 'asset_type'), 410 webinar_id: 411 safeValue(formData, 'analytics_event') === 'webinar_registration' 412 ? safeValue(formData, 'asset_id') 413 : '', 414 provider: safeValue(formData, 'provider'), 415 advertising_consent: advertisingConsent, 416 // Meta browser/server identifiers are only present after advertising 417 // consent. They are deliberately kept out of the data layer otherwise. 418 fbp: advertisingConsent ? safeValue(formData, 'fbp') : '', 419 fbc: advertisingConsent ? safeValue(formData, 'fbc') : '', 420 source_page: safeValue(formData, 'source_page') || window.location.pathname, 421 }; 422 } 423 424 function pushOutcome(form, formData, eventName, onComplete) { 425 const analyticsEvent = safeValue(formData, 'analytics_event'); 426 if (!TRACKED_OUTCOMES.has(analyticsEvent)) { 427 onComplete(); 428 return; 429 } 430 431 window.dataLayer = window.dataLayer || []; 432 let completed = false; 433 const finish = function () { 434 if (completed) return; 435 completed = true; 436 window.clearTimeout(fallback); 437 onComplete(); 438 }; 439 const fallback = window.setTimeout(finish, 1200); 440 window.dataLayer.push({ 441 ...trackingPayload(form, formData, eventName), 442 eventCallback: finish, 443 eventTimeout: 1000, 444 }); 445 } 446 447 // Inline submission error, announced to assistive tech via role="alert". 448 // Created lazily per form, re-used on repeat failures, cleared on retry. 449 function clearFormError(form) { 450 const el = form.querySelector('[data-form-error]'); 451 if (el) el.hidden = true; 452 } 453 454 function showFormError(form, pendingConfirmation = false) { 455 let el = form.querySelector('[data-form-error]'); 456 if (!el) { 457 el = document.createElement('div'); 458 el.setAttribute('data-form-error', ''); 459 el.setAttribute('role', 'alert'); 460 el.style.cssText = 461 'margin-top:12px;padding:12px 16px;border-radius:8px;font-size:14px;' + 462 'background:rgba(255,81,81,0.10);border:1px solid rgba(255,81,81,0.45);color:inherit;'; 463 const submitBtn = form.querySelector('button[type="submit"]'); 464 if (submitBtn && submitBtn.parentElement === form) { 465 submitBtn.insertAdjacentElement('afterend', el); 466 } else { 467 form.appendChild(el); 468 } 469 } 470 el.textContent = pendingConfirmation 471 ? 'Weâre still confirming your submission. Please wait a moment and try again; your details wonât be sent twice.' 472 : form.hasAttribute('data-calculator-capture') 473 ? "Sorry, we couldnât confirm your submission. Check your connection and try again." 474 : "Sorry, something went wrong sending your details. Check your connection and try again â nothing has been submitted.";
475 el.hidden = false; 476 } 477 478 document.querySelectorAll('form[data-ph-form]').forEach((form) => { 479 const calculatorCapture = form.hasAttribute('data-calculator-capture'); 480 const retryStorageKey = calculatorCapture 481 ? 'ph:portfolio-attempt:' + (form.closest('[data-capture]')?.getAttribute('data-capture') || form.id) 482 : null; 483 const readRetry = () => { 484 try { return JSON.parse(sessionStorage.getItem(retryStorageKey)); } catch { return null; } 485 }; 486 const writeRetry = (attempt) => { 487 try { sessionStorage.setItem(retryStorageKey, JSON.stringify(attempt)); } catch { /* in-memory retry remains */ } 488 }; 489 const clearRetry = () => { 490 try { sessionStorage.removeItem(retryStorageKey); } catch { /* private mode */ } 491 }; 492 let pendingAttempt = null; 493 let submitting = false; 494 form.addEventListener('submit', async (event) => { 495 event.preventDefault(); 496 // Native `required` blocks an empty surname, but accepts whitespace. 497 // Keep that invalid value out of the Invest enquiry and Salesforce Zap. 498 const lastNameInput = form.getAttribute('name') === 'invest' 499 ? form.querySelector('input[name="last_name"]') : null; 500 if (lastNameInput instanceof HTMLInputElement && !lastNameInput.value.trim()) { 501 lastNameInput.setCustomValidity('Enter your last name.'); 502 lastNameInput.reportValidity(); 503 lastNameInput.addEventListener('input', () => lastNameInput.setCustomValidity(''), { once: true }); 504 return; 505 } 506 if (calculatorCapture && submitting) return; 507 submitting = true; 508 clearFormError(form); 509 const submitBtn = form.querySelector('button[type="submit"]'); 510 if (submitBtn) submitBtn.disabled = true; 511 512 // Netlify uses a field named `title` for the submission summary shown in 513 // its dashboard. Populate it from the submitted email here, before 514 // FormData snapshots the form. This field is sent only to Netlify; the 515 // analytics payload below is explicitly allow-listed and excludes it. 516 const emailInput = form.querySelector('input[type="email"], input[name="email"]'); 517 const titleInput = form.querySelector('input[name="title"]'); 518 if (emailInput instanceof HTMLInputElement && titleInput instanceof HTMLInputElement) { 519 titleInput.value = emailInput.value.trim(); 520 } 521 522 const formData = new FormData(form); 523 const body = new URLSearchParams(); 524 formData.forEach((value, key) => { 525 body.append(key, typeof value === 'string' ? value : ''); 526 }); 527 528 // Netlify routes an AJAX-submitted form by a `form-name` field in the 529 // POST body (the form's `name` attribute alone isn't enough once we 530 // intercept and fetch() ourselves). Without it Netlify returns 404. 531 const netlifyFormName = form.getAttribute('name'); 532 if (netlifyFormName) body.set('form-name', netlifyFormName); 533 534 // Normalise EVERY checkbox to a single true/false (applies to all forms: 535 // newsletter consent, sunday-times publish_consent, survey unsubscribe 536 // boxes). HTML omits an unticked box entirely, so set an explicit value 537 // from the checkbox state so downstream (Salesforce booleans, Zaps) gets 538 // a clean value. Do NOT pair a checkbox with a hidden default of the 539 // same name: a native (no-JS) submit then sends both ("false,true") and 540 // Salesforce rejects the enquiry. Replaces the old "on"/absent. 541 form.querySelectorAll('input[type="checkbox"][name]').forEach((cb) => { 542 body.set(cb.name, cb.checked ? 'true' : 'false'); 543 }); 544 545 try { 546 if (calculatorCapture) { 547 // Only calculator forms retain IDs and wait for assessment storage. 548 // Ordinary forms keep the ID supplied by FormAttribution each time. 549 const payload = JSON.stringify(Array.from(body.entries()).filter( 550 ([key]) => !['submission_id', 'assessment_id'].includes(key) 551 )); 552 // Only a digest and random IDs survive a reload; no name, email, 553 // answers or form payload is written to browser storage. 554 const fingerprintParts = [payload]; 555 form.dispatchEvent(new CustomEvent('ph:form-fingerprint', { 556 bubbles: true, detail: { parts: fingerprintParts }, 557 })); 558 const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(fingerprintParts))); 559 const fingerprint = Array.from(new Uint8Array(digest), b => b.toString(16).padStart(2, '0')).join(''); 560 const restored = pendingAttempt || readRetry(); 561 if (!restored || restored.fingerprint !== fingerprint) { 562 pendingAttempt = { fingerprint, id: body.get('submission_id'), assessmentId: null, netlifyAttempted: false }; 563 } else { 564 pendingAttempt = restored; 565 } 566 body.set('submission_id', pendingAttempt.id);
567 if (pendingAttempt.assessmentId) body.set('assessment_id', pendingAttempt.assessmentId); 568 else if (!String(body.get('assessment_id') || '').startsWith('preview-')) body.set('assessment_id', ''); 569 formData.set('submission_id', pendingAttempt.id); 570 writeRetry(pendingAttempt); 571 const preparations = []; 572 form.dispatchEvent(new CustomEvent('ph:form-prepare', { 573 bubbles: true, 574 detail: { formData: body, waitUntil: promise => preparations.push(Promise.resolve(promise)) }, 575 })); 576 await Promise.all(preparations); 577 pendingAttempt.assessmentId = body.get('assessment_id'); 578 writeRetry(pendingAttempt); 579 } 580 if (form.hasAttribute('data-form-preview')) { 581 form.dispatchEvent(new CustomEvent('ph:form-preview', { 582 bubbles: true, 583 detail: { formData: body }, 584 })); 585 pendingAttempt = null; 586 if (calculatorCapture) clearRetry(); 587 if (submitBtn) submitBtn.disabled = false; 588 return; 589 } 590 let alreadyVerified = false; 591 if (calculatorCapture && pendingAttempt.netlifyAttempted) { 592 const statusUrl = '/api/portfolio/capture?submission_id=' + encodeURIComponent(pendingAttempt.id) 593 + '&assessment_id=' + encodeURIComponent(pendingAttempt.assessmentId); 594 const statusResponse = await fetch(statusUrl, { cache: 'no-store' }); 595 if (!statusResponse.ok) throw new Error('Could not confirm prior submission'); 596 const receipt = await statusResponse.json(); 597 alreadyVerified = receipt.status === 'verified'; 598 if (!alreadyVerified) throw new Error('pending_confirmation'); 599 } 600 if (!alreadyVerified) { 601 if (calculatorCapture) { 602 pendingAttempt.netlifyAttempted = true; 603 writeRetry(pendingAttempt); 604 } 605 const res = await fetch('/', { 606 method: 'POST', 607 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 608 body: body.toString(), 609 }); 610 if (!res.ok) { 611 // These responses definitively reject the POST. A timeout, 5xx 612 // or lost response is uncertain and must be reconciled first. 613 if (calculatorCapture && [400, 401, 403, 404, 413, 415, 422, 429].includes(res.status)) { 614 pendingAttempt.netlifyAttempted = false; 615 writeRetry(pendingAttempt); 616 } 617 throw new Error('HTTP ' + res.status); 618 } 619 } 620 pendingAttempt = null; 621 if (calculatorCapture) clearRetry(); 622 623 form.dispatchEvent(new CustomEvent('ph:form-success', { 624 bubbles: true, 625 detail: { formData: body }, 626 })); 627 628 pushOutcome(form, formData, 'ph_form_success', function () { 629 const isInline = form.hasAttribute('data-inline-success'); 630 if (isInline) { 631 showThankYou(form); 632 } else { 633 window.location.href = buildRedirectUrl(form, formData); 634 } 635 }); 636 } catch (err) { 637 console.error('Form submission failed:', err); 638 const analyticsEvent = safeValue(formData, 'analytics_event'); 639 if (!form.hasAttribute('data-form-preview') && TRACKED_OUTCOMES.has(analyticsEvent)) { 640 window.dataLayer = window.dataLayer || []; 641 window.dataLayer.push({ 642 ...trackingPayload(form, formData, 'ph_form_error'), 643 error_type: err instanceof Error && err.message === 'pending_confirmation' 644 ? 'pending_confirmation' 645 : err instanceof Error && /^HTTP /.test(err.message) ? 'http_error' : 'network_error', 646 http_status: 647 err instanceof Error && /^HTTP \d+$/.test(err.message) 648 ? err.message.slice(5) 649 : '', 650 }); 651 } 652 if (submitBtn) submitBtn.disabled = false;
653 showFormError(form, err instanceof Error && err.message === 'pending_confirmation'); 654 } finally { 655 submitting = false; 656 } 657 }); 658 // Preview buttons are disabled in HTML so a missing script cannot fall 659 // back to a native submission. Enable only after the handler is attached. 660 if (form.hasAttribute('data-form-preview')) { 661 const button = form.querySelector('button[type="submit"]'); 662 if (button) button.disabled = false; 663 } 664 }); 665 666 document.addEventListener('click', function (event) { 667 const target = event.target; 668 const link = target instanceof Element ? target.closest('a[data-analytics-cta]') : null; 669 if (!link) return; 670 window.dataLayer = window.dataLayer || []; 671 window.dataLayer.push({ 672 event: 'ph_cta_click', 673 cta_name: link.dataset.ctaName || '', 674 cta_location: link.dataset.ctaLocation || 'unknown', 675 business_area: link.dataset.businessArea || 'general', 676 destination_url: link.getAttribute('href') || '', 677 }); 678 }); 679 })(); 680</script>
680
680<script> 681 (function () { 682 function attachEmailToQuizLinks() { 683 const params = new URLSearchParams(window.location.search); 684 const email = params.get('email') || params.get('Email'); 685 if (!email) return; 686 687 const links = document.querySelectorAll('a[href*="quiz.propertyhub.net"]:not([data-email-relayed])'); 688 links.forEach(function (link) { 689 try { 690 const url = new URL(link.href); 691 url.searchParams.set('email', email); 692 link.href = url.toString(); 693 link.dataset.emailRelayed = '1'; 694 } catch (e) { 695 /* malformed href â skip */ 696 } 697 }); 698 } 699 700 if (document.readyState === 'loading') { 701 document.addEventListener('DOMContentLoaded', attachEmailToQuizLinks); 702 } else { 703 attachEmailToQuizLinks(); 704 } 705 706 const observer = new MutationObserver(attachEmailToQuizLinks); 707 if (document.body) { 708 observer.observe(document.body, { childList: true, subtree: true }); 709 } else { 710 document.addEventListener('DOMContentLoaded', function () { 711 observer.observe(document.body, { childList: true, subtree: true }); 712 }); 713 } 714 })(); 715</script>
715 <!-- n8n chat widget â themed to match Property Hub. 716 Maps the widget's CSS variables to the site's brand tokens. Because 717 these reference var(--color-coral) etc. and the widget mounts inside 718 <body>, invest pages (.theme-invest) automatically render it in purple. -->
718<script type="module"> 719 // Kill switch for the n8n chat widget. Set to false to disable loading 720 // the bot site-wide (e.g. while its n8n workflow is being fixed before 721 // go-live); flip back to true to restore it â no other change needed. 722 // The whole loader below is left intact so re-enabling is a one-line flip. 723 const CHAT_ENABLED = false; 724 725 // The chat bundle is large and secondary to the page's main task. Load it 726 // only after the visitor interacts, rather than competing with LCP and 727 // parsing 1.9MB of JavaScript during an untouched initial page load. 728 let chatLoadPromise; 729 730 async function loadPropertyHubChat() { 731 if (chatLoadPromise) return chatLoadPromise; 732 733 chatLoadPromise = (async () => { 734 const [{ createChat }] = await Promise.all([ 735 import(/* @vite-ignore */ "https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js"), 736 loadChatStyles(), 737 ]); 738 739 createChat({ 740 webhookUrl: 741 "https://primary-production-86ea3.up.railway.app/webhook/d718b778-6558-4da9-96dc-cfcf5086e5a5/chat", 742 initialMessages: [ 743 "Hello! I'm here to answer your questions about PropertyHub. How can I help?", 744 ], 745 }); 746 747 const chatRoot = await waitForChatRoot(); 748 patchChatA11y(chatRoot); 749 observeChatA11y(chatRoot); 750 document.dispatchEvent(new CustomEvent("propertyhub:chat-ready")); 751 })().catch((error) => { 752 chatLoadPromise = undefined; 753 throw error; 754 }); 755 756 return chatLoadPromise; 757 } 758 759 function loadChatStyles() { 760 if (document.getElementById("propertyhub-chat-styles")) { 761 return Promise.resolve(); 762 } 763 764 return new Promise((resolve, reject) => { 765 const link = document.createElement("link"); 766 link.id = "propertyhub-chat-styles"; 767 link.rel = "stylesheet"; 768 link.href = "https://cdn.jsdelivr.net/npm/@n8n/chat/dist/style.css"; 769 link.onload = () => resolve(); 770 link.onerror = () => reject(new Error("Chat styles failed to load")); 771 document.head.appendChild(link); 772 }); 773 } 774 775 function waitForChatRoot() { 776 const existing = document.querySelector("#n8n-chat, .n8n-chat"); 777 if (existing) return Promise.resolve(existing); 778 779 return new Promise((resolve, reject) => { 780 const timeout = window.setTimeout(() => { 781 observer.disconnect(); 782 reject(new Error("Chat widget did not mount")); 783 }, 5000); 784 const observer = new MutationObserver(() => { 785 const root = document.querySelector("#n8n-chat, .n8n-chat"); 786 if (!root) return; 787 window.clearTimeout(timeout); 788 observer.disconnect(); 789 resolve(root); 790 }); 791 observer.observe(document.body, { childList: true, subtree: true }); 792 }); 793 } 794 795 // Accessibility patches for the n8n widget's own markup, which we can't 796 // configure. Two problems it ships with: 797 // 1. Its "Hi there! ð" greeting is an <h1>, so every page ends up with a 798 // second page-level heading (present even while the chat is closed). 799 // We can't swap the tag in place, so we demote it with ARIA: role 800 // "heading" at level 2 wins over the implicit <h1> level for 801 // assistive tech, leaving the page's real <h1> unique. 802 // 2. Its launcher is a bare <div>/SVG with no role or label, so keyboard 803 // users can't reach or activate it. We give it a button role, a name, 804 // and Enter/Space handling. 805 // The widget mounts asynchronously and re-renders on open/close, so we 806 // re-apply on every mutation rather than once. 807 function patchChatA11y(root) { 808 root.querySelectorAll("h1").forEach(function (h) { 809 h.setAttribute("role", "heading"); 810 h.setAttribute("aria-level", "2"); 811 }); 812 813 var toggle = root.querySelector(".chat-window-toggle, [class*='toggle']"); 814 if (toggle && toggle.tagName !== "BUTTON" && !toggle.dataset.a11yPatched) { 815 toggle.dataset.a11yPatched = "true"; 816 toggle.setAttribute("role", "button"); 817 toggle.setAttribute("tabindex", "0"); 818 toggle.setAttribute("aria-label", "Open chat"); 819 toggle.addEventListener("keydown", function (e) { 820 if (e.key === "Enter" || e.key === " ") { 821 e.preventDefault(); 822 toggle.click(); 823 } 824 }); 825 } 826 } 827 828 function observeChatA11y(root) { 829 new MutationObserver(() => patchChatA11y(root)).observe(root, { 830 childList: true, 831 subtree: true, 832 }); 833 } 834 835 window.loadPropertyHubChat = loadPropertyHubChat; 836 837 // Only wire up the interaction listeners that trigger chat loading when 838 // the kill switch is on. With CHAT_ENABLED = false the widget never loads 839 // and no bubble appears; everything above stays ready to run again. 840 if (CHAT_ENABLED) { 841 const intentEvents = ["pointerdown", "keydown", "scroll"]; 842 const handleFirstIntent = () => {
843 intentEvents.forEach((eventName) => 844 window.removeEventListener(eventName, handleFirstIntent), 845 ); 846 loadPropertyHubChat().catch(() => { 847 // Chat is progressive enhancement; navigation and forms must keep 848 // working if the third-party CDN or webhook is unavailable. 849 }); 850 }; 851 intentEvents.forEach((eventName) => 852 window.addEventListener(eventName, handleFirstIntent, { 853 once: true, 854 passive: true, 855 }), 856 ); 857 } 858 </script>
858 <!-- Chat-widget suppression (issue #36). 859 The n8n chat bubble is secondary to conversion, so we hide it in two 860 situations: 861 1. While any element flagged `[data-suppress-chat]` is present AND 862 actually shown â e.g. the mobile sticky CTA bar, which slides in 863 from the bottom corner and would otherwise overlap the bubble. 864 2. While any lead form is (even partially) in the viewport â the 865 bubble is a distraction at the moment we want a form fill. 866 Suppression toggles the `chat-suppressed` class on <html>; the CSS in 867 the style block above does the actual hiding. 868 869 This script is deliberately self-contained and does NOT depend on the 870 chat widget having loaded: if the CDN module is blocked the class 871 still toggles harmlessly, and nothing throws. It also works no matter 872 the merge order of the sticky-CTA-bar PR â it discovers suppressors 873 from the DOM rather than referencing the bar directly. -->
873<script> 874 (function () { 875 var html = document.documentElement; 876 877 // --- form suppressors --------------------------------------------- 878 // Every real lead form on the site renders a <form> (LeadForm.astro 879 // and a handful of inline page forms). We observe all of them except 880 // ones that are explicitly opted out or never rendered. 881 var formsIntersecting = new Set(); 882 883 // --- generic [data-suppress-chat] suppressors --------------------- 884 // The mobile CTA bar (and any future element) flags itself with 885 // `data-suppress-chat`. The bar slides in/out, so presence alone isn't 886 // enough: we only suppress while such an element is actually shown. 887 // "Shown" = on screen (IntersectionObserver) AND not visually hidden 888 // (display/visibility/opacity 0, or data-suppress-chat="false"/"off"). 889 // We watch both intersection and attribute/class changes so the bar 890 // toggling its visible state re-evaluates suppression. 891 var flagIntersecting = new Set(); 892 893 function flagIsActive(el) { 894 if (!flagIntersecting.has(el)) return false; 895 var val = (el.getAttribute("data-suppress-chat") || "").toLowerCase(); 896 if (val === "false" || val === "off" || val === "0") return false; 897 var cs = window.getComputedStyle(el); 898 if ( 899 cs.display === "none" || 900 cs.visibility === "hidden" || 901 parseFloat(cs.opacity) === 0 902 ) { 903 return false; 904 } 905 return true; 906 } 907 908 function update() { 909 var suppress = formsIntersecting.size > 0; 910 if (!suppress) { 911 // Only walk the flag set if no form already triggered suppression. 912 var flags = document.querySelectorAll("[data-suppress-chat]"); 913 for (var i = 0; i < flags.length; i++) { 914 if (flagIsActive(flags[i])) { 915 suppress = true; 916 break; 917 } 918 } 919 } 920 html.classList.toggle("chat-suppressed", suppress); 921 } 922 923 // IntersectionObserver shared by forms; threshold 0 = "even partially". 924 var io; 925 try { 926 io = new IntersectionObserver( 927 function (entries) { 928 entries.forEach(function (entry) { 929 var set = 930 entry.target.tagName === "FORM" 931 ? formsIntersecting 932 : flagIntersecting; 933 if (entry.isIntersecting) set.add(entry.target); 934 else set.delete(entry.target); 935 }); 936 update(); 937 }, 938 { threshold: 0 } 939 ); 940 } catch (e) { 941 // No IntersectionObserver (very old browser) â bail out quietly; 942 // the chat simply never gets suppressed, which is a safe default. 943 return; 944 } 945 946 function isHidden(el) { 947 // Skip forms that aren't actually visible (e.g. the honeypot row or 948 // a form inside a closed accordion) â they shouldn't suppress chat. 949 return !el.offsetParent && el.offsetWidth === 0 && el.offsetHeight === 0; 950 } 951 952 var watchedForms = new WeakSet(); 953 var watchedFlags = new WeakSet(); 954 955 function observeForms() { 956 document.querySelectorAll("form").forEach(function (form) { 957 if (watchedForms.has(form)) return; 958 if (form.hasAttribute("data-no-suppress-chat")) return; 959 if (isHidden(form)) return; 960 watchedForms.add(form); 961 io.observe(form); 962 }); 963 } 964 965 function observeFlags() { 966 document 967 .querySelectorAll("[data-suppress-chat]") 968 .forEach(function (el) { 969 if (watchedFlags.has(el)) return; 970 watchedFlags.add(el); 971 io.observe(el); 972 }); 973 } 974 975 function scan() { 976 observeForms(); 977 observeFlags(); 978 update(); 979 } 980 981 scan(); 982 983 // The CTA bar mounts/toggles after load and Astro view transitions can 984 // swap page content; a MutationObserver keeps our observers current and 985 // re-evaluates when a flagged element changes its class/attributes 986 // (e.g. the bar adding/removing `is-visible`). 987 try { 988 var mo = new MutationObserver(function (mutations) { 989 var needsRescan = false;
990 var needsUpdate = false; 991 mutations.forEach(function (m) { 992 if (m.type === "childList") needsRescan = true; 993 else if (m.type === "attributes") { 994 // A flagged element changed visibility-affecting attributes. 995 needsUpdate = true; 996 } 997 }); 998 if (needsRescan) scan(); 999 else if (needsUpdate) update(); 1000 }); 1001 mo.observe(document.body, { 1002 childList: true, 1003 subtree: true, 1004 attributes: true, 1005 attributeFilter: ["class", "style", "data-suppress-chat", "hidden"], 1006 }); 1007 } catch (e) { 1008 /* MutationObserver unavailable â initial scan still applies. */ 1009 } 1010 1011 document.addEventListener("astro:page-load", scan); 1012 })(); 1013 </script>
1013 </body> </html>
1013<script type="module">const s=document.getElementById("team-track"),r=document.getElementById("team-prev"),c=document.getElementById("team-next");if(s&&r&&c){let a=function(){const e=s.parentElement;if(!e)return 0;const i=Math.floor((e.offsetWidth+l)/d());return Math.max(0,f-i)},n=function(){const e=d();s.style.transform=`translateX(-${t*e}px)`;const i=a();r.style.opacity=t===0?"0.4":"1",c.style.opacity=t>=i?"0.4":"1"},t=0;const p=()=>window.innerWidth<640?300:380,l=32,d=()=>p()+l,f=s.children.length;r.addEventListener("click",()=>{t>0&&(t--,n())}),c.addEventListener("click",()=>{const e=a();t<e&&(t++,n())});let m=setInterval(()=>{const e=a();t<e?t++:t=0,n()},4e3);const o=s.parentElement;o&&(o.addEventListener("mouseenter",()=>clearInterval(m)),o.addEventListener("mouseleave",()=>{m=setInterval(()=>{const e=a();t<e?t++:t=0,n()},4e3)})),n(),window.addEventListener("resize",()=>{const e=a();t>e&&(t=e),n()})}</script>
1013 <!-- Scroll reveal animations -->
1013<script type="module">const s={threshold:.1,rootMargin:"0px 0px -40px 0px"},t=new IntersectionObserver(e=>{e.forEach(r=>{r.isIntersecting&&(r.target.classList.add("visible"),t.unobserve(r.target))})},s);document.querySelectorAll("[data-reveal]").forEach(e=>{t.observe(e)});const a=new IntersectionObserver(e=>{e.forEach(r=>{r.isIntersecting&&(r.target.classList.add("visible"),a.unobserve(r.target))})},s);document.querySelectorAll("[data-reveal-stagger]").forEach(e=>{a.observe(e)});</script>
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.