1<!DOCTYPE html> 2<html lang="en" class="scroll-smooth"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <!-- page-meta:start --> 7 <title>Privacy Policy | UnitPulse</title> 8 <meta name="description" content="How UnitPulse collects, uses, and safeguards information across its websites, property listings, and the UnitPulse AI platform." /> 9 <link rel="canonical" href="https://unitpulse.ai/privacy/" /> 10 <meta property="og:type" content="website" /> 11 <meta property="og:site_name" content="UnitPulse" /> 12 <meta property="og:locale" content="en_US" /> 13 <meta property="og:url" content="https://unitpulse.ai/privacy/" /> 14 <meta property="og:title" content="Privacy Policy | UnitPulse" /> 15 <meta property="og:description" content="How UnitPulse collects, uses, and safeguards information across its websites, property listings, and the UnitPulse AI platform." /> 16 <meta property="og:image" content="https://unitpulse.ai/social/unitpulse-home-v1.png" /> 17 <meta property="og:image:type" content="image/png" /> 18 <meta property="og:image:width" content="1200" /> 19 <meta property="og:image:height" content="630" /> 20 <meta property="og:image:alt" content="UnitPulse AI agents for leasing." /> 21 <meta name="twitter:card" content="summary_large_image" /> 22 <meta name="twitter:url" content="https://unitpulse.ai/privacy/" /> 23 <meta name="twitter:title" content="Privacy Policy | UnitPulse" /> 24 <meta name="twitter:description" content="How UnitPulse collects, uses, and safeguards information across its websites, property listings, and the UnitPulse AI platform." /> 25 <meta name="twitter:image" content="https://unitpulse.ai/social/unitpulse-home-v1.png" /> 26 <meta name="twitter:image:alt" content="UnitPulse AI agents for leasing." /> 27 <!-- page-meta:end --> 28 <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 28-29
28 29 <script async src="https://www.googletagmanager.com/gtag/js?id=
29G-BGLEGBNQ3E
vendor: 16 bytes, lines 29-30
29"></script> 30
30<script> 31
vendor: 146 bytes, lines 31-35
31window.dataLayer = window.dataLayer || []; 32 function gtag(){dataLayer.push(arguments);} 33 gtag('js', new Date()); 34 35 gtag('config', '
35G-BGLEGBNQ3E
vendor: 8 bytes, lines 35-36
35'); 36
36</script>
36 37 <link rel="icon" href="https://tripalink-public.s3.us-east-2.amazonaws.com/Logo+-+Dark.png" /> 38
38<script src="https://cdn.tailwindcss.com"></script>
38 39 <link rel="preconnect" href="https://fonts.googleapis.com"> 40 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 41 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Manrope:[email protected]&family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400;1,700&family=Hanken+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet"> 42
42<script> 43 tailwind.config = { 44 theme: { 45 extend: { 46 fontFamily: { 47 // Editorial-Ink: Hanken Grotesk body/UI, Inter kept only as fallback. 48 sans: ['"Hanken Grotesk"', 'Inter', 'sans-serif'], 49 // Display serif â H1/H2, hero taglines, FAQ questions only. 400 weight. 50 display: ['"Libre Caslon Text"', 'Georgia', 'serif'], 51 // Wordmark/legacy B2B headings only â never the hero H1/H2. 52 heading: ['Manrope', 'sans-serif'], 53 }, 54 fontSize: { 55 micro: '0.6875rem', // 11px â small labels, badges, kickers 56 caption: '0.8125rem', // 13px â secondary/meta text 57 body: '0.9375rem', // 15px â editorial body 58 'body-lg': '1.0625rem', // 17px â emphasis, prices, lead-ins 59 }, 60 colors: { 61 background: '#ffffff', 62 // Do NOT add a `black` key here: it silently reassigns 63 // bg-black and breaks the dark section. Body text colour is set 64 // by the `body { color }` rule below instead. 65 primary: { 66 DEFAULT: '#1A4534', 67 hover: '#112F23', 68 soft: '#E9F0EC', 69 }, 70 // Every neutral shares the brand hue (156). A warm neutral 71 // here is a bug, not a style choice. 72 cream: '#F7FCFA', 73 hair: '#DCE6E2', 74 hairline: '#DCE6E2', 75 muted: '#4C5652', 76 // Placeholder tone: 4.71:1 on white â the rung between `muted` 77 // (too heavy, reads filled) and `faint` (fails AA for text). 78 hint: '#647870', 79 faint: '#7B8E87', 80 accent: { 81 // Sole bright accent, for dark surfaces where `primary` 82 // fails contrast. Same hue family as primary. Tuning it: 83 // saturation reads as "vivid", lightness reads as "washed 84 // out" â reach for S, not L. 85 lime: '#53C18D', 86 'lime-dark': '#3C9F71', 87 }, 88 surface: { 89 app: '#FAF9FA', 90 '2': '#EFEDEF', 91 '2-hover': '#E7E4E7', 92 // Dark sections use this, never bg-black. 93 canvas: '#0A1F15', 94 }, 95 danger: { 96 DEFAULT: '#B23A3A', 97 hover: '#8F2A24', 98 bg: '#F5DADA', 99 ring: 'rgba(178, 58, 58, 0.20)', 100 }, 101 // No `success` token on purpose: in a green-branded system it 102 // would be indistinguishable from the brand. Success states use 103 // primary-soft/primary. 104 }, 105 borderRadius: { 106 card: '20px', 107 }, 108 boxShadow: { 109 // Ink-tinted, not neutral black. Default surfaces stay flat 110 // (border-hair, no shadow); apply on hover/floating only. 111 soft: '0 2px 6px rgba(26, 69, 52, 0.06), 0 14px 30px -12px rgba(26, 69, 52, 0.10)', 112 lifted: '0 4px 10px rgba(26, 69, 52, 0.08), 0 22px 44px -14px rgba(26, 69, 52, 0.18)', 113 }, 114 animation: { 115 'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite', 116 'float': 'float 6s ease-in-out infinite', 117 'fade-in-up': 'fadeInUp 0.5s ease-out forwards', 118 'fade-in': 'fadeIn 0.8s ease-out forwards', 119 // Slow ambient glow breathing behind the FeatureCanvas 120 // dark section â scale + opacity, not layout properties. 121 'aura': 'auraPulse 8s ease-in-out infinite', 122 }, 123 keyframes: { 124 float: { 125 '0%, 100%': { transform: 'translateY(0)' }, 126 '50%': { transform: 'translateY(-20px)' }, 127 }, 128 fadeInUp: { 129 '0%': { opacity: '0', transform: 'translateY(20px)' }, 130 // Ends at 'none', not a retained translateY(0) â a 131 // retained transform makes the element a containing
132 // block and silently breaks descendant backdrop-filter. 133 '100%': { opacity: '1', transform: 'none' }, 134 }, 135 fadeIn: { 136 '0%': { opacity: '0' }, 137 '100%': { opacity: '1' }, 138 }, 139 auraPulse: { 140 '0%, 100%': { transform: 'scale(1)', opacity: '0.35' }, 141 '50%': { transform: 'scale(1.15)', opacity: '0.6' }, 142 }, 143 } 144 }, 145 }, 146 } 147 </script>
147 148 <style> 149 :root { 150 --primary: #1A4534; 151 --primary-rgb: 26, 69, 52; 152 --primary-hover: #112F23; 153 --primary-soft: #E9F0EC; 154 --cream: #F7FCFA; 155 --hair: #DCE6E2; 156 --muted: #4C5652; 157 --hint: #647870; 158 --faint: #7B8E87; 159 --accent-lime: #53C18D; 160 --accent-lime-rgb: 83, 193, 141; 161 --accent-lime-dark: #3C9F71; 162 --surface-app: #FAF9FA; 163 --surface-2: #EFEDEF; 164 --surface-canvas: #0A1F15; 165 } 166 body { 167 background-color: var(--surface-app); 168 color: #171717; 169 overflow-x: hidden; 170 /* Line Grid Pattern - Made extremely subtle (2% opacity black) */ 171 background-image: linear-gradient(rgba(0, 0, 0, 0.02) 1px, transparent 1px), 172 linear-gradient(90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px); 173 background-size: 40px 40px; 174 } 175 /* Page-level scrollbar â matches the listing partner page. */ 176 ::-webkit-scrollbar { 177 width: 6px; 178 } 179 ::-webkit-scrollbar-track { 180 background: transparent; 181 } 182 ::-webkit-scrollbar-thumb { 183 background: #000000; 184 border-radius: 10px; 185 } 186 ::-webkit-scrollbar-thumb:hover { 187 background: #333333; 188 } 189 /* Hide scrollbar utility (used by horizontal carousels). */ 190 .scrollbar-hide::-webkit-scrollbar {
191 display: none; 192 } 193 .scrollbar-hide { 194 -ms-overflow-style: none; 195 scrollbar-width: none; 196 } 197 /* iOS Safari zooms in â and never back out â when a field smaller than 198 16px takes focus. Every form control here is text-sm (14px), so this 199 guard is load-bearing, not defensive. */ 200 @media (max-width: 767px) { 201 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]), 202 textarea, 203 select { 204 font-size: 16px !important; 205 } 206 } 207 @media (prefers-reduced-motion: reduce) { 208 *, *::before, *::after { 209 animation-duration: 0.01ms !important; 210 animation-iteration-count: 1 !important; 211 transition-duration: 0.01ms !important; 212 /* Zeroing duration alone is not enough: a 500ms transition-delay 213 with a 0.01ms duration still leaves the element in its "before" 214 state (often opacity:0) for half a second, then snaps. FadeIn 215 stacks delays up to 500ms and the pipeline up to 2100ms, so 216 without this the page reads as broken rather than as still. */ 217 transition-delay: 0ms !important; 218 animation-delay: 0ms !important; 219 } 220 /* <html class="scroll-smooth"> sets scroll-behavior:smooth, and a JS 221 `behavior:"auto"` resolves to THAT computed value rather than 222 jumping â so anchor navigation stayed smooth without this. */ 223 html, body, * { 224 scroll-behavior: auto !important; 225 } 226 } 227 </style> 228 229
229<script type="importmap"> 230{ 231 "imports": { 232 "react/": "https://esm.sh/react@^19.2.3/", 233 "react": "https://esm.sh/react@^19.2.3", 234 "react-dom/": "https://esm.sh/react-dom@^19.2.3/", 235 "lucide-react": "https://esm.sh/lucide-react@^0.561.0" 236 } 237} 238</script>
238 239
239<script type="module" crossorigin src="/assets/index-DjFc12ua.js"></script>
239 240 <link rel="stylesheet" crossorigin href="/assets/index-YzFKXkEb.css"> 241</head> 242 <body> 243 <div id="root"> 244 <!-- Crawler-visible fallback. React (createRoot) replaces the contents of 245 #root on mount, so real visitors never see this. It exists purely so 246 that clients which do not execute JavaScript â the Google OAuth 247 brand-verification reviewer in particular â can read what the app does 248 and reach the Privacy Policy / Terms without running the SPA. Keep the 249 legal links as absolute URLs so they resolve from the raw HTML. 250 This block is the default (homepage) fallback; scripts/build-page-html.mjs 251 swaps in a per-route fallback between the markers below for any route 252 that declares `bodyFallback` in seo/page-meta.js (e.g. /privacy). --> 253 <!-- crawler-fallback:start --> 254 <main> 255 <h1>Privacy Policy â Google Account and Google Calendar Data</h1> 256 <p>When you sign in with Google, UnitPulse receives your Google account identifier, name and email address, used only to verify your identity and to create or associate your UnitPulse account. UP Leasing also offers an optional Google Calendar integration so that property tours scheduled in UnitPulse appear on a leasing agent's own calendar; it is off by default and is enabled only when an individual agent grants consent on Google's OAuth screen.</p> 257 <p>Scopes we request: https://www.googleapis.com/auth/calendar.freebusy (read free/busy time ranges on the agent's calendars, to offer only times the agent is available and avoid double-booking) and https://www.googleapis.com/auth/calendar.events.owned (view, create, change and delete events on calendars the agent owns, to create, update, cancel and reassign tour appointments). We do not request calendar or calendar.events, so we can never read from or write to a calendar the agent does not own. We do not request calendar.readonly, and we request no access to Gmail, Drive, Contacts or any other Google service.</p> 258 <p>We read free/busy ranges, create a calendar event when a tour is booked, update or delete it when the tour is rescheduled or cancelled, read events in a bounded time window to locate the tour appointments we previously created, and move a tour appointment to another agent's calendar when a lead is reassigned. Every event UnitPulse creates is stamped with a UnitPulse identifier; we read and modify only events carrying that identifier, and never modify, move or delete events UnitPulse did not create.</p> 259 <p>A tour appointment carries the date and time, the property address, the unit, and the prospect's name, email and phone in the event description. Where the prospect's email address is known, they are added as an attendee. We store OAuth refresh and access tokens (encrypted at rest) and the connected Google account and calendar identifiers while the integration is connected. We cache, in Redis, a copy of the UnitPulse tour appointments on the agent's calendar and a map of our tour identifiers to Google event identifiers; both expire automatically after 20 days. We do not store the agent's free/busy data or any event UnitPulse did not create.</p> 260 <p>We do not use Google user data to train, improve or fine-tune any AI or machine learning model. We do not sell, rent or trade Google user data, and we do not use it for advertising, marketing or profiling. You can revoke UnitPulse's access at any time from inside UnitPulse or from your Google Account at https://myaccount.google.com/permissions.</p> 261 <p>Limited Use: UnitPulse AI, Inc.'s use and transfer to any other app of information received from Google APIs will adhere to the Google API Services User Data Policy (https://developers.google.com/terms/api-services-user-data-policy), including the Limited Use requirements.</p> 262 </main> 263 <footer> 264 <a href="https://unitpulse.ai/privacy">Privacy Policy</a> 265 <a href="https://unitpulse.ai/terms">Terms of Service</a> 266 </footer> 267 <!-- crawler-fallback:end --> 268 </div>
269<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:'a45cab7a7d5a0e4c',t:'MTc5MTIwNTg2MQ=='};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>
269</body> 270</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.