1<!DOCTYPE html> 2<html lang="en"> 3 <head> 4 <meta charset="utf-8" /> 5 <meta 6 name="viewport" 7 content="width=device-width, initial-scale=1, maximum-scale=5" 8 /> 9 10 <!-- SEO Meta Tags --> 11 <!-- description, keywords, robots, og:*, twitter:title/description/image are 12 set dynamically by App.jsx's root <Helmet> instead of hardcoded here â 13 react-helmet-async can only dedupe/override tags it manages itself, so a 14 static tag here would sit alongside any page-specific one a route sets 15 (e.g. LeadTutorProfile, BlogDetail), never actually being replaced. --> 16 <meta name="author" content="Battersea House" /> 17 18 <!-- Open Graph / Facebook --> 19 <meta property="og:site_name" content="Battersea House" /> 20 21 <!-- Twitter --> 22 <meta name="twitter:card" content="summary_large_image" /> 23 <meta name="twitter:url" content="https://batterseahouse.co.uk/" /> 24 25 <!-- PWA & Mobile --> 26 <meta name="mobile-web-app-capable" content="yes" /> 27 <meta name="apple-mobile-web-app-capable" content="yes" /> 28 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> 29 <meta name="theme-color" content="#000000" /> 30 <meta name="apple-mobile-web-app-title" content="Battersea House" /> 31 32 <!-- Preload LCP hero image â responsive: browser picks the correct size from imagesrcset --> 33 <link rel="preload" as="image" fetchpriority="high" 34 imagesrcset="/images/1-768w.webp 768w, /images/1-1200w.webp 1200w, /images/1-1440w.webp 1440w, /images/1.webp 1920w" 35 imagesizes="100vw" 36 href="/images/1-768w.webp" 37 /> 38 39 <!-- Preload critical fonts (WOFF2 â 47% smaller than OTF) --> 40 <link rel="preload" href="/assets/fonts/ProximaNova-Regular.woff2" as="font" type="font/woff2" crossorigin /> 41 <link rel="preload" href="/assets/fonts/ProximaNova-Light.woff2" as="font" type="font/woff2" crossorigin /> 42 <link rel="preload" href="/assets/fonts/TrajanPro-Regular.woff2" as="font" type="font/woff2" crossorigin /> 43 44 <!-- Critical resource hints --> 45 <link rel="icon" href="/favicon.ico" /> 46 <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> 47 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> 48 <link rel="manifest" href="/manifest.json" /> 49 50 <!-- Apple Touch Icons (iOS) --> 51 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> 52 <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png" /> 53 <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png" /> 54 <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png" /> 55 56 <!-- Android/Chrome Icons (referenced in manifest.json) --> 57 <!-- Microsoft Tile Icon --> 58 <meta name="msapplication-TileImage" content="/android-chrome-192x192.png" /> 59 <meta name="msapplication-TileColor" content="#ba110b" /> 60 61 <base href="/" /> 62 63 <!-- Optimize rendering --> 64 <meta http-equiv="x-dns-prefetch-control" content="on" /> 65 66 <!-- DNS Prefetch & Preconnect for critical third-party origins --> 67 <!-- Note: fonts.googleapis.com / fonts.gstatic.com removed â fonts are self-hosted in /assets/fonts/ --> 68 <link rel="dns-prefetch" href="//www.googletagmanager.com" /> 69 <link rel="dns-prefetch" href="//accounts.google.com" /> 70 <!-- Preconnect solo para los 2 orÃgenes más crÃticos para el first paint --> 71 <link rel="preconnect" href="https://www.googletagmanager.com" crossorigin /> 72 <link rel="preconnect" href="https://api.batterseahouse.co.uk" crossorigin /> 73 74 <!-- Google Tag Manager â instalación estándar, lo más arriba posible del <head>. 75 Antes esto se diferÃa hasta window.load + requestIdleCallback: GA4 disparaba 76 'GA4 - Configuration' pero nunca el 'page_view' inicial, y Search Console veÃa 77 muchas más visitas orgánicas que GA4 (card "Fix delayed GTM loading", 2026-09). 78 El guard evita el doble-load cuando react-snap reproduce el HTML prerenderizado: 79 la foto de cada ruta ya trae el
79<script src="gtm.js">. El push de 'gtm.js' va 80 ANTES del guard â ese evento dispara GA4 y el dataLayer arranca vacÃo en cada 81 carga. Con el push después, en las páginas prerenderizadas GTM cargaba y GA4 82 no mandaba ningún page_view. --> 83
83<script> 84 (function(w,d,s,l,i){ 85 w[l]=w[l]||[]; 86 w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'}); 87 if(d.querySelector('script[src*="googletagmanager.com/gtm.js"]'))return; 88 var f=d.getElementsByTagName(s)[0], 89 j=d.createElement(s), 90 dl=l!='dataLayer'?'&l='+l:''; 91 j.async=true; 92 j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl; 93 f.parentNode.insertBefore(j,f); 94 })(window,document,'script','dataLayer','GTM-P682G3VG'); 95 </script>
95 96 <!-- End Google Tag Manager --> 97 98 <!-- DNS prefetch para orÃgenes que cargan después del first paint --> 99 <link rel="dns-prefetch" href="//www.clarity.ms" /> 100 <link rel="dns-prefetch" href="//cdn.trustindex.io" /> 101 <link rel="dns-prefetch" href="//api.batterseahouse.co.uk" /> 102 103 <!-- Title MUST be before scripts for better SEO --> 104 <title>Online Tutoring UK | Battersea House</title> 105 106 <!-- Critical CSS for above-the-fold content --> 107 <style> 108 /* Critical CSS - Base styles for immediate render */ 109 *,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::before,::after{--tw-content:''} 110 html{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"} 111 body{margin:0;line-height:inherit;font-family:"Proxima Nova",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:#fff;color:#333} 112 h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit;margin:0} 113 a{color:inherit;text-decoration:inherit} 114 button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0} 115 button,select{text-transform:none} 116 button,[type='button'],[type='reset'],[type='submit']{-webkit-appearance:button;background-color:transparent;background-image:none} 117 img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle} 118 img,video{max-width:100%;height:auto} 119 120 /* Critical layout styles */ 121 .hm-sec1{position:relative;height:668px}@media(max-width:640px){.hm-sec1{height:450px}} 122 .homebannerContent{position:absolute;inset:0;margin:auto;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;max-width:1015px;width:100%} 123 124 /* Critical typography */ 125 h1{font-size:42px;line-height:52px;font-weight:600;color:#fff}@media(max-width:640px){h1{font-size:1.25rem;line-height:1.75rem}} 126 127 /* Critical colors */ 128 .text-white{color:#fff} 129 .bg-red_900{background-color:#ba110b} 130 .text-red_900{color:#ba110b} 131 132 /* Hide until JS loads */ 133 noscript{display:block} 134 </style> 135 136 <!-- Structured Data (JSON-LD) for SEO --> 137
137<script type="application/ld+json"> 138 { 139 "@context": "https://schema.org", 140 "@type": "EducationalOrganization", 141 "name": "Battersea House", 142 "url": "https://batterseahouse.co.uk", 143 "logo": "https://batterseahouse.co.uk/images/footerlogo.svg", 144 "description": "Affordable and high-quality online tutoring in the UK. Achieve academic excellence through world-class tutors, personalised learning, and immersive technologies.", 145 "address": { 146 "@type": "PostalAddress", 147 "addressCountry": "GB", 148 "addressRegion": "England" 149 }, 150 "sameAs": [ 151 "https://www.facebook.com/batterseahouse", 152 "https://twitter.com/batterseahouse", 153 "https://www.instagram.com/batterseahouse", 154 "https://www.youtube.com/batterseahouse" 155 ], 156 "serviceType": "Online Tutoring", 157 "areaServed": "GB", 158 "offers": { 159 "@type": "Offer", 160 "category": "Educational Services" 161 } 162 } 163 </script>
163 164 165 <!-- Microsoft Clarity â deferred to idle time after load, 3s fallback â guard prevents double-load from react-snap --> 166
166<script> 167 window.addEventListener('load', function() { 168 var initClarity = function() { 169 if (!document.querySelector('script[src*="clarity.ms"]')) { 170 (function(c,l,a,r,i,t,y){ 171 c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; 172 t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; 173 y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); 174 })(window, document, "clarity", "script", "wh8of2essr"); 175 } 176 }; 177 if ('requestIdleCallback' in window) { 178 requestIdleCallback(initClarity, { timeout: 3000 }); 179 } else { 180 setTimeout(initClarity, 3000); 181 } 182 }); 183 </script>
183 184 <!-- End Microsoft Clarity --> 185 186
186<script type="module" crossorigin src="/static/js/index-BLiVic6c.js"></script>
186 187 <link rel="modulepreload" crossorigin href="/static/js/vendor-redux-Bh6RIGug.js"> 188 <link rel="modulepreload" crossorigin href="/static/js/vendor-mui-n0zgR2W0.js"> 189 <link rel="modulepreload" crossorigin href="/static/js/vendor-networking-C9Tr2w_g.js"> 190 <link rel="modulepreload" crossorigin href="/static/js/vendor-react-B2yXjvnQ.js"> 191 <link rel="stylesheet" crossorigin href="/static/css/index-DhJ8-aji.css"> 192 <link rel="manifest" href="/manifest.webmanifest">
192<script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script>
192</head> 193 <body> 194 <!-- Google Tag Manager (noscript) -->
vendor: 87 bytes, lines 194-197
194 195 <noscript> 196 <iframe 197 src="https://www.googletagmanager.com/ns.html?id=
197GTM-P682G3VG
vendor: 126 bytes, lines 197-203
197" 198 height="0" 199 width="0" 200 style="display: none; visibility: hidden" 201 ></iframe> 202 </noscript> 203
203<!-- End Google Tag Manager (noscript) --> 204 205 <noscript>You need to enable JavaScript to run this app.</noscript> 206 207 <div class="battersea-code" id="root"></div> 208 209 <!-- Vite entry point --> 210
210<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"fc2084c044674cfeafefc4a718e3e791","spa":2}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 210
210
211<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a4506f1a1fc17dd6',t:'MTc5MTA3NzU1OQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
211</body> 212</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.