1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8" /> 6 <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 7 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 8 <meta name="theme-color" content="#000000" /> 9 <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" /> 10 <link rel="apple-touch-icon" href="/vite.svg" /> 11 12 <!-- Primary Meta Tags --> 13 <title>Raptor The Luxury Brand | Premium Eaves, Window & Gutter Cleaning in the GTA</title> 14 <meta name="title" content="Raptor The Luxury Brand | Premium Eaves, Window & Gutter Cleaning in the GTA" /> 15 <meta name="description" content="Raptor The Luxury Brand â the No.1 luxury home service brand in North America. Professional eaves cleaning, window cleaning, eavestrough cleaning, power washing & premium gutter protection across Hamilton, Ancaster, Burlington, Oakville, Milton, Mississauga & the GTA." /> 16 <meta name="keywords" content="eaves cleaning, window cleaning, eavestrough cleaning, gutter cleaning, power washing, gutter protection, eaves guards, Alu-Rex, Hamilton, Ancaster, Burlington, Oakville, Milton, Mississauga, Toronto, GTA, Raptor The Luxury Brand" /> 17 <meta name="author" content="Raptor The Luxury Brand" /> 18 <meta name="rating" content="general" /> 19 <meta name="distribution" content="global" /> 20 <meta name="revisit-after" content="7 days" /> 21 22 <!-- Geo Tags --> 23 <meta name="geo.region" content="CA-ON" /> 24 <meta name="geo.placename" content="Hamilton, Ontario" /> 25 <meta name="geo.position" content="43.2557;-79.8711" /> 26 <meta name="ICBM" content="43.2557, -79.8711" /> 27 28 <!-- Open Graph / Facebook --> 29 <meta property="og:type" content="website" /> 30 <meta property="og:url" content="https://raptorscleaning.com/" /> 31 <meta property="og:title" content="Raptor The Luxury Brand | Premium Eaves, Window & Gutter Cleaning" /> 32 <meta property="og:description" content="The No.1 Luxury Service Brand in North America. Professional eaves cleaning, window cleaning, eavestrough cleaning, power washing & premium gutter protection across Hamilton, Ancaster & the GTA." /> 33 <meta property="og:image" content="https://horizons-cdn.hostinger.com/fb7f01d3-cba8-4a66-a6db-4f79ac30e95a/5efa23145623c56aa0f185e3c1777171.png" /> 34 <meta property="og:site_name" content="Raptor The Luxury Brand" /> 35 <meta property="og:locale" content="en_CA" /> 36 37 <!-- Twitter --> 38 <meta name="twitter:card" content="summary_large_image" /> 39 <meta name="twitter:url" content="https://raptorscleaning.com/" /> 40 <meta name="twitter:title" content="Raptor The Luxury Brand | Premium Eaves, Window & Gutter Cleaning" /> 41 <meta name="twitter:description" content="The No.1 Luxury Service Brand in North America. Professional cleaning & gutter protection across Hamilton, Ancaster & the GTA." /> 42 <meta name="twitter:image" content="https://horizons-cdn.hostinger.com/fb7f01d3-cba8-4a66-a6db-4f79ac30e95a/5efa23145623c56aa0f185e3c1777171.png" /> 43 44 <!-- Canonical --> 45 <link rel="canonical" href="https://raptorscleaning.com/" /> 46 47 <!-- Google Fonts Preconnect --> 48 <link rel="preconnect" href="https://fonts.googleapis.com"> 49 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 50 51 <!-- Bebas Neue (Headings) & DM Sans (Body) --> 52 <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:opsz,[email protected],400;500;700&display=swap" rel="stylesheet"> 53 54 <!-- Organization + WebSite Structured Data --> 55
55<script type="application/ld+json"> 56 { 57 "@context": "https://schema.org", 58 "@graph": [ 59 { 60 "@type": "Organization", 61 "@id": "https://raptorscleaning.com/#organization", 62 "name": "Raptor The Luxury Brand", 63 "alternateName": "Raptors Cleaning", 64 "url": "https://raptorscleaning.com/", 65 "logo": "https://horizons-cdn.hostinger.com/fb7f01d3-cba8-4a66-a6db-4f79ac30e95a/5efa23145623c56aa0f185e3c1777171.png", 66 "image": "https://horizons-cdn.hostinger.com/fb7f01d3-cba8-4a66-a6db-4f79ac30e95a/5efa23145623c56aa0f185e3c1777171.png", 67 "telephone": "+1-437-335-5568", 68 "email": "[email protected]", 69 "address": { 70 "@type": "PostalAddress", 71 "streetAddress": "542 Appleby Line", 72 "addressLocality": "Burlington", 73 "addressRegion": "ON", 74 "postalCode": "L7L 2Y3", 75 "addressCountry": "CA" 76 }, 77 "geo": { 78 "@type": "GeoCoordinates", 79 "latitude": 43.3856, 80 "longitude": -79.7586 81 }, 82 "areaServed": [ 83 { "@type": "City", "name": "Burlington" }, 84 { "@type": "City", "name": "Oakville" }, 85 { "@type": "City", "name": "Milton" }, 86 { "@type": "City", "name": "Mississauga" }, 87 { "@type": "City", "name": "Hamilton" }, 88 { "@type": "City", "name": "Ancaster" }, 89 { "@type": "City", "name": "Brampton" }, 90 { "@type": "City", "name": "Toronto" }, 91 { "@type": "City", "name": "Markham" }, 92 { "@type": "City", "name": "Richmond Hill" }, 93 { "@type": "City", "name": "Vaughan" }, 94 { "@type": "City", "name": "Newmarket" }, 95 { "@type": "City", "name": "Aurora" }, 96 { "@type": "City", "name": "Whitby" }, 97 { "@type": "City", "name": "Oshawa" }, 98 { "@type": "City", "name": "Ajax" }, 99 { "@type": "City", "name": "Pickering" }, 100 { "@type": "City", "name": "Scarborough" } 101 ], 102 "contactPoint": { 103 "@type": "ContactPoint", 104 "telephone": "+1-437-335-5568", 105 "contactType": "customer service", 106 "areaServed": "CA", 107 "availableLanguage": ["en"] 108 }, 109 "sameAs": [ 110 "https://www.facebook.com/raptorscleaning", 111 "https://www.instagram.com/raptorscleaning" 112 ] 113 }, 114 { 115 "@type": "WebSite", 116 "@id": "https://raptorscleaning.com/#website", 117 "url": "https://raptorscleaning.com/", 118 "name": "Raptor The Luxury Brand", 119 "publisher": { "@id": "https://raptorscleaning.com/#organization" }, 120 "inLanguage": "en-CA" 121 } 122 ] 123 } 124 </script>
124 125 126 <style> 127 /* Critical CSS for immediate font loading */ 128 body { 129 /* Fallback stack ensures content is visible immediately */ 130 font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; 131 -webkit-font-smoothing: antialiased; 132 -moz-osx-font-smoothing: grayscale; 133 } 134 135 h1, h2, h3, h4, h5, h6 { 136 font-family: "Bebas Neue", sans-serif; 137 } 138 </style> 139
139<script type="module" crossorigin src="/assets/index-Ow5fMCIk.js"></script>
139 140 <link rel="stylesheet" crossorigin href="/assets/index-p12-Pzhq.css"> 141
141<script type="module"> 142window.onerror = (message, source, lineno, colno, errorObj) => { 143 const errorDetails = errorObj ? JSON.stringify({ 144 name: errorObj.name, 145 message: errorObj.message, 146 stack: errorObj.stack, 147 source, 148 lineno, 149 colno, 150 }) : null; 151 152 window.parent.postMessage({ 153 type: 'horizons-runtime-error', 154 message, 155 error: errorDetails 156 }, '*'); 157}; 158</script>
158 159
159<script type="module"> 160const observer = new MutationObserver((mutations) => { 161 for (const mutation of mutations) { 162 for (const addedNode of mutation.addedNodes) { 163 if ( 164 addedNode.nodeType === Node.ELEMENT_NODE && 165 ( 166 addedNode.tagName?.toLowerCase() === 'vite-error-overlay' || 167 addedNode.classList?.contains('backdrop') 168 ) 169 ) { 170 handleViteOverlay(addedNode); 171 } 172 } 173 } 174}); 175 176observer.observe(document.documentElement, { 177 childList: true, 178 subtree: true 179}); 180 181function handleViteOverlay(node) { 182 if (!node.shadowRoot) { 183 return; 184 } 185 186 const backdrop = node.shadowRoot.querySelector('.backdrop'); 187 188 if (backdrop) { 189 const overlayHtml = backdrop.outerHTML; 190 const parser = new DOMParser(); 191 const doc = parser.parseFromString(overlayHtml, 'text/html'); 192 const messageBodyElement = doc.querySelector('.message-body'); 193 const fileElement = doc.querySelector('.file'); 194 const messageText = messageBodyElement ? messageBodyElement.textContent.trim() : ''; 195 const fileText = fileElement ? fileElement.textContent.trim() : ''; 196 const error = messageText + (fileText ? ' File:' + fileText : ''); 197 198 window.parent.postMessage({ 199 type: 'horizons-vite-error', 200 error, 201 }, '*'); 202 } 203} 204</script>
204 205
205<script type="module"> 206const originalConsoleError = console.error; 207const MATCH_LINE_COL_REGEX = /:(\d+):(\d+)\)?\s*$/; // regex to match the :lineNum:colNum 208const MATCH_AT_REGEX = /^\s*at\s+(?:async\s+)?(?:.*?\s+)?\(?/; // regex to remove the 'at' keyword and any 'async' or function name 209const MATCH_PATH_REGEX = /^\//; // regex to remove the leading slash 210 211function parseStackFrameLine(line) { 212 const lineColMatch = line.match(MATCH_LINE_COL_REGEX); 213 if (!lineColMatch) return null; 214 const [, lineNum, colNum] = lineColMatch; 215 const suffix = `:${lineNum}:${colNum}`; 216 const idx = line.lastIndexOf(suffix); 217 if (idx === -1) return null; 218 const before = line.substring(0, idx); 219 const path = before.replace(MATCH_AT_REGEX, '').trim(); 220 221 if (!path) return null; 222 223 try { 224 const pathname = new URL(path).pathname; 225 const filePath = pathname.replace(MATCH_PATH_REGEX, '') || pathname; 226 return `${filePath}:${lineNum}:${colNum}`; 227 } catch (e) { 228 const filePath = path.replace(MATCH_PATH_REGEX, '') || path; 229 return `${filePath}:${lineNum}:${colNum}`; 230 } 231} 232 233function getFilePathFromStack(stack, skipFrames = 0) { 234 if (!stack || typeof stack !== 'string') return null; 235 const lines = stack.split('\n').slice(1); 236 237 const frames = lines.map(line => parseStackFrameLine(line.replace(/\r$/, ''))).filter(Boolean); 238 239 return frames[skipFrames] ?? null; 240} 241 242function formatConsoleMessage(args, skipStackFrames = 1) { 243 let messageString = ''; 244 let filePath = null; 245 246 for (let i = 0; i < args.length; i++) { 247 const arg = args[i]; 248 if (arg instanceof Error) { 249 filePath = getFilePathFromStack(arg.stack, 0); 250 messageString = `${arg.name}: ${arg.message}`; 251 if (filePath) { 252 messageString = `${messageString} at ${filePath}`; 253 } 254 break; 255 } 256 } 257 258 if (!messageString) { 259 messageString = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '); 260 const stack = new Error().stack; 261 filePath = getFilePathFromStack(stack, skipStackFrames); 262 if (filePath) { 263 messageString = `${messageString} at ${filePath}`; 264 } 265 } 266 267 return messageString; 268} 269 270console.error = function(...args) { 271 originalConsoleError.apply(console, args); 272 273 window.parent.postMessage({ 274 type: 'horizons-console-error', 275 error: formatConsoleMessage(args, 1) 276 }, '*'); 277}; 278 279const originalConsoleWarn = console.warn; 280 281console.warn = function(...args) { 282 originalConsoleWarn.apply(console, args); 283 284 window.parent.postMessage({ 285 type: 'horizons-console-warn', 286 warning: formatConsoleMessage(args, 1) 287 }, '*'); 288}; 289</script>
289 290
290<script type="module"> 291// Prevents authentication failures (PB returns 400) from crashing the app â use console.info, not console.error/PM2. 292// [urlPattern, bodyPattern] tuples; align with sandboxErrorUtils ERROR_BLACKLIST. 293const BENIGN_FETCH_ERRORS = [ 294 [/hcgi\/platform\/api\/collections\/.*auth-with-password.*/i, /Failed to authenticate/i], 295 [/hcgi\/api\//i, /Insufficient credits/i], 296 // A user-supplied integration secret (Stripe/PayPal/Twilio/...) is not set yet â an expected setup state, not a bug to fix. 297 [/hcgi\/api\//i, /INTEGRATION_NOT_CONFIGURED/i], 298]; 299 300function isBenignFetchError(url, body) { 301 return BENIGN_FETCH_ERRORS.some(([urlPattern, bodyPattern]) => 302 urlPattern.test(url) && (!bodyPattern || bodyPattern.test(body))); 303} 304 305const PLATFORM_URL_PATTERN = /hcgi\/platform\//i; 306const VALIDATION_CODE_TEXT_PATTERN = /validation_/; 307 308function hasValidationCode(value) { 309 if (value == null) { 310 return false; 311 } 312 if (typeof value === 'string') { 313 return value.startsWith('validation_'); 314 } 315 if (Array.isArray(value)) { 316 return value.some(hasValidationCode); 317 } 318 if (typeof value === 'object') { 319 return Object.values(value).some(hasValidationCode); 320 } 321 return false; 322} 323 324function isValidationFetchWarning(url, body) { 325 if (!PLATFORM_URL_PATTERN.test(url)) { 326 return false; 327 } 328 try { 329 return hasValidationCode(JSON.parse(body)); 330 } catch { 331 return VALIDATION_CODE_TEXT_PATTERN.test(body); 332 } 333} 334 335const originalFetch = window.fetch; 336 337window.fetch = function(...args) { 338 const url = args[0] instanceof Request ? args[0].url : args[0]; 339 340 // Skip WebSocket URLs 341 if (url.startsWith('ws:') || url.startsWith('wss:')) { 342 return originalFetch.apply(this, args); 343 } 344 345 return originalFetch.apply(this, args) 346 .then(async response => { 347 const contentType = response.headers.get('Content-Type') || ''; 348 349 // Exclude HTML document responses 350 const isDocumentResponse = 351 contentType.includes('text/html') || 352 contentType.includes('application/xhtml+xml'); 353 354 // no-cors requests (e.g. Google Analytics/Ads beacons) always return an opaque 355 const isOpaqueResponse = response.type === 'opaque'; 356 357 if (!response.ok && !isDocumentResponse && !isOpaqueResponse) { 358 const responseClone = response.clone(); 359 const errorFromRes = await responseClone.text(); 360 const requestUrl = response.url; 361 const errorMessage = `Fetch error from ${requestUrl}: ${errorFromRes}`; 362 363 if (isBenignFetchError(requestUrl, errorFromRes)) { 364 console.info(errorMessage); 365 } else if (isValidationFetchWarning(requestUrl, errorFromRes)) { 366 console.warn(errorMessage); 367 } else { 368 console.error(errorMessage); 369 } 370 } 371 372 return response; 373 }) 374 .catch(error => { 375 if (!url.match(/.html?$/i)) { 376 // Cancelled in-flight fetch (e.g. preview reload) â not a server failure. 377 if (error?.name === 'AbortError') { 378 console.info(error); 379 } else { 380 console.error(error); 381 } 382 } 383 384 throw error; 385 }); 386}; 387</script>
387 388
388<script type="module"> 389if (window.navigation && window.self !== window.top) { 390 window.navigation.addEventListener('navigate', (event) => { 391 const url = event.destination.url; 392 393 try { 394 const destinationUrl = new URL(url); 395 const destinationOrigin = destinationUrl.origin; 396 const currentOrigin = window.location.origin; 397 398 if (destinationOrigin === currentOrigin) { 399 return; 400 } 401 } catch (error) { 402 return; 403 } 404 405 window.parent.postMessage({ 406 type: 'horizons-navigation-error', 407 url, 408 }, '*'); 409 }); 410} 411</script>
411 412</head> 413 414<body> 415 <div id="root"></div> 416</body> 417 418</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.