PageSourceSearch

https://propertyhub.net/about/

html propertyhub.net collected 2026-10-02 12:55:48 UTC 111,152 bytes, 1,013 lines download raw bytes

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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; Sourcing</a> <a href="/guides/financing-tax/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Financing &amp; 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 &amp; Portfolio</a> <a href="/guides/lettings-landlord/" class="block py-2 text-[13px] text-text-secondary hover:text-coral transition-colors duration-150">Lettings &amp; 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 &amp; 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 &amp; 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 &ndash; 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 &ndash; FTSE-listed housebuilders and major regional players &ndash; 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 &ndash; 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 &ndash; 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 &amp; 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 &ndash; 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>&larr; Scroll to explore &rarr;</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&pound;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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &copy; 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 &amp; 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.