PageSourceSearch

https://unitpulse.ai/privacy

html unitpulse.ai collected 2026-10-05 13:11:01 UTC 15,831 bytes, 270 lines download raw bytes

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.