PageSourceSearch

https://www.orwellsbaddream.com/privacy

html orwellsbaddream.com collected 2026-09-24 14:49:25 UTC 147,096 bytes, 1,800 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>Orwell's bad dream</title>
7<link rel="icon" type="image/png" href="/favicon.png">
8<link rel="apple-touch-icon" href="/favicon.png">
9<!-- Social share preview: just the eye + one cryptic line. No brand name,
10     no description — keep the unfurl mysterious. -->
11<meta property="og:type" content="website">
12<meta property="og:url" content="https://orwellsbaddream.com/">
13<meta property="og:title" content="Communicate by seeing">
14<meta property="og:image" content="https://orwellsbaddream.com/favicon.png">
15<meta property="og:image:type" content="image/png">
16<meta property="og:image:width" content="512">
17<meta property="og:image:height" content="512">
18<meta property="og:image:alt" content="An eye">
19<meta name="twitter:card" content="summary">
20<meta name="twitter:title" content="Communicate by seeing">
21<meta name="twitter:image" content="https://orwellsbaddream.com/favicon.png">
22<meta name="twitter:image:alt" content="An eye">
23<link rel="preconnect" href="https://fonts.googleapis.com">
24<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Silkscreen:wght@400;700&display=swap" rel="stylesheet">
25<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
25
26<!-- Microsoft Authentication Library — interactive sign-in for the
27     Excel integration's File Picker. ~150KB but only used on the
28     "Connect Excel" flow; idle elsewhere. -->
29<script src="https://cdn.jsdelivr.net/npm/@azure/msal-browser@3/lib/msal-browser.min.js"></script>
29
30<!-- Config loaded from env-generated file — credentials never in git -->
31<script src="/config.js"></script>
31
32<!-- Blocking, in <head>: a theme applied after first paint is a flash. -->
33<script src="/app-theme.js"></script>
33
34<!-- Shared match-scoring (single source of truth, mirrored by the extension) -->
35<script src="/match-scoring.js"></script>
35
36<!-- Shared marking icon set (single source of truth, mirrored by the extension) -->
37<script src="/marking-icons.js"></script>
37
38<link rel="stylesheet" href="/styles.css">
39  
39<script src="/boot-auth-guard.js"></script>
39
40</head>
41<body>
42
43
44<!-- LANDING / AUTH — animated pixel eyes + LOGIN button.
45     No default `active` class: navigate() / renderRoute() controls
46     visibility, so a hard refresh on a signed-in account doesn't
47     paint landing for ~50ms before auth resolves and routes us
48     elsewhere. -->
49<div id="page-landing" class="page">
50  <!-- The pixels the eyes throw off. Fixed rather than absolute, so the field
51       persists across the whole page instead of scrolling away with the
52       sign-in band. Filled by app-landing.js; empty and invisible until then,
53       and stays empty under prefers-reduced-motion. -->
54  <div class="cph-dust" id="cph-dust" aria-hidden="true"></div>
55
56  <!-- The first screen is its own 100vh block so the legal line can sit at
57       the bottom of IT rather than the bottom of the viewport. It used to be
58       position:fixed, which was invisible while the page could not scroll and
59       would have floated over the gif the moment it could. -->
60  <div class="cph-screen">
61  <div class="cph-container">
62    <div class="cph-eyes">
63      <div class="cph-eye" id="cph-eye-left"></div>
64      <div class="cph-eye" id="cph-eye-right"></div>
65    </div>
66    <button class="cph-login-btn" id="linkedin-btn" data-ot-h="h1" aria-label="Sign in">
67      <div class="cph-login-grid" id="cph-login-grid"></div>
68    </button>
69    <div id="auth-error" class="cph-error hidden"></div>
70  </div>
71  <div class="cph-legal" aria-label="Legal">
72    <a href="/terms">Terms of Use</a>
73    <span style="opacity:.35">·</span>
74    <a href="#" class="cph-about-link" data-ot-h="h2">What is Orwell's bad dream?</a>
75    <span style="opacity:.35">·</span>
76    <a href="/privacy">Privacy Policy</a>
77  </div>
78  </div>
79
80  <!-- No autoplay attribute: app-landing.js starts it when it scrolls into
81       view and pauses it when it leaves, so it isn't decoding 1080p behind
82       the sign-in screen for someone who never scrolls. preload="none" means
83       the 19MB isn't fetched until then either.
84       muted + playsinline stay — both are required or iOS Safari refuses the
85       scripted play() just as it refuses autoplay. -->
86  <div class="cph-demo">
87    <video id="cph-demo-video" src="/orwell-demo.mp4" muted loop playsinline preload="none"
88           width="1920" height="1080" aria-label="Product demo"></video>
89  </div>
90
91  <!-- PRICING. The Team figure is fetched from /api/stripe/price, which
92       resolves the product's current default price — the same source
93       checkout.js bills against, so the page and the till cannot disagree.
94       Hardcoding it here would break the "Stripe is the single place pricing
95       lives" property that checkout.js:27 goes out of its way to keep.
96       Everything else states the model as the billing code implements it:
97       free while solo, per active seat from the second member
98       (app-team.js:2448), read-only rather than deleted when a team lapses
99       (team_writable). -->
100  <div class="cph-pricing">
101    <h2 class="cph-pricing-title">Pricing</h2>
102
103    <div class="cph-plans">
104
105      <div class="cph-plan">
106        <div class="cph-plan-head">
107          <div class="cph-plan-name">Solo</div>
108          <div class="cph-plan-price">Free<span class="cph-plan-per">no card, no trial clock</span></div>
109        </div>
110        <p class="cph-plan-body">The whole product, for one person.</p>
111        <div class="cph-feats-label">What you get</div>
112        <ul class="cph-feats">
113          <li>Extension on LinkedIn, Gmail and Outlook</li>
114          <li>Attio, Excel, Google&nbsp;Sheets, HubSpot and Luma</li>
115          <li>Every person you touch in one searchable list</li>
116          <li>Pipeline stages you name yourself</li>
117          <li>CSV import and export</li>
118        </ul>
119        <button type="button" class="cph-plan-btn" data-ot-h="h1">Start free</button>
120      </div>
121
122      <div class="cph-plan cph-plan-main">
123        <div class="cph-plan-badge">Most teams</div>
124        <div class="cph-plan-head">
125          <div class="cph-plan-name">Team</div>
126          <div class="cph-plan-price"><span id="cph-price-amount">&mdash;</span><span class="cph-plan-per" id="cph-price-per">per seat</span></div>
127        </div>
128        <p class="cph-plan-body">From your second member on. The part that stops the double-message.</p>
129        <div class="cph-feats-label">Everything in Solo, plus</div>
130        <ul class="cph-feats">
131          <li>One shared history &mdash; see who already reached out, and when</li>
132          <li>Notes and comments on every profile</li>
133          <li>Tag a teammate by name, they get the notification</li>
134          <li>Shared lists and a shared pipeline</li>
135          <li>Every active member is one seat. Remove them, the seat goes too</li>
136        </ul>
137        <!-- Also the auth modal, and it cannot be anything else: checkout is
138             POST /api/stripe/checkout { team_id } behind requireTeamAdmin, so
139             paying needs an account, a team, and admin of it, in that order.
140             A "Pay now" button here would have nothing to call. -->
141        <button type="button" class="cph-plan-btn" data-ot-h="h1">Create a team</button>
142      </div>
143
144      <div class="cph-plan">
145        <div class="cph-plan-head">
146          <div class="cph-plan-name">Enterprise</div>
147          <div class="cph-plan-price">Let's talk<span class="cph-plan-per">for bigger teams</span></div>
148        </div>
149        <p class="cph-plan-body">When procurement gets involved, or the self-serve plan doesn't fit.</p>
150        <div class="cph-feats-label">Everything in Team, plus</div>
151        <ul class="cph-feats">
152          <li>Volume pricing</li>
153          <li>Invoicing instead of a card</li>
154          <li>A direct line to the people who build it</li>
155        </ul>
156        <a class="cph-plan-btn" href="mailto:[email protected]?subject=Enterprise%20plan">Talk to us</a>
157      </div>
158
159    </div>
160
161    <p class="cph-pricing-foot">
162      <span>Cancel whenever you like</span>
163      <span>Data stays inside your team</span>
164    </p>
165  </div>
166</div>
167</div>
168
169
170<!-- DASHBOARD -->
171<div id="page-dashboard" class="page">
172  <div class="container">
173    <!-- Home's camera hangs on the right WALL — the window's edge, not the
174         container's — and looks left, across the greeting it shares a line
175         with. pixel-eye.js draws the source art for this one rather than the
176         mirror it uses on the left. -->
177    <div class="pixel-eye-host is-home" data-facing="left"></div>
178    <div class="dash-hero">
179      <button class="dash-hero-btn" data-ot-h="h3" aria-label="Open account">
180        <div class="dash-hero-avatar" id="dash-hero-avatar">—</div>
181        <div class="dash-hero-text">
182          <h2 id="dash-hero-greeting" class="dash-hero-greeting-row">
183            <span id="dash-hero-greeting-text">Welcome back</span>
184            <svg viewBox="0 0 16 16" fill="none" width="14" height="14" aria-hidden="true"><path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
185          </h2>
186          <p id="dash-hero-sub">Track outreach with your team</p>
187        </div>
188      </button>
189    </div>
190
191    <div id="linkedin-section"></div>
192
193    <div class="dash-stats hidden" id="dash-stats">
194      <div class="dash-stat">
195        <div class="dash-stat-label">Pending requests</div>
196        <div class="dash-stat-value" id="stat-pending">—</div>
197        <div class="dash-stat-sub" id="stat-pending-sub">Waiting on approval</div>
198      </div>
199    </div>
200
201    <!-- Above the teams, not under them: the extension is what makes the
202         teams fill up, so it shouldn't sit below a grid you have to scroll
203         past — the Create team card is in that grid. -->
204    <div class="dash-ext-cta">
205      <a href="https://chromewebstore.google.com/detail/orwells-bad-dream/fjahimldocnmhhalgbnjmdgdlkmmobhk" target="_blank" rel="noopener" class="dash-ext-download-btn" aria-label="Install the Chrome extension">Download the extension</a>
206      <button class="dash-ext-setup-btn" data-ot-h="h4">Setup guide</button>
207    </div>
208
209    <div class="dash-section">
210      <div id="teams-list"></div>
211    </div>
212
213  </div>
214</div>
215
216<!-- SETTINGS -->
217<div id="page-settings" class="page">
218  <div class="container account-shell">
219    <a href="#" id="settings-back" class="profile-back-link" data-ot-h="h5">&larr; Back</a>
220    <h1 class="ws-title account-title">Account</h1>
221
222    <!-- Who you are. The avatar and the two fields that name you, in one
223         card, because they are all answers to the same question. -->
224    <h2 class="account-section-head">Profile</h2>
225    <div class="settings-card">
226      <div class="account-identity">
227        <div class="profile-avatar is-self" id="settings-avatar" title="Change profile picture">—</div>
228        <div class="account-identity-text">
229          <div id="settings-display-name" class="account-identity-name">—</div>
230          <div id="settings-email" class="account-identity-email">—</div>
231        </div>
232        <!-- Theme sits with the person it belongs to, not in a section of its
233             own explaining that it belongs to them. -->
234        <div class="theme-choice" role="radiogroup" aria-label="Theme">
235          <button type="button" class="theme-opt" data-theme-choice="dark" role="radio" aria-checked="true" data-ot-h="h6"><span class="theme-swatch is-dark" aria-hidden="true"></span>Dark</button>
236          <button type="button" class="theme-opt" data-theme-choice="light" role="radio" aria-checked="false" data-ot-h="h7"><span class="theme-swatch is-light" aria-hidden="true"></span>Light</button>
237        </div>
238      </div>
239      <input type="file" id="profile-avatar-file" accept="image/png,image/jpeg,image/webp" style="display:none" data-ot-h="h8">
240      <div class="settings-row">
241        <div class="settings-row-label">Name</div>
242        <div class="settings-row-value account-field">
243          <input type="text" id="settings-name-input" class="form-input" maxlength="80" placeholder="Your name">
244          <button class="btn btn-sm btn-secondary" id="settings-name-save" data-ot-h="h9">Save</button>
245        </div>
246      </div>
247      <!-- The handle column stays (NOT NULL, auto-assigned) but has no UI:
248           mentions are written by name now, so there is nothing to pick and
249           nothing to memorise. -->
250      <p class="account-hint">Teammates tag you by name in a comment or an outreach note, and it lands in your notifications.</p>
251    </div>
252
253    <!-- The page's behaviour changes with which of these exist — whether you
254         can change your email, whether there's a password to change — so it
255         says which are attached instead of leaving you to infer it. -->
256    <h2 class="account-section-head">Sign-in methods</h2>
257    <div class="settings-card">
258      <div id="settings-identities" class="account-identities"></div>
259    </div>
260
261    <h2 class="account-section-head" id="settings-password-head" style="display:none">Security</h2>
262
263    <!-- Signed in with LinkedIn or Google and want a password too. Supabase
264         attaches it to the email that account already carries, so there is no
265         "current password" to ask for — there isn't one yet. -->
266    <!-- No email on the account, so there is nothing for a password to
267         belong to: a password signs you in as an address, and without one
268         there is no address to be. Add it first; Supabase confirms it, and
269         the password form takes its place. -->
270    <div class="settings-card" id="settings-add-email-card" style="display:none">
271      <p class="account-hint" style="margin:0 0 14px">Your <span id="settings-noemail-provider">provider</span> sign-in didn't give us an email address. Add one to be able to set a password — a password signs you in as an address, so there has to be one first.</p>
272      <div class="settings-row">
273        <div class="settings-row-label">Email</div>
274        <div class="settings-row-value account-field">
275          <input type="email" id="settings-new-email-input" class="form-input" placeholder="[email protected]" autocomplete="email">
276          <button class="btn btn-sm btn-secondary" id="settings-new-email-save" data-ot-h="h10">Add</button>
277        </div>
278      </div>
279      <div class="form-error hidden" id="settings-new-email-error"></div>
280      <p class="account-hint">We'll send a confirmation link. Once you've clicked it, come back here to set a password.</p>
281    </div>
282
283    <div class="settings-card" id="settings-set-password-card" style="display:none">
284      <p class="account-hint" style="margin:0 0 14px">You signed in with <span id="settings-oauth-provider">LinkedIn</span>. Add a password and you can also sign in with <span id="settings-oauth-email">your email</span>.</p>
285      <div class="form-group">
286        <label class="form-label" for="settings-add-password">Password</label>
287        <div class="password-wrap">
288          <input class="form-input" type="password" id="settings-add-password" autocomplete="new-password" minlength="8">
289          <button type="button" class="password-toggle" data-target="settings-add-password" aria-label="Show password">
290            <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
291            <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
292          </button>
293        </div>
294      </div>
295      <div class="form-group">
296        <label class="form-label" for="settings-add-password-confirm">Confirm password</label>
297        <div class="password-wrap">
298          <input class="form-input" type="password" id="settings-add-password-confirm" autocomplete="new-password" minlength="8">
299          <button type="button" class="password-toggle" data-target="settings-add-password-confirm" aria-label="Show password">
300            <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
301            <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
302          </button>
303        </div>
304      </div>
305      <!-- A live session is not enough to mint a new way into the account: an
306           unlocked laptop would be. Supabase mails a six-digit nonce and
307           takes it alongside the new password. -->
308      <div class="form-group" id="settings-add-code-group" style="display:none">
309        <label class="form-label" for="settings-add-code">Code from your email</label>
310        <input class="form-input" type="text" id="settings-add-code" inputmode="numeric" autocomplete="one-time-code" maxlength="10" placeholder="6-digit code">
311      </div>
312      <div class="form-error hidden" id="settings-add-password-error"></div>
313      <button class="btn btn-secondary" id="settings-add-password-save" data-ot-h="h11" style="width:100%">Email me a code</button>
314    </div>
315
316    <div class="settings-card" id="settings-password-card" style="display:none">
317      <p class="account-hint" style="margin:0 0 14px">Use at least 8 characters. You'll be signed out on every device after saving.</p>
318      <div class="form-group">
319        <label class="form-label" for="settings-current-password">Current password</label>
320        <div class="password-wrap">
321          <input class="form-input" type="password" id="settings-current-password" autocomplete="current-password">
322          <button type="button" class="password-toggle" data-target="settings-current-password" aria-label="Show password">
323            <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
324            <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
325          </button>
326        </div>
327      </div>
328      <div class="form-group">
329        <label class="form-label" for="settings-new-password">New password</label>
330        <div class="password-wrap">
331          <input class="form-input" type="password" id="settings-new-password" autocomplete="new-password" minlength="8">
332          <button type="button" class="password-toggle" data-target="settings-new-password" aria-label="Show password">
333            <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
334            <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
335          </button>
336        </div>
337      </div>
338      <div class="form-group">
339        <label class="form-label" for="settings-confirm-password">Confirm new password</label>
340        <div class="password-wrap">
341          <input class="form-input" type="password" id="settings-confirm-password" autocomplete="new-password" minlength="8">
342          <button type="button" class="password-toggle" data-target="settings-confirm-password" aria-label="Show password">
343            <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
344            <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
345          </button>
346        </div>
347      </div>
348      <div class="form-error hidden" id="settings-password-error"></div>
349      <button class="btn btn-secondary" id="settings-password-save" data-ot-h="h12" style="width:100%">Update password</button>
350    </div>
351
352    <!-- Only for accounts made with an email and password. A LinkedIn or
353         Google account's email belongs to that provider — changing it here
354         would desync the two and break the next sign-in. -->
355    <div class="settings-card" id="settings-email-card" style="display:none">
356      <div class="settings-row">
357        <div class="settings-row-label">Email</div>
358        <div class="settings-row-value account-field">
359          <input type="email" id="settings-email-input" class="form-input" placeholder="[email protected]" autocomplete="email">
360          <button class="btn btn-sm btn-secondary" id="settings-email-save" data-ot-h="h13">Save</button>
361        </div>
362      </div>
363      <div class="settings-row">
364        <div class="settings-row-label">Password</div>
365        <div class="settings-row-value account-field">
366          <div class="password-wrap" style="flex:1;min-width:0">
367            <input class="form-input" type="password" id="settings-email-password" autocomplete="current-password" placeholder="Your current password">
368            <button type="button" class="password-toggle" data-target="settings-email-password" aria-label="Show password">
369              <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
370              <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
371            </button>
372          </div>
373        </div>
374      </div>
375      <div class="form-error hidden" id="settings-email-error"></div>
376      <p class="account-hint">We'll send a confirmation link to the new address. The change takes effect once you click it.</p>
377    </div>
378
379    <h2 class="account-section-head">Session</h2>
380    <div class="settings-card account-session">
381      <button class="btn btn-secondary account-logout-btn" data-ot-h="h14">Log out</button>
382      <div class="account-legal">
383        <a href="#" data-ot-h="h15">Terms of Use</a>
384        <a href="#" data-ot-h="h16">Privacy Policy</a>
385      </div>
386      <!-- Same box, deliberately unequal weight. Leaving for the day and
387           erasing everything you ever logged are not two options to weigh up;
388           one is routine and the other is irreversible. The description stays
389           because it is the last thing anyone reads before doing it. -->
390      <div class="account-danger">
391        <p>Deleting your account permanently removes your profile, your team memberships and every outreach you've logged. Teams where you're the only admin go with it. This cannot be undone.</p>
392        <button type="button" class="account-delete-btn" data-ot-h="h17">Delete account</button>
393      </div>
394    </div>
395  </div>
396</div>
397
398<!-- OAUTH CONSENT (Supabase OAuth 2.1 server redirects here with ?authorization_id) -->
399<div id="page-oauth-consent" class="page">
400  <div class="container" style="max-width:460px;padding-top:64px;padding-bottom:64px">
401    <div class="settings-card" id="oauth-consent-card">
402      <p style="font-size:13px;color:var(--text-3)">Loading authorization request…</p>
403    </div>
404  </div>
405</div>
406
407<!-- TEAM DETAIL -->
408<!-- WORKSPACE SIDEBAR (fixed; shown on the team + pipeline pages) -->
409<aside id="app-sidebar" class="ws-sidebar" style="display:none">
410  <div class="ws-head">
411    <button class="ws-collapse" type="button" data-ot-h="h18" title="Hide sidebar" aria-label="Hide sidebar"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3.5L5.5 8l4.5 4.5"/></svg></button>
412  </div>
413  <!-- Top right: a bell and, when there is something, a number over it. No
414       label — the shape is the word. -->
415  <div class="ws-top">
416    <button class="ws-notify-btn" type="button" id="ws-notify-btn" data-ws="notifications" title="Notifications" aria-label="Notifications">
417      <svg viewBox="0 0 16 16" width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2.2a3.6 3.6 0 013.6 3.6c0 3 1.1 4 1.1 4H3.3s1.1-1 1.1-4A3.6 3.6 0 018 2.2z"/><path d="M6.6 12.1a1.5 1.5 0 002.8 0"/></svg>
418      <span class="ws-notify-count" id="ws-notify-count" hidden>0</span>
419    </button>
420  </div>
421  <button class="ws-brand" type="button" data-ot-h="h19" title="Team &amp; members">
422    <span id="ws-team-icon" class="ws-team-icon"></span>
423    <span id="ws-team-name" class="ws-team-name"></span>
424    <svg class="ws-brand-chev" viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>
425  </button>
426  <nav class="ws-nav">
427    <button class="ws-item" type="button" data-ws="people"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="5.5" r="2.4"/><path d="M1.6 13c0-2.5 2-4.1 4.4-4.1S10.4 10.5 10.4 13"/><path d="M10.8 3.6a2.3 2.3 0 010 4"/><path d="M14.4 13c0-1.9-1.1-3.2-2.7-3.7"/></svg><span class="ws-label">People</span></button>
428    <button class="ws-item" type="button" data-ws="pipeline"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"><rect x="2" y="2.5" width="3.4" height="11" rx="1"/><rect x="6.3" y="2.5" width="3.4" height="7.6" rx="1"/><rect x="10.6" y="2.5" width="3.4" height="9.3" rx="1"/></svg><span class="ws-label">Lists</span></button>
429    <button class="ws-item" type="button" data-ws="integrations"><svg class="ws-eye" viewBox="0 0 18 12" width="16" height="16" fill="currentColor" shape-rendering="crispEdges" style="image-rendering:pixelated" aria-hidden="true"><rect x="4" y="1" width="1" height="1"/><rect x="5" y="1" width="1" height="1"/><rect x="6" y="1" width="1" height="1"/><rect x="7" y="1" width="1" height="1"/><rect x="8" y="1" width="1" height="1"/><rect x="9" y="1" width="1" height="1"/><rect x="10" y="1" width="1" height="1"/><rect x="11" y="1" width="1" height="1"/><rect x="12" y="1" width="1" height="1"/><rect x="13" y="1" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="3" y="2" width="1" height="1"/><rect x="14" y="2" width="1" height="1"/><rect x="15" y="2" width="1" height="1"/><rect x="1" y="3" width="1" height="1"/><rect x="16" y="3" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="8" y="4" width="1" height="1"/><rect x="9" y="4" width="1" height="1"/><rect x="16" y="4" width="1" height="1"/><rect x="1" y="5" width="1" height="1"/><rect x="7" y="5" width="1" height="1"/><rect x="8" y="5" width="1" height="1"/><rect x="9" y="5" width="1" height="1"/><rect x="10" y="5" width="1" height="1"/><rect x="16" y="5" width="1" height="1"/><rect x="1" y="6" width="1" height="1"/><rect x="7" y="6" width="1" height="1"/><rect x="8" y="6" width="1" height="1"/><rect x="9" y="6" width="1" height="1"/><rect x="10" y="6" width="1" height="1"/><rect x="16" y="6" width="1" height="1"/><rect x="1" y="7" width="1" height="1"/><rect x="8" y="7" width="1" height="1"/><rect x="9" y="7" width="1" height="1"/><rect x="16" y="7" width="1" height="1"/><rect x="1" y="8" width="1" height="1"/><rect x="16" y="8" width="1" height="1"/><rect x="2" y="9" width="1" height="1"/><rect x="3" y="9" width="1" height="1"/><rect x="14" y="9" width="1" height="1"/><rect x="15" y="9" width="1" height="1"/><rect x="4" y="10" width="1" height="1"/><rect x="5" y="10" width="1" height="1"/><rect x="6" y="10" width="1" height="1"/><rect x="7" y="10" width="1" height="1"/><rect x="8" y="10" width="1" height="1"/><rect x="9" y="10" width="1" height="1"/><rect x="10" y="10" width="1" height="1"/><rect x="11" y="10" width="1" height="1"/><rect x="12" y="10" width="1" height="1"/><rect x="13" y="10" width="1" height="1"/></svg><span class="ws-label">Configure</span></button>
430  </nav>
431  <div class="ws-foot">
432    <button class="ws-item" type="button" data-ws="home"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 7.2L8 2.5l5.5 4.7"/><path d="M3.8 6.4V13h8.4V6.4"/><path d="M6.6 13V9.2h2.8V13"/></svg><span class="ws-label">Home</span></button>
433    <button class="ws-account" type="button" data-ot-h="h3"><span id="ws-acct-avatar" class="ws-acct-avatar"></span><span id="ws-acct-name" class="ws-acct-name"></span></button>
434  </div>
435</aside>
436<!-- Shown only when the sidebar is collapsed -->
437<button id="ws-show" class="ws-show-btn" type="button" data-ot-h="h18" title="Show sidebar" aria-label="Show sidebar"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3.5L10.5 8 6 12.5"/></svg></button>
438
439<div id="page-team" class="page">
440  <div class="ws-content">
441    <!-- The camera sits above the page heading, watching the room. One host
442         for the whole content area: it stays put while the view under it
443         changes, rather than being rebuilt per section. Mirrored in
444         pixel-eye.js so it faces into the page. -->
445    <div id="pixel-eye-host" class="pixel-eye-host"></div>
446    <input type="file" id="team-icon-file" accept="image/png,image/jpeg,image/webp" style="display:none" data-ot-h="h20">
447
448    <!-- PEOPLE VIEW -->
449    <section class="team-view" data-team-view="people">
450      <div class="ws-topbar">
451        <h1 class="ws-title">People</h1>
452      </div>
453      <!-- Search first, filters behind a button.
454           Searching is what you do most and it was underneath four rows of
455           filter chips; filtering is occasional and was permanently on
456           screen. The order now matches how often each is used. -->
457      <div class="people-toolbar">
458        <div class="people-searchbar">
459          <input type="text" id="people-search" class="form-input" placeholder="Search" autocomplete="off">
460          <button class="btn btn-sm btn-secondary" id="people-search-clear" style="display:none" data-ot-h="h21">Clear</button>
461        </div>
462        <div class="people-filters-wrap">
463          <button type="button" class="btn btn-sm btn-secondary people-filters-toggle" id="people-filters-toggle" data-ot-h="h22" aria-expanded="false">
464            Filters<span id="people-filters-count" class="people-filters-count" hidden></span>
465          </button>
466          <!-- A dropdown, anchored to its button. As a block below the
467               toolbar it just moved the same four rows down the page. -->
468          <div id="people-filters" class="people-filters" hidden>
469            <div id="people-teammate-filter"></div>
470            <div id="people-source-filter" class="people-source-filter"></div>
471            <div id="people-date-filter" class="people-date-filter"></div>
472            <div id="people-marking-filter" class="people-marking-filter"></div>
473          </div>
474        </div>
475      </div>
476      <div id="team-people-list" class="people-grid"></div>
477      <!-- Export and import sit under the list, below the pager. They act on
478           the whole dataset rather than on what's on screen, and the topbar
479           belongs to searching and filtering. -->
480      <div class="panel-actions people-foot">
481        <label class="people-dl-label">
481Next <input type="number" id="people-download-count" min="1" max="100" value="100" class="people-dl-input"> from this page</label>
482        <button class="btn btn-sm btn-secondary" data-ot-h="h23">Download CSV</button>
483        <span class="panel-sep" aria-hidden="true"></span>
484        <button class="btn btn-sm btn-secondary" data-ot-h="h24">Bulk import</button>
485      </div>
486    </section>
487
488    <!-- NOTIFICATIONS VIEW -->
489    <section class="team-view" data-team-view="notifications">
490      <div class="ws-topbar">
491        <h1 class="ws-title">Notifications</h1>
492        <div class="panel-actions">
493          <button class="btn btn-sm btn-secondary" id="notifications-mark-all" data-ot-h="h25">Mark all read</button>
494        </div>
495      </div>
496      <div id="notifications-list"></div>
497    </section>
498
499    <!-- SETTINGS VIEW (internal key stays "integrations") -->
500    <section class="team-view" data-team-view="integrations">
501      <div class="ws-topbar"><h1 class="ws-title">Configure</h1></div>
502      <p class="panel-desc">Your team's profile markings, plus connected data sources.</p>
503      <div id="team-surface-prefs"></div>
504      <div id="team-integrations-list"></div>
505    </section>
506
507    <!-- TEAM VIEW -->
508    <section class="team-view" data-team-view="team">
509      <div class="ws-topbar">
510        <h1 class="ws-title">Team</h1>
511      </div>
512
513      <!-- Billing lapse banner — shown to every member of a locked team,
514           not just admins, so nobody is left guessing why a write failed.
515           Filled in by renderBillingState() in app-team.js. -->
516      <div id="team-billing-banner" class="billing-banner" hidden></div>
517
518      <!-- Identity hero card. Who the team is and how to get into it are the
519           same question, so the invite code sits with the icon and name
520           rather than in a panel of its own further down. -->
521      <div class="panel team-hero-card">
522        <div class="team-hero-top">
523          <div class="team-hero-id">
524            <div id="team-icon" class="team-icon" title="Team icon"></div>
525            <div class="team-hero-meta">
526              <div class="team-hero-name-row">
527                <h2 id="team-name" class="team-hero-name"></h2>
528                <!-- Admin-only, unhidden by loadTeamDetail once membership is
529                     known. Kept out of the markup's reach otherwise: a member
530                     who clicked it would only get an RLS refusal. -->
531                <button type="button" id="team-name-edit" class="team-name-edit" title="Rename team" aria-label="Rename team" data-ot-h="h26" hidden>
532                  <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"><path d="M11.4 2.6l2 2L6 12l-2.6.6L4 10z"/><path d="M10.1 3.9l2 2"/></svg>
533                </button>
534              </div>
535              <div class="team-hero-sub" id="team-member-count"></div>
536            </div>
537          </div>
538          <div class="team-hero-invite">
539            <div class="team-hero-invite-label">Invite teammates</div>
540            <div id="team-invite-section"></div>
541            <div class="team-hero-invite-desc">
542              <p>Anyone with this code can <em>request</em> to join. Requests land in Members below, and nobody gets access until an admin accepts them.</p>
543              <!-- Collapsed by default: the one-line seat note next to the code
544                   covers the common case, and this is here for the admin who
545                   wants the whole rule before handing the code out. Hidden by
546                   renderInviteSeatNote() for comped teams, where none of it
547                   applies, and for members, who cannot accept anyone. -->
548              <details class="team-hero-billing" id="team-hero-billing" hidden>
549                <summary>How adding teammates affects billing</summary>
550                <ul>
551                  <li><strong>Solo is free.</strong> On your own, there's no subscription and no card on file.</li>
552                  <li><strong>Requests are free.</strong> A pending request costs nothing — only members you've accepted count.</li>
553                  <li><strong>Accepting your first teammate starts the plan.</strong> You'll be asked for a card. From then on every active member is a seat, including you, on one invoice.</li>
554                  <li><strong>Change the team whenever.</strong> Seats added or removed mid-cycle are prorated and settled on your next renewal.</li>
555                  <li><strong>Back to one member, back to free.</strong> Removing everyone else cancels the subscription outright and invoices the unused time back.</li>
556                  <li><strong>A failed payment doesn't delete anything.</strong> The team goes read-only until the card is fixed; your data stays put.</li>
557                </ul>
558              </details>
559            </div>
560          </div>
561        </div>
562      </div>
563
564      <div class="team-panels">
565        <!-- Admin-only. Hidden entirely for members and for exempt teams. -->
566        <section class="panel" id="team-billing-panel" hidden>
567          <div class="panel-head"><h3 class="panel-title">Plan &amp; billing</h3></div>
568          <div id="team-billing-body"></div>
569        </section>
570        <section class="panel" id="team-members-panel">
571          <div class="panel-head"><h3 class="panel-title">Members</h3></div>
572          <div id="team-members-list" class="panel-flatlist"></div>
573        </section>
574      </div>
575
576      <!-- Last thing on the page, behind its own heading. Admin-only, so most
577           people never see it at all; for those who do it should take a scroll
578           and a read, not sit one mis-click from the page title. -->
579      <div id="team-danger-section" style="display:none">
580        <section class="panel team-danger-panel">
581          <div class="panel-head"><h3 class="panel-title">Danger zone</h3></div>
582          <div class="team-danger-row">
583            <!-- What deletion does is explained in the confirm dialog, where
584                 it is a question you're answering rather than a paragraph
585                 sitting on the page you're trying to use. -->
586            <div class="team-danger-copy">
587              <div class="team-danger-label">Delete this team</div>
588            </div>
589            <div class="team-danger-actions">
590              <button class="btn btn-sm btn-secondary" data-ot-h="h27">Delete member data</button>
591              <button class="btn btn-sm btn-secondary team-delete-btn" data-ot-h="h28">Delete team</button>
592            </div>
593          </div>
594        </section>
595      </div>
596    </section>
597  </div>
598</div>
599
600<!-- PERSON PROFILE PAGE -->
601<div id="page-profile" class="page">
602  <div class="container profile-container">
603    <div class="profile-topbar">
604      <a href="#" id="profile-back" class="profile-back-link">&larr; Back to team</a>
605      <!-- One switch for every way to change this profile. Off, the page
606           reads: no Edit/Delete on your own outreaches and comments, no
607           rename, no destructive profile actions. On, they all appear at
608           once. Purely a CSS class on #page-profile, so toggling costs no
609           refetch and survives the re-render after each save. -->
610      <div class="profile-topbar-actions">
611        <span id="profile-edit-actions" class="profile-edit-only">
612          <button type="button" id="profile-delete-btn" class="btn btn-sm btn-secondary" style="color:var(--danger)" data-ot-h="h29">Delete profile</button>
613          <button type="button" id="profile-merge-btn" class="btn btn-sm btn-secondary" data-ot-h="h30">Merge profile</button>
614        </span>
615        <button type="button" id="profile-edit-btn" class="btn btn-sm btn-secondary" aria-pressed="false" data-ot-h="h31">Edit</button>
616      </div>
617    </div>
618
619    <div class="profile-main-eyes" aria-hidden="true">
620      <div class="bg-eye" id="profile-eye-left"></div>
621      <div class="bg-eye" id="profile-eye-right"></div>
622    </div>
623
624    <div class="profile-grid">
625      <aside class="profile-sidebar">
626        <!-- Identity -->
627        <div class="card profile-hero-card">
628          <div class="profile-hero-avatar" id="profile-hero-avatar"></div>
629          <!-- Team markings (mirrors the extension's bottom-right square) -->
630          <div class="profile-hero-markings" id="profile-hero-markings" style="display:none"></div>
631          <h2 id="profile-name" class="profile-hero-name"></h2>
632          <div id="profile-sub" class="profile-hero-sub"></div>
633          <div class="profile-hero-actions profile-edit-only">
634            <button type="button" id="profile-edit-name-btn" class="btn btn-sm btn-secondary" data-ot-h="h32">Edit name</button>
635          </div>
636        </div>
637
638        <!-- URL-status banner — visible when the profile's LinkedIn
639             URL hasn't been verified, or has been marked dead. -->
640        <div id="profile-url-status" style="display:none"></div>
641
642        <!-- Facts about this person -->
643        <div class="card">
644          <div class="profile-card-head">
645            <h3 class="profile-card-title">About</h3>
646          </div>
647          <div class="profile-about-section">
648            <div class="profile-about-label">Contact</div>
649            <!-- Read-only, all three of them. LinkedIn, email and phone are
650                 what a connector or the extension observed; the place to
651                 correct one is the system it came from. -->
652            <div id="profile-emails-list"></div>
653            <div id="profile-phones-list"></div>
654          </div>
655          <div class="profile-about-section" id="profile-about-enrichment-section" style="display:none">
656            <div class="profile-about-label">Enrichment</div>
657            <div id="profile-enrichment"></div>
658          </div>
659          <div class="profile-about-section" id="profile-about-events-section" style="display:none">
660            <div class="profile-about-label">Events</div>
661            <div id="profile-events"></div>
662          </div>
663          <div class="profile-about-section" id="profile-about-gsheets-section" style="display:none">
664            <div class="profile-about-label">From your sheets</div>
665            <div id="profile-gsheets"></div>
666          </div>
667        </div>
668
669        <div id="profile-luma-guess" class="card" style="display:none">
670          <h3 class="profile-card-title">May have attended</h3>
671          <p class="profile-card-sub">Found on Luma under a matching name, but we couldn't confirm it's the same person. Did they attend?</p>
672          <div id="profile-luma-guess-list"></div>
673        </div>
674
675        <div id="profile-gsheet-guess" class="card" style="display:none">
676          <h3 class="profile-card-title">May be on your sheets</h3>
677          <p class="profile-card-sub">
677Found on a sheet under a matching name, but we couldn't confirm it's the same person. Is this them?</p>
678          <div id="profile-gsheet-guess-list"></div>
679        </div>
680
681        <div id="profile-luma-suggest" class="card" style="display:none">
682          <h3 class="profile-card-title">Possible Luma matches</h3>
683          <div id="profile-luma-suggest-list"></div>
684          <p class="profile-card-sub" style="margin:8px 0 0">These names looked similar on Luma but didn't match exactly. Link any that are the same person and their events will show up above.</p>
685        </div>
686
687        <div id="profile-gsheet-suggest" class="card" style="display:none">
688          <h3 class="profile-card-title">Possible sheet matches</h3>
689          <div id="profile-gsheet-suggest-list"></div>
690          <p class="profile-card-sub" style="margin:8px 0 0">These rows look similar to this person but didn't match exactly. Confirm any that are the same person and they'll show up above.</p>
691        </div>
692
693        <div id="profile-connection-records" class="card" style="display:none">
694          <h3 class="profile-card-title">Other connections</h3>
695          <div id="profile-connection-records-list"></div>
696        </div>
697      </aside>
698
699      <section class="profile-main">
700        <div class="profile-main-section">
701          <h3 class="profile-section-head">Comments</h3>
702          <div class="card" style="padding:14px 16px">
703            <textarea id="profile-note-input" class="form-input" rows="3" placeholder="Add a comment — what you know, deal status, intro paths. @ to tag a teammate." style="resize:vertical;min-height:64px"></textarea>
704            <!-- Hidden until there's something to post — an always-on button
705                 under an empty box is an instruction you can't follow. -->
706            <div class="profile-note-post-row" id="profile-note-post-row" hidden>
707              <button class="btn btn-sm btn-primary" id="profile-note-save" data-ot-h="h33">Post comment</button>
708            </div>
709          </div>
710          <div id="profile-notes-list"></div>
711        </div>
712
713        <div class="profile-main-section">
714          <h3 class="profile-section-head">Outreach history</h3>
715          <div id="profile-outreach-add"></div>
716          <div id="profile-outreaches" class="card profile-outreach-card">
717            <p class="profile-about-empty" style="padding:10px 0">Loading…</p>
718          </div>
719        </div>
720      </section>
721    </div>
722  </div>
723</div>
724
725<!-- MODALS -->
726<div class="modal-overlay" id="modal-create-team">
727  <div class="modal">
728    <h3>Create a team</h3>
729    <div class="form-group">
730      <label class="form-label">Team name</label>
731      <input class="form-input" type="text" id="create-team-name" placeholder="e.g. Deal Flow Team">
732    </div>
733    <div class="modal-actions">
734      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
735      <button class="btn btn-primary btn-sm" data-ot-h="h35">Create</button>
736    </div>
737  </div>
738</div>
739<!-- Everyone who has left anything in this team, current members and people
740     who were removed long ago. The checkbox on removal only helps at the
741     moment of removal; this is how an admin clears up afterwards. -->
742<div class="modal-overlay" id="modal-member-data">
743  <div class="modal" style="max-width:520px">
744    <h3>Delete member data</h3>
745    <p style="font-size:12.5px;color:var(--text-2);line-height:1.55;margin:8px 0 14px">Everyone who has logged anything in this team, including people who are no longer in it. Deleting removes that person's outreaches, comments, markings and views from this team only. It cannot be undone.</p>
746    <div id="member-data-list" style="max-height:340px;overflow-y:auto"></div>
747    <div class="modal-actions">
748      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Close</button>
749    </div>
750  </div>
751</div>
752<div class="modal-overlay" id="modal-confirm">
753  <div class="modal">
754    <h3 id="confirm-title">Are you sure?</h3>
755    <p id="confirm-message" style="font-size:13px;color:var(--text-2);line-height:1.5;margin-top:8px;white-space:pre-line"></p>
756    <!-- An optional extra decision that belongs WITH the confirmation rather
757         than in a second dialog after it. Hidden unless the caller asks. -->
758    <label class="confirm-extra" id="confirm-extra" hidden>
759      <input type="checkbox" id="confirm-extra-box">
760      <span id="confirm-extra-label"></span>
761    </label>
762    <div class="modal-actions">
763      <button class="btn btn-secondary btn-sm" id="confirm-cancel">Cancel</button>
764      <button class="btn btn-primary btn-sm" id="confirm-ok">Confirm</button>
765    </div>
766  </div>
767</div>
768<!-- Profile-marking alternative editor (Integrations page, admins only).
769     Icon grid + color swatches are rendered by openMarkingOptionModal(). -->
770<div class="modal-overlay" id="modal-marking-option">
771  <div class="modal">
772    <h3 id="marking-option-title">New marking</h3>
773    <div class="form-group">
774      <label class="form-label" for="marking-option-name">Name</label>
775      <input class="form-input" type="text" id="marking-option-name" maxlength="40" placeholder="e.g. Talking to a teammate">
776    </div>
777    <div class="form-group">
778      <label class="form-label">Icon</label>
779      <div id="marking-option-icons" class="marking-icon-grid"></div>
780    </div>
781    <div class="form-group">
782      <label class="form-label">Color</label>
783      <div id="marking-option-colors" class="marking-color-row"></div>
784    </div>
785    <div class="modal-actions">
786      <button class="btn btn-secondary btn-sm" id="marking-option-delete" style="margin-right:auto;color:var(--danger);display:none" data-ot-h="h36">Delete</button>
787      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
788      <button class="btn btn-primary btn-sm" id="marking-option-save" data-ot-h="h37">Save</button>
789    </div>
790  </div>
791</div>
792<div class="modal-overlay" id="modal-gsheet-header-row">
793  <div class="modal">
794    <h3>Which row has the column titles?</h3>
795    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px" id="gsheet-header-row-sheet"></p>
796    <div class="form-group">
797      <label class="form-label" for="gsheet-header-row-input">Header row</label>
798      <input type="number" id="gsheet-header-row-input" class="form-input" min="1" value="1">
799      <p class="form-hint" style="margin-top:6px">If your sheet has a title banner in row 1, set this to 2. Otherwise leave it at 1.</p>
800    </div>
801    <div class="modal-actions">
802      <button class="btn btn-secondary btn-sm" id="gsheet-header-row-cancel">Cancel</button>
803      <button class="btn btn-primary btn-sm" id="gsheet-header-row-ok">Connect</button>
804    </div>
805  </div>
806</div>
807<!-- Excel workbook picker — used by startExcelConnect(). Same shape as the
808     gsheet flow but driven by Microsoft Graph: a search input that
809     hits /me/drive/search and a list of recent .xlsx files to pick
810     from. Worksheet selection happens inline after a workbook click. -->
811<div class="modal-overlay" id="modal-excel-workbook">
812  <div class="modal" style="max-width:520px">
813    <h3>Pick an Excel workbook</h3>
814    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 12px">Connect a workbook from your OneDrive or SharePoint. We'll show its rows on profile pages and re-sync as the file changes.</p>
815    <div class="form-group" style="margin-bottom:8px">
816      <input type="text" id="excel-workbook-search" class="form-input" placeholder="Search workbooks by name…" autocomplete="off">
817    </div>
818    <div id="excel-workbook-list" style="max-height:340px;overflow-y:auto;border:1px solid var(--border);border-radius:8px;background:var(--surface-2);padding:4px"></div>
819    <div id="excel-workbook-account" style="font-size:11.5px;color:var(--text-3);margin-top:8px"></div>
820    <div class="modal-actions">
821      <button class="btn btn-secondary btn-sm" id="excel-workbook-cancel">Cancel</button>
822    </div>
823  </div>
824</div>
825<!-- Minimal Excel configure modal — pick which column has the LinkedIn
826     URL/slug, the email column, and which columns to display on
827     profile pages. Polish (display template editor, icon upload, hedge
828     template) lands later; this gets the integration functional
829     enough that matches can surface on profile pages. -->
830<div class="modal-overlay" id="modal-connect-integration">
831  <div class="modal">
832    <h3 id="connect-integration-title">Connect</h3>
833    <p id="connect-integration-help" style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px"></p>
834    <div class="form-group" id="connect-integration-label-group" style="display:none">
835      <label class="form-label">Label (optional)</label>
836      <input class="form-input" type="text" id="connect-integration-label" placeholder="e.g. Bob's events" autocomplete="off">
837    </div>
838    <div class="form-group">
839      <label class="form-label" id="connect-integration-key-label">API key</label>
840      <input class="form-input" type="password" id="connect-integration-key" placeholder="Paste your key" autocomplete="off">
841    </div>
842    <div class="form-group" id="connect-integration-ics-group" style="display:none">
843      <label class="form-label">iCal URL (optional)</label>
844      <input class="form-input" type="password" id="connect-integration-ics" placeholder="https://api2.luma.com/ics/get?…" autocomplete="off">
845      <p style="font-size:11.5px;color:var(--text-3);margin:6px 2px 0;line-height:1.45">Include co-hosted events the calendar API can't see. Find this in Luma Settings → Add to Calendar → copy the subscription URL.</p>
846    </div>
847    <div class="form-group" id="connect-integration-attio-list-group" style="display:none">
848      <label class="form-label">Also add pushed people to a list (optional)</label>
849      <input class="form-input" type="text" id="connect-integration-attio-list" placeholder="Paste an Attio list URL or list ID" autocomplete="off">
850      <p style="font-size:11.5px;color:var(--text-3);margin:6px 2px 0;line-height:1.45">People we push land in one auto-created "Orwell's bad dream" list with a Source filter (Outreach / Luma / HubSpot). Add one more list of your own here if you want them somewhere specific too.</p>
851    </div>
852    <div id="connect-integration-error" class="form-error hidden"></div>
853    <div class="modal-actions">
854      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
855      <button class="btn btn-primary btn-sm" id="connect-integration-save" data-ot-h="h38">Connect</button>
856    </div>
857  </div>
858</div>
859<div class="modal-overlay" id="modal-luma-csv">
860  <div class="modal">
861    <h3>Import Luma event CSV</h3>
862    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px">Upload the guest-list CSV Luma gives you (Event → Manage → Guests → Export). Useful for co-hosted events the API can't read. Attendees will show up on matching LinkedIn profiles the same way API-synced events do.</p>
863    <div class="form-group">
864      <label class="form-label">Event name</label>
865      <input class="form-input" type="text" id="luma-csv-name" placeholder="e.g. Antler Fall '25 Demo Day" autocomplete="off">
866    </div>
867    <div class="form-group">
868      <label class="form-label">Event date</label>
869      <input class="form-input" type="text" id="luma-csv-date" placeholder="yyyy-mm-dd" readonly>
870    </div>
871    <div class="form-group">
872      <label class="form-label">Event URL</label>
873      <input class="form-input" type="url" id="luma-csv-url" placeholder="https://lu.ma/..." autocomplete="off">
874      <p style="font-size:11.5px;color:var(--text-3);margin:6px 2px 0;line-height:1.45">The lu.ma link for the event. Used to avoid importing the same event twice.</p>
875    </div>
876    <div class="form-group">
877      <label class="form-label">CSV file</label>
878      <div class="csv-pill" style="display:flex">
879        <input type="file" id="luma-csv-file" accept=".csv,.tsv,text/csv,text/tab-separated-values" style="display:none" data-ot-h="h39">
880        <button type="button" data-ot-h="h40">Choose file</button>
881        <span class="csv-sep"></span>
882        <span class="csv-name" id="luma-csv-file-name">No file chosen</span>
883      </div>
884    </div>
885    <div id="luma-csv-error" class="form-error hidden"></div>
886    <div class="modal-actions">
887      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
888      <button class="btn btn-primary btn-sm" id="luma-csv-save" data-ot-h="h41">Import</button>
889    </div>
890  </div>
891</div>
892<div class="modal-overlay" id="modal-luma-event-edit">
893  <div class="modal">
894    <h3>Edit event</h3>
895    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px">Update the name, date, or URL for this manually-imported event. The change applies to every attendee row already loaded for it.</p>
896    <div class="form-group">
897      <label class="form-label">Event name</label>
898      <input class="form-input" type="text" id="luma-edit-name" autocomplete="off">
899    </div>
900    <div class="form-group">
901      <label class="form-label">Event date</label>
902      <input class="form-input" type="text" id="luma-edit-date" placeholder="yyyy-mm-dd" readonly>
903    </div>
904    <div class="form-group">
905      <label class="form-label">Event URL</label>
906      <input class="form-input" type="url" id="luma-edit-url" placeholder="https://lu.ma/..." autocomplete="off">
907    </div>
908    <div id="luma-edit-error" class="form-error hidden"></div>
909    <div class="modal-actions">
910      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
911      <button class="btn btn-primary btn-sm" id="luma-edit-save" data-ot-h="h42">Save</button>
912    </div>
913  </div>
914</div>
915<div class="modal-overlay" id="modal-bulk-import-outreach">
916  <div class="modal" style="max-width:560px">
917    <h3>Bulk import outreaches</h3>
918    <p style="font-size:13px;color:var(--text-2);margin:8px 0 12px">Upload a CSV file to log multiple outreaches at once. The CSV must have exactly 4 columns in this order:</p>
919    <table style="width:100%;font-size:12px;border-collapse:collapse;margin-bottom:12px">
920      <thead><tr style="text-align:left;border-bottom:1px solid var(--border)">
921        <th style="padding:6px 8px;color:var(--text-3);font-weight:600;width:40px"></th>
922        <th style="padding:6px 8px;font-weight:600">A</th>
923        <th style="padding:6px 8px;font-weight:600">B</th>
924        <th style="padding:6px 8px;font-weight:600">C</th>
925        <th style="padding:6px 8px;font-weight:600">D</th>
926      </tr></thead>
927      <tbody>
928        <tr style="border-bottom:1px solid var(--border)">
929          <td style="padding:6px 8px;color:var(--text-3);font-weight:600">Info</td>
930          <td style="padding:6px 8px;color:var(--text-2)">Reacher (name, LinkedIn, or "unknown")</td>
931          <td style="padding:6px 8px;color:var(--text-2)">Founder's LinkedIn</td>
932          <td style="padding:6px 8px;color:var(--text-2)">Date</td>
933          <td style="padding:6px 8px;color:var(--text-2)">Notes (optional)</td>
934        </tr>
935        <tr style="border-bottom:1px solid var(--border);font-family:var(--mono);font-size:11px;color:var(--text-3)">
936          <td style="padding:6px 8px;color:var(--text-3);font-weight:600;font-family:var(--font)">1</td>
937          <td style="padding:6px 8px">linkedin.com/in/john-doe</td>
938          <td style="padding:6px 8px">linkedin.com/in/jane-smith</td>
939          <td style="padding:6px 8px">2025-03-15</td>
940          <td style="padding:6px 8px">Discussed Series A</td>
941        </tr>
942        <tr style="font-family:var(--mono);font-size:11px;color:var(--text-3)">
943          <td style="padding:6px 8px;color:var(--text-3);font-weight:600;font-family:var(--font)">2</td>
944          <td style="padding:6px 8px">Alex from events team</td>
945          <td style="padding:6px 8px">linkedin.com/in/bob-lee</td>
946          <td style="padding:6px 8px">2025-04-01</td>
947          <td style="padding:6px 8px">Cold outreach</td>
948        </tr>
949      </tbody>
950    </table>
951    <p style="font-size:11px;color:var(--text-3);margin-bottom:12px">No header row. Column A can be any free text (a teammate's name, a LinkedIn URL, or "unknown"). If it's a LinkedIn URL that matches a team member, the outreach is credited to them. Column B must be a LinkedIn URL or slug.</p>
952    <div class="csv-pill">
953      <input type="file" id="csv-upload" accept=".csv,text/csv" style="display:none">
954      <button type="button" data-ot-h="h43">Choose file</button>
955      <span class="csv-sep"></span>
956      <span class="csv-name" id="csv-name">No file chosen</span>
957      <span class="csv-sep"></span>
958      <button type="button" id="csv-upload-btn" data-ot-h="h44" disabled>Upload & Import</button>
959    </div>
960    <div id="csv-status" style="margin-top:10px;font-size:13px"></div>
961    <div class="modal-actions" style="justify-content:flex-end;margin-top:16px">
962      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Close</button>
963    </div>
964  </div>
965</div>
966<div class="modal-overlay" id="modal-gsheet-config">
967  <div class="modal" style="max-width:560px">
968    <h3>Configure Google Sheet</h3>
969    <p id="gsheet-config-sheet-name" style="font-size:12.5px;color:var(--text-3);margin-top:4px;margin-bottom:14px"></p>
970
971    <div class="form-group" style="display:flex;align-items:center;gap:14px">
972      <div style="flex-shrink:0">
973        <input type="file" id="gsheet-config-icon-file" accept="image/png,image/jpeg,image/webp" style="display:none" data-ot-h="h45">
974        <button type="button" id="gsheet-config-icon-btn" data-ot-h="h46" style="width:56px;height:56px;background:var(--surface-2);border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:pointer;padding:0" title="Choose icon">
975          <span id="gsheet-config-icon-preview" style="width:44px;height:44px;display:flex;align-items:center;justify-content:center;overflow:hidden"></span>
976        </button>
977        <button type="button" id="gsheet-config-icon-clear" data-ot-h="h47" style="display:none;background:none;border:none;color:var(--danger);font-size:11px;padding:2px;margin-top:4px;cursor:pointer;width:100%;text-align:center">Remove</button>
978      </div>
979      <div style="flex:1;min-width:0">
980        <label class="form-label" for="gsheet-config-label">Display name</label>
981        <input type="text" id="gsheet-config-label" class="form-input" placeholder="Sheet" data-ot-h="h48">
982        <p class="form-hint" style="margin-top:4px;font-size:11px">Shown on profiles in the website and extension. Icon: PNG/JPEG/WebP, square, max 2 MB.</p>
983      </div>
984    </div>
985
986    <div style="padding:10px 12px;background:var(--surface-2);border:1px solid var(--border);border-radius:8px;margin-bottom:14px">
987      <div style="display:flex;align-items:center;gap:8px">
988        <label for="gsheet-config-header-row" style="font-size:12px;color:var(--text-2);font-weight:600;flex-shrink:0">Header row</label>
989        <input type="number" id="gsheet-config-header-row" class="form-input" min="1" value="1" style="width:72px;padding:4px 8px;font-size:12px">
990        <button type="button" class="btn btn-secondary btn-sm" id="gsheet-config-resync-btn" data-ot-h="h49" style="margin-left:auto">Sync now</button>
991      </div>
992      <p class="form-hint" style="margin:6px 0 0;font-size:11px">Which row contains the column titles? If your sheet has a title banner in row 1, set this to 2. Click <b>Sync now</b> to (re-)fetch with this row as headers.</p>
993      <div style="display:flex;align-items:center;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)">
994        <label for="gsheet-config-sync-interval" style="font-size:12px;color:var(--text-2);font-weight:600;flex-shrink:0">Auto-sync every</label>
995        <select id="gsheet-config-sync-interval" class="form-input" style="width:auto;padding:4px 8px;font-size:12px">
996          <option value="15">15 min</option>
997          <option value="30">30 min</option>
998          <option value="60">1 hour</option>
999          <option value="240">4 hours</option>
1000          <option value="1440">1 day</option>
1001        </select>
1002      </div>
1003      <p class="form-hint" style="margin:6px 0 0;font-size:11px">How often background syncs check this sheet. Pick a longer interval for sheets fed by Coefficient/IMPORTRANGE so we don't pull every 15 min when nothing's actually new.</p>
1004      <div id="gsheet-config-last-synced" style="font-size:11px;color:var(--text-3);margin-top:4px"></div>
1005    </div>
1006
1007    <div class="form-group">
1008      <button type="button" class="gsheet-toggle" id="gsheet-toggle-url" aria-pressed="true" data-ot-h="h50">
1009        <span class="gsheet-toggle-check">✓</span>
1010        <span>My sheet has a LinkedIn URL column</span>
1011      </button>
1012      <div style="margin-top:10px">
1013        <label class="form-label" id="gsheet-config-column-label">LinkedIn URL column</label>
1014        <select id="gsheet-config-column" class="form-input"></select>
1015        <p id="gsheet-config-slug-warning" class="form-hint" style="display:none;color:var(--warn);margin-top:6px">
1016          Heads up: name matching is fuzzy. People with similar names may be confused for each other, and some rows may not match at all.
1017        </p>
1018      </div>
1019    </div>
1020
1021    <div class="form-group">
1022      <button type="button" class="gsheet-toggle" id="gsheet-toggle-email" aria-pressed="false" data-ot-h="h51">
1023        <span class="gsheet-toggle-check">✓</span>
1024        <span>My sheet has an email column</span>
1025      </button>
1026      <div id="gsheet-config-email-wrap" style="margin-top:10px;display:none">
1027        <label class="form-label">Email column</label>
1028        <select id="gsheet-config-email-column" class="form-input"></select>
1029        <p class="form-hint" style="margin-top:6px">Matching rows with the same email on a profile auto-confirm — even when a teammate originally picked "name match".</p>
1030      </div>
1031    </div>
1032
1033    <div class="form-group">
1034      <button type="button" class="gsheet-toggle" id="gsheet-toggle-phone" aria-pressed="false" data-ot-h="h52">
1035        <span class="gsheet-toggle-check">✓</span>
1036        <span>My sheet has a phone column</span>
1037      </button>
1038      <div id="gsheet-config-phone-wrap" style="margin-top:10px;display:none">
1039        <label class="form-label">Phone column</label>
1040        <select id="gsheet-config-phone-column" class="form-input"></select>
1041        <p class="form-hint" style="margin-top:6px">Phone numbers are shown on the profile under the email. Unlike email, a phone never decides <em>who</em> someone is — it is information, not a match. Numbers that differ only by country code are shown once.</p>
1042      </div>
1043    </div>
1044
1045    <div class="form-group">
1046      <div id="gsheet-config-preview-wrap" style="padding:10px 12px;background:var(--bg);border:1px dashed var(--border);border-radius:8px;margin-bottom:8px;display:none">
1047        <div style="font-size:11px;color:var(--text-3);font-weight:600;text-transform:uppercase;letter-spacing:0.03em;margin-bottom:6px">Preview · confirmed match</div>
1048        <div id="gsheet-config-preview" class="pill-row"></div>
1049      </div>
1050      <label class="form-label">What to show on profiles</label>
1051      <p style="font-size:11.5px;color:var(--text-3);margin:2px 0 8px;line-height:1.45">Build the pill by stacking column values and your own text in any order. Text between columns is literal — add spaces, dashes, labels, whatever you want. Nothing here changes the sheet itself.</p>
1052      <div id="gsheet-config-template-rows" style="display:flex;flex-direction:column;gap:4px;padding:6px 8px;background:var(--surface-2);border:1px solid var(--border);border-radius:8px"></div>
1053      <div style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap">
1054        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h53">+ Add column</button>
1055        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h54">+ Add text</button>
1056        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h55" title="Shows as a chip; the full cell value reveals below the pill when clicked">+ Add expandable</button>
1057      </div>
1058    </div>
1059
1060    <div class="form-group">
1061      <div id="gsheet-config-pending-preview-wrap" style="padding:10px 12px;background:var(--bg);border:1px dashed var(--border);border-radius:8px;margin-bottom:8px;display:none">
1062        <div style="font-size:11px;color:var(--text-3);font-weight:600;text-transform:uppercase;letter-spacing:0.03em;margin-bottom:6px">Preview · pending match</div>
1063        <div id="gsheet-config-preview-pending" class="pill-row"></div>
1064      </div>
1065      <label class="form-label">Pending-match label</label>
1066      <p style="font-size:11.5px;color:var(--text-3);margin:2px 0 8px;line-height:1.45">Shown as a standalone pill for rows the extension thinks <i>might</i> be this person but aren't certain yet. It's its own little template — when a teammate confirms the match, it converts into the "what to show on profiles" pill above. Leave empty to drop pending pills entirely.</p>
1067      <div id="gsheet-config-hedge-rows" style="display:flex;flex-direction:column;gap:4px;padding:6px 8px;background:var(--surface-2);border:1px solid var(--border);border-radius:8px"></div>
1068      <div style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap">
1069        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h56">+ Add column</button>
1070        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h57">+ Add text</button>
1071        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h58" title="Shows as a chip; the full cell value reveals below the pill when clicked">+ Add expandable</button>
1072      </div>
1073    </div>
1074
1075    <div id="gsheet-config-error" class="form-error hidden"></div>
1076    <div class="modal-actions">
1077      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1078      <button class="btn btn-primary btn-sm" id="gsheet-config-save" data-ot-h="h59">Save</button>
1079    </div>
1080  </div>
1081</div>
1082<!-- Logging an outreach from the website. Same two questions as the edit
1083     modal below, in the same order, so the thing you fill in to create one is
1084     the thing you come back to when you change it. -->
1085<div class="modal-overlay" id="modal-log-outreach">
1086  <div class="modal">
1087    <h3>Log an outreach</h3>
1088    <div class="form-group">
1089      <label class="form-label">How did you reach them?</label>
1090      <div class="channel-picker" id="log-outreach-channels" role="radiogroup" aria-label="Channel"></div>
1091    </div>
1092    <div class="form-group">
1093      <label class="form-label">Note <span style="color:var(--text-3);font-weight:400">(optional)</span></label>
1094      <input class="form-input" type="text" id="log-outreach-note" placeholder="What was it about? @ tags a teammate." autocomplete="off">
1095    </div>
1096    <div class="modal-actions">
1097      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1098      <button class="btn btn-primary btn-sm" id="log-outreach-save" data-ot-h="h60">Log outreach</button>
1099    </div>
1100  </div>
1101</div>
1102<div class="modal-overlay" id="modal-edit-outreach">
1103  <div class="modal">
1104    <h3>Edit outreach</h3>
1105    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px">The channel and the note can be changed — including on outreaches the extension logged. The original date stays the same.</p>
1106    <div class="form-group">
1107      <label class="form-label">How did you reach them?</label>
1108      <div class="channel-picker" id="edit-outreach-channels" role="radiogroup" aria-label="Channel"></div>
1109    </div>
1110    <div class="form-group">
1111      <label class="form-label">Note <span style="color:var(--text-3);font-weight:400">(optional)</span></label>
1112      <input class="form-input" type="text" id="edit-outreach-note" placeholder="What was it about?" autocomplete="off">
1113    </div>
1114    <div class="modal-actions">
1115      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1116      <button class="btn btn-primary btn-sm" id="edit-outreach-save" data-ot-h="h61">Save</button>
1117    </div>
1118  </div>
1119</div>
1120<div class="modal-overlay" id="modal-edit-profile-note">
1121  <div class="modal">
1122    <h3>Edit comment</h3>
1123    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px">You can change your comment.</p>
1124    <div class="form-group">
1125      <label class="form-label">Comment</label>
1126      <textarea class="form-input" id="edit-profile-note-body" rows="4" style="resize:vertical;min-height:80px"></textarea>
1127    </div>
1128    <div class="modal-actions">
1129      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1130      <button class="btn btn-primary btn-sm" id="edit-profile-note-save" data-ot-h="h62">Save</button>
1131    </div>
1132  </div>
1133</div>
1134<!-- SIGN IN / SIGN UP — LinkedIn + email options -->
1135<div class="modal-overlay" id="modal-auth">
1136  <div class="modal auth-modal">
1137    <div class="auth-modal-view" id="auth-view-main">
1138      <h3 class="auth-title" id="auth-title">&gt; SIGN_IN</h3>
1139
1140      <button type="button" class="btn btn-primary auth-provider-btn" id="auth-linkedin-btn">
1141        <svg width="18" height="18" viewBox="0 0 16 16" fill="none" aria-hidden="true" style="flex-shrink:0"><rect width="16" height="16" rx="3" fill="#0A66C2"/><path d="M5.2 12.5H3.3V6.7h1.9v5.8zm-.95-6.6a1.1 1.1 0 110-2.2 1.1 1.1 0 010 2.2zm8.25 6.6h-1.9V9.7c0-.7-.01-1.6-1-1.6s-1.15.77-1.15 1.55v2.85H6.5V6.7h1.82v.79h.03c.25-.48.87-1 1.8-1 1.93 0 2.28 1.27 2.28 2.92v3.09h-.03z" fill="#fff"/></svg>
1142        Continue with LinkedIn
1143      </button>
1144
1145      <button type="button" class="btn btn-secondary auth-provider-btn" id="auth-google-btn">
1146        <svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true" style="flex-shrink:0"><path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 01-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62z"/><path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 009 18z"/><path fill="#FBBC05" d="M3.97 10.72a5.41 5.41 0 010-3.44V4.95H.96a9 9 0 000 8.1l3.01-2.33z"/><path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.46.89 11.43 0 9 0A9 9 0 00.96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z"/></svg>
1147        Continue with Google
1148      </button>
1149
1150      <div class="form-divider">or with email</div>
1151
1152      <div class="auth-tabs">
1153        <button type="button" class="auth-tab active" data-tab="signin">Sign in</button>
1154        <button type="button" class="auth-tab" data-tab="signup">Create account</button>
1155      </div>
1156
1157      <form id="auth-email-form" autocomplete="on">
1158        <!-- Sign-up only. setAuthMode() toggles both `hidden` and the
1159             `required` attribute — a hidden input that stays `required`
1160             blocks submit with an un-focusable validation bubble. -->
1161        <div class="form-group" id="auth-name-group" hidden>
1162          <label class="form-label" for="auth-name">Your name</label>
1163          <input class="form-input" type="text" id="auth-name" autocomplete="name" maxlength="80">
1164          <p class="form-hint">Shown to teammates on the outreach you log.</p>
1165        </div>
1166        <div class="form-group">
1167          <label class="form-label" for="auth-email">Email</label>
1168          <input class="form-input" type="email" id="auth-email" autocomplete="email" required>
1169        </div>
1170        <div class="form-group">
1171          <label class="form-label" for="auth-password">Password</label>
1172          <div class="password-wrap">
1173            <input class="form-input" type="password" id="auth-password" autocomplete="current-password" required minlength="8">
1174            <button type="button" class="password-toggle" data-target="auth-password" aria-label="Show password">
1175              <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1176              <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
1177            </button>
1178          </div>
1179          <p class="form-hint" id="auth-password-hint" style="display:none">At least 8 characters.</p>
1180        </div>
1181        <!-- Sign-up only, same hidden/required pairing as the name field. -->
1182        <div class="form-group" id="auth-password-confirm-group" hidden>
1183          <label class="form-label" for="auth-password-confirm">Confirm password</label>
1184          <div class="password-wrap">
1185            <input class="form-input" type="password" id="auth-password-confirm" autocomplete="new-password" minlength="8">
1186            <button type="button" class="password-toggle" data-target="auth-password-confirm" aria-label="Show password">
1187              <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1188              <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
1189            </button>
1190          </div>
1191        </div>
1192        <div class="form-error hidden" id="auth-error-msg"></div>
1193        <button type="submit" class="btn btn-primary" id="auth-submit-btn" style="width:100%">Sign in</button>
1194      </form>
1195
1196      <div id="auth-forgot-link-wrap" style="text-align:center;margin-top:12px">
1197        <button type="button" id="auth-forgot-link" style="background:none;border:none;padding:0;font-size:13px;color:var(--text-3);text-decoration:underline;cursor:pointer;font-family:inherit">Forgot password?</button>
1198      </div>
1199
1200      <div class="auth-close-wrap">
1201        <button type="button" class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1202      </div>
1203    </div>
1204
1205    <div class="auth-modal-view" id="auth-view-forgot" hidden>
1206      <h3 class="auth-title">&gt; RESET_PASSWORD</h3>
1207      <p style="font-size:13px;color:var(--text-2);line-height:1.6;margin:8px 0 16px">Enter the email on your account and we'll send you a link to set a new password.</p>
1208      <form id="auth-forgot-form" autocomplete="on">
1209        <div class="form-group">
1210          <label class="form-label" for="auth-forgot-email">Email</label>
1211          <input class="form-input" type="email" id="auth-forgot-email" autocomplete="email" required>
1212        </div>
1213        <div class="form-error hidden" id="auth-forgot-error"></div>
1214        <button type="submit" class="btn btn-primary" id="auth-forgot-submit" style="width:100%">Send reset link</button>
1215      </form>
1216      <div class="auth-close-wrap">
1217        <button type="button" class="btn btn-secondary btn-sm" id="auth-forgot-back-btn">Back</button>
1218      </div>
1219    </div>
1220
1221    <div class="auth-modal-view" id="auth-view-reset-sent" hidden>
1222      <h3 class="auth-title">&gt; CHECK_YOUR_INBOX</h3>
1223      <p style="font-size:14px;color:var(--text-2);line-height:1.6;margin:8px 0 16px">
1224        If an account exists for <strong id="auth-reset-sent-email" style="color:var(--accent)"></strong>, we sent a password reset link.
1225      </p>
1226      <p style="font-size:13px;color:var(--text-3);line-height:1.6;margin:0 0 20px">
1227        Click the link in the email to choose a new password. If it doesn't arrive within a minute, check your spam folder.
1228      </p>
1229      <div class="modal-actions">
1230        <button type="button" class="btn btn-primary btn-sm" data-ot-h="h34">Done</button>
1231      </div>
1232    </div>
1233
1234    <div class="auth-modal-view" id="auth-view-set-new" hidden>
1235      <h3 class="auth-title">&gt; SET_NEW_PASSWORD</h3>
1236      <p style="font-size:13px;color:var(--text-2);line-height:1.6;margin:8px 0 16px">Choose a new password for your account.</p>
1237      <form id="auth-set-new-form" autocomplete="on">
1238        <div class="form-group">
1239          <label class="form-label" for="auth-new-password">New password</label>
1240          <div class="password-wrap">
1241            <input class="form-input" type="password" id="auth-new-password" autocomplete="new-password" minlength="8" required>
1242            <button type="button" class="password-toggle" data-target="auth-new-password" aria-label="Show password">
1243              <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1244              <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
1245            </button>
1246          </div>
1247          <p class="form-hint">At least 8 characters.</p>
1248        </div>
1249        <div class="form-group">
1250          <label class="form-label" for="auth-new-password-confirm">Confirm new password</label>
1251          <div class="password-wrap">
1252            <input class="form-input" type="password" id="auth-new-password-confirm" autocomplete="new-password" minlength="8" required>
1253            <button type="button" class="password-toggle" data-target="auth-new-password-confirm" aria-label="Show password">
1254              <svg class="eye-show" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1255              <svg class="eye-hide" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
1256            </button>
1257          </div>
1258        </div>
1259        <div class="form-error hidden" id="auth-set-new-error"></div>
1260        <button type="submit" class="btn btn-primary" id="auth-set-new-submit" style="width:100%">Save new password</button>
1261      </form>
1262    </div>
1263
1264    <div class="auth-modal-view" id="auth-view-sent" hidden>
1265      <h3 class="auth-title">&gt; CHECK_YOUR_INBOX</h3>
1266      <p style="font-size:14px;color:var(--text-2);line-height:1.6;margin:8px 0 16px">
1267        We sent a confirmation link to <strong id="auth-sent-email" style="color:var(--accent)"></strong>.
1268      </p>
1269      <p style="font-size:13px;color:var(--text-3);line-height:1.6;margin:0 0 20px">
1270        Click the link in the email to activate your account. If it doesn't arrive within a minute, check your spam folder.
1271      </p>
1272      <div class="modal-actions">
1273        <button type="button" class="btn btn-secondary btn-sm" id="auth-sent-back-btn">Back</button>
1274        <button type="button" class="btn btn-primary btn-sm" data-ot-h="h34">Done</button>
1275      </div>
1276    </div>
1277  </div>
1278</div>
1279
1280<div class="modal-overlay" id="modal-join-team">
1281  <div class="modal">
1282    <h3>Join a team</h3>
1283    <div class="form-group">
1284      <label class="form-label">Invite code</label>
1285      <input class="form-input" type="text" id="join-team-code" placeholder="e.g. a1b2c3d4" style="font-family:var(--mono)">
1286      <p class="form-hint">A code doesn't let you in by itself. Your request goes to the team's admins, and you get access when one of them accepts it.</p>
1287    </div>
1288    <div id="join-error" class="form-error hidden"></div>
1289    <div class="modal-actions">
1290      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1291      <button class="btn btn-primary btn-sm" data-ot-h="h63">Send request</button>
1292    </div>
1293  </div>
1294</div>
1295<!-- REVIEW MATCHES (bulk attendee/row → LinkedIn profile triage) -->
1296<div id="page-review" class="page">
1297  <div class="container" style="max-width:1040px;padding-top:24px;padding-bottom:64px">
1298    <a href="#" id="review-back" style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-2);margin-bottom:16px">&larr; Back to team</a>
1299    <div id="review-header" style="margin-bottom:20px">
1300      <h1 id="review-title" style="font-size:24px;font-weight:700;margin:0 0 6px">Review matches</h1>
1301      <p id="review-subtitle" style="font-size:13px;color:var(--text-3);margin:0"></p>
1302    </div>
1303    <div id="review-summary" class="card" style="padding:12px 14px;margin-bottom:16px;display:none">
1304      <div id="review-summary-body" style="font-size:13px"></div>
1305    </div>
1306    <div id="review-body">
1307      <p style="font-size:13px;color:var(--text-3)">Loading…</p>
1308    </div>
1309  </div>
1310</div>
1311
1312<!-- ENTRIES (saved lists of people + their entries, batched by week) -->
1313<div id="page-entries" class="page">
1314  <div class="entries-shell">
1315    <!-- Same camera as the workspace, bolted to this room's wall too. One
1316         host per page; pixel-eye.js builds an instance for each. -->
1317    <div class="pixel-eye-host is-entries"></div>
1318    <!-- LISTS OVERVIEW -->
1319    <div id="entries-lists-view">
1320      <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:4px">
1321        <h1 class="ws-title">Entry lists</h1>
1322        <button class="btn btn-sm btn-primary" id="entries-new-list-btn" data-ot-h="h64">+ New list</button>
1323      </div>
1324      <p style="font-size:13px;color:var(--text-3);margin:4px 0 16px">Pick yourself and/or teammates to review the outreach and connector activity they've logged.</p>
1325
1326      <div id="entries-lists-grid"></div>
1327    </div>
1328
1329    <!-- LIST DETAIL -->
1330    <!-- PIPELINE (dedicated board page for accepted people) -->
1331    <div id="entries-pipeline-view" style="display:none">
1332      <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:14px">
1333        <div>
1334          <a href="#" id="entries-pipeline-back" class="entries-nav-back">&larr; Back to list</a>
1335          <h1 id="entries-pipeline-title" style="font-size:36px;font-weight:700;line-height:1.1;margin:4px 0 0">Pipeline</h1>
1336          <p id="entries-pipeline-subtitle" style="font-size:13px;color:var(--text-3);margin:2px 0 0"></p>
1337        </div>
1338        <div class="pl-header-actions">
1339          <button class="btn btn-sm btn-secondary" id="entries-edit-list-btn">Edit list</button>
1340          <button class="btn btn-sm btn-secondary" style="color:var(--danger)" id="entries-delete-list-btn">Delete list</button>
1341        </div>
1342      </div>
1343      <div id="entries-pipeline-list"></div>
1344    </div>
1345  </div>
1346</div>
1347
1348<!-- NEW / EDIT ENTRY LIST -->
1349<div class="modal-overlay" id="modal-new-entry-list">
1350  <div class="modal" style="max-width:520px">
1351    <h3 id="entry-list-modal-title">New list</h3>
1352    <p style="font-size:12.5px;color:var(--text-2);line-height:1.5;margin:6px 0 14px">Pick yourself and/or teammates. The list shows the outreach and connector entries logged by the people you select.</p>
1353    <div class="form-group">
1354      <label class="form-label">List name</label>
1355      <input class="form-input" type="text" id="entry-list-name" placeholder="e.g. My pipeline review" autocomplete="off">
1356    </div>
1357    <div class="form-group">
1358      <label class="form-label">Whose activity?</label>
1359      <div id="entry-list-teammate-picker" style="display:flex;flex-wrap:wrap;gap:6px;margin-top:4px"></div>
1360    </div>
1361    <div class="form-group">
1362      <label class="form-label">Start from</label>
1363      <input class="form-input" type="text" id="entry-list-start-date" placeholder="Any time">
1364      <p style="font-size:11.5px;color:var(--text-3);margin-top:4px">Only entries from this date onward are included, and the list keeps building up from there. Leave empty for all history.</p>
1365    </div>
1366    <div class="form-group">
1367      <label class="form-label">Pipeline stages</label>
1368      <div id="entry-list-stages-editor"></div>
1369      <p style="font-size:11.5px;color:var(--text-3);margin-top:6px">Stages are shared by all the team's lists. People in a deleted stage move to Unsorted. The last stage is the closed stage — rename it freely; it always stays last.</p>
1370    </div>
1371    <div class="modal-actions">
1372      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1373      <button class="btn btn-primary btn-sm" id="entry-list-save-btn" data-ot-h="h65">Create list</button>
1374    </div>
1375  </div>
1376</div>
1377
1378<!-- EDIT PROFILE NAME -->
1379<div class="modal-overlay" id="modal-edit-team-name">
1380  <div class="modal" style="max-width:420px">
1381    <h3>Rename team</h3>
1382    <p style="font-size:12.5px;color:var(--text-2);line-height:1.55;margin:8px 0 14px">
1382Everyone in the team sees the new name. Your invite code doesn't change.</p>
1383    <div class="form-group">
1384      <label class="form-label">Team name</label>
1385      <input class="form-input" type="text" id="edit-team-name-input" autocomplete="off" maxlength="60" data-ot-h="h66">
1386    </div>
1387    <div class="modal-actions">
1388      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1389      <button class="btn btn-primary btn-sm" data-ot-h="h67">Save</button>
1390    </div>
1391  </div>
1392</div>
1393
1394<div class="modal-overlay" id="modal-edit-entry-name">
1395  <div class="modal" style="max-width:420px">
1396    <h3>Edit name</h3>
1397    <p style="font-size:12.5px;color:var(--text-2);line-height:1.55;margin:8px 0 14px">This name overrides the one fetched from LinkedIn / connectors everywhere it shows.</p>
1398    <div class="form-group">
1399      <label class="form-label">Name</label>
1400      <input class="form-input" type="text" id="edit-entry-name-input" autocomplete="off">
1401    </div>
1402    <div class="modal-actions">
1403      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1404      <button class="btn btn-secondary btn-sm" id="edit-entry-name-reset" data-ot-h="h68">Reset to fetched</button>
1405      <button class="btn btn-primary btn-sm" data-ot-h="h69">Save</button>
1406    </div>
1407  </div>
1408</div>
1409
1410<!-- MERGE THIS MANUAL PROFILE INTO AN EXISTING PERSON -->
1411<div class="modal-overlay" id="modal-merge-profile">
1412  <div class="modal" style="max-width:460px">
1413    <h3>Merge into another person</h3>
1414    <p style="font-size:12.5px;color:var(--text-2);line-height:1.55;margin:8px 0 10px">Search for someone already in your team's people and merge this person into them. Every outreach, comment, email, marking, event link and list membership moves across, and this entry stops existing as a separate person.</p>
1415    <p class="modal-warning">This can't be undone. There is no way to split the two apart afterwards, and the merged-away entry can't be restored — so be sure it's the same person before you pick them.</p>
1416    <input class="form-input" type="text" id="merge-search-input" placeholder="Search people…" autocomplete="off">
1417    <div id="merge-search-results" style="max-height:320px;overflow:auto;margin-top:10px"></div>
1418    <div class="modal-actions">
1419      <button class="btn btn-secondary btn-sm" data-ot-h="h34">Cancel</button>
1420    </div>
1421  </div>
1422</div>
1423
1424<!-- INSTALL EXTENSION -->
1425<div id="page-install" class="page">
1426  <div class="container install-shell">
1427    <a href="#" class="profile-back-link" data-ot-h="h70">&larr; Back</a>
1428    <h1 class="ws-title install-title">Install the extension</h1>
1429
1430    <!-- Two cards, two steps. The browser list was six bullets for a fact
1431         that fits in a sentence, and the tips were paragraphs about a
1432         puzzle-piece icon. -->
1433    <div class="card install-step">
1434      <h3>1 &middot; Add it to your browser</h3>
1435      <p>Open the Chrome Web Store listing and add it. Chrome, Edge, Brave, Arc, Opera and Vivaldi all work; Firefox, Safari and mobile don't.</p>
1436      <a href="https://chromewebstore.google.com/detail/orwells-bad-dream/fjahimldocnmhhalgbnjmdgdlkmmobhk" target="_blank" rel="noopener" class="btn btn-primary btn-sm">Open Chrome Web Store</a>
1437    </div>
1438
1439    <div class="card install-step">
1440      <h3>2 &middot; Sign in</h3>
1441      <p>Click the extension's icon in your toolbar and sign in with this account. Then open any LinkedIn profile, or your inbox, and it starts tracking.</p>
1442    </div>
1443  </div>
1444</div>
1445
1446<!-- PRIVACY POLICY -->
1447<div id="page-privacy" class="page">
1448  <div class="container" style="max-width:720px;padding-top:48px;padding-bottom:64px">
1449    <a href="#" data-ot-h="h70" style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-2);margin-bottom:24px">&larr; Back</a>
1450    <h1 style="font-size:28px;font-weight:700;margin-bottom:4px">Privacy Policy</h1>
1451    <p style="color:var(--text-3);font-size:13px;margin-bottom:32px">Effective Date: September 6, 2026</p>
1452
1453    <div style="background:var(--accent-bg);border-radius:var(--radius-lg);padding:20px 24px;margin-bottom:32px;font-size:14px;color:var(--accent-text);line-height:1.7">
1454      <strong>In short:</strong> Orwell's bad dream is a collaboration tool for small teams doing outreach &mdash; on LinkedIn, over email, and by hand. We store the outreach history, comments, and contact details you enter, and &mdash; only if you choose to connect them &mdash; data from the HubSpot, Luma, Google Sheets, Excel or Attio accounts you integrate. We never read your LinkedIn messages or connections, and we never read your mailbox: the extension only sees the thread you have open.
1455    </div>
1456
1457    <div style="background:hsla(30,85%,50%,.08);border:1px solid hsla(30,85%,50%,.3);border-radius:var(--radius-lg);padding:16px 20px;margin-bottom:32px;font-size:13.5px;color:var(--text);line-height:1.65">
1458      <strong>If you're not a user of this App &mdash; i.e. you're looking for this policy because your name or email appears in one of our teams' records &mdash;</strong> please read the separate <a href="/privacy/profile-data" style="color:var(--accent);text-decoration:underline">Notice for people whose data we hold</a>. It explains what we hold about people who never signed up themselves, the legal basis for processing it, how long we keep it, and how to exercise your access / objection / erasure rights.
1459    </div>
1460
1461    <div class="privacy-content">
1462      <h2>1. Data Controller</h2>
1463      <p>Orwell's bad dream is the data controller responsible for your personal data under this policy. You can reach the data controller at <
1463a href="mailto:[email protected]" style="color:var(--accent)">[email protected]</a>.</p>
1464
1465      <h2>2. What This App Does</h2>
1466      <p>Orwell's bad dream ("the App", "we", "our") is a collaboration tool for small teams. It lets you and your teammates keep a shared record of outreach &mdash; who you contacted, through which channel, when, with what comments and contact details &mdash; and surfaces that shared record when any of you opens that person's LinkedIn profile or an email thread they are on. You can group people into lists and move them through stages, tag each other in comments, and mark profiles with your team's own labels. Optionally, you can connect your own HubSpot, Luma, Google Sheets, Microsoft Excel or Attio accounts so matching data from those systems appears on the profile as well.</p>
1467
1468      <h2>3. Legal Basis for Processing (GDPR Article 6)</h2>
1469      <p>The App processes two kinds of personal data under two different legal bases:</p>
1470      <p><strong>3.1 &mdash; Your own data, as a user of the App.</strong> When you sign up, connect an integration, or use the App's features, processing is carried out to perform the contract between you and us (Article 6(1)(b)) and &mdash; for each optional integration &mdash; on the basis of your explicit consent granted at the moment you connect it (Article 6(1)(a)). You can withdraw that consent at any time by disconnecting the integration, leaving the team, or deleting your account (see Section 8).</p>
1471      <p><strong>3.2 &mdash; Data about people who are not users of the App.</strong> The App's core function is to let a team keep a shared record of professional outreach. That record necessarily contains personal data about the people being contacted (public LinkedIn profile identifier, display name, any email a teammate types in or that arrives via a connected integration, any notes teammates write, and a timestamped log of when any team member opened that person's LinkedIn profile page). These data subjects have not signed up themselves and cannot give prior consent. We rely on <strong>legitimate interests (Article 6(1)(f))</strong> to process this data &mdash; specifically, the legitimate interest of the App's users and their teams in coordinating legitimate professional outreach (recruiting, sales, partnerships, fundraising) and avoiding duplicate or conflicting contact. We have carried out the balancing test required by Article 6(1)(f); the summary is in the <a href="#lia-appendix" style="color:var(--accent)">Legitimate-Interests Assessment</a> at the end of this policy. People whose data we hold under this basis have the right to object to the processing (Article 21) and the right to erasure (Article 17). See the <a href="/privacy/profile-data" style="color:var(--accent)">Notice for people whose data we hold</a>.</p>
1472      <p><strong>3.3 &mdash; Feature-specific transparency for users.</strong> Because the App's core feature is team-visible outreach and profile-visit tracking, every user must actively accept these terms before the App stores any outreach or profile-visit data. This means: <em>
1472when you use the Chrome extension on a LinkedIn profile page, the App writes a row to your team's shared database recording that you visited that profile, along with the profile's public identifier, its display name, and the time.</em> Your teammates can see these rows for the whole team. This is by design and is the core reason the App exists. If you are not comfortable with this, do not sign up.</p>
1473
1474      <h2>4. Information We Collect</h2>
1475      <p><strong>Account data.</strong> When you sign in via LinkedIn OpenID Connect we receive your name, email address, and LinkedIn profile identifier. When you sign in with email and password, we store that email and a hashed password (via our authentication provider, Supabase). You may optionally upload a profile avatar.</p>
1476      <p><strong>Content you create.</strong> Comments you write on profiles, emails and phone numbers you add for a profile, outreach entries (profile identifier, channel, date, note, who logged it), profile markings &mdash; your team's own tags &mdash; pipeline lists and the stage each person sits in, and teams you create or join. When you tag a teammate by name in a comment or an outreach note, we store that mention: who was tagged, by whom, on which profile, and an excerpt of the text, so it can appear in their notifications.</p>
1477      <p><strong>Integration credentials.</strong> Stored encrypted in Supabase Vault and visible to the App only through server-side functions scoped to your team:</p>
1478      <ul>
1479        <li>HubSpot &mdash; a Private App access token you generate, with read scopes for contacts and deals.</li>
1480        <li>Luma &mdash; an API key and, optionally, an iCal subscription URL.</li>
1481        <li>Google Sheets &mdash; the Google Picker and Sheets APIs with the <code>drive.file</code> scope, which only grants access to the specific file you pick. Enabling background sync grants offline access once and stores a refresh token in Vault; without it, nothing long-lived is kept.</li>
1482        <li>Microsoft Excel &mdash; a refresh token for your Microsoft account, stored in Vault, used to read the workbooks you pick from OneDrive or SharePoint.</li>
1483        <li>Attio &mdash; an access token you generate in your Attio workspace. The App only reads from Attio and never writes to it.</li>
1484      </ul>
1485      <p><strong>Data synced via integrations.</strong> Only information already visible inside the connected account. Specifically: HubSpot contacts and deal-stage labels; Luma events and attendee lists (from the API or from CSV exports you upload); rows from the single Google Sheets tab or Excel worksheet you pick, plus the headers and an optional custom icon you upload for the connector; and people and their details from your Attio workspace. Some of these sources carry phone numbers, which are stored and shown on the profile alongside emails.</p>
1486      <p><strong>Profile photos.</strong> When a teammate opens someone's LinkedIn profile with the extension, the App records the photo's URL so the person is recognisable in the App. Those URLs expire, so we copy the image into our own storage on the same EU infrastructure and serve it from there. It is deleted along with the rest of a profile's data when the profile is deleted or the team is removed.</p>
1487      <p>We do not read, store, or access your LinkedIn messages, your connections list, your newsfeed, or any LinkedIn content beyond the public profile identifier of people you manually log.</p>
1488      <p><strong>Diagnostic data.</strong> When the App's website or extension throws an unhandled exception in your browser, we capture the error message, JavaScript stack trace, the page URL where it happened, and your browser user-agent string, and store them so we can debug. The capture is tied to your account so we can find it; it is not used for analytics, profiling, or anything other than debugging. You can delete this data at any time along with the rest of your account (Section 8). No diagnostic data is captured before you sign in.</p>
1489
1490      <h2>5. How We Use Your Information</h2>
1491      <p>Your data is used only to (a) authenticate you and maintain your session, (b) show outreach history, notes, emails, and integration data to you and your teammates on matching LinkedIn profiles and in the team dashboard, and (c) enable you to export, edit, or delete your data. We do not use your data for advertising, profiling, automated decision-making, model training, or any purpose other than the features described.</p>
1492
1493      <h2>6. Data Sharing &amp; International Transfers</h2>
1494      <p>We do not sell, rent, or share your personal data with third parties. Data is processed on our behalf by Supabase (database, storage, and authentication) on their EU-region infrastru
1494cture. Data you bring in via an integration is fetched directly from the third-party service (HubSpot, Luma, Google) to your browser, and then stored in our database on the same EU infrastructure. We disclose data only where strictly required by law.</p>
1495      <p><strong>6.1 &mdash; Data stays inside the team that already had it.</strong> The whole point of the App is to surface what <em>your team</em> already knows, not to widen that circle. Every record in the App &mdash; outreaches, notes, emails, profile visits, and every row synced from a connected HubSpot, Luma, or Google Sheets account &mdash; is scoped to a single team and visible only to members of that team. Row-level-security policies in our database enforce this, not just application code. If you connect your company's HubSpot to a team, the contacts from that HubSpot stay inside <em>that</em> team: we do not copy them to any other team, we do not build a cross-team contact graph, and we do not expose a contact's data to any user who was not already a member of the team you brought it into. If you or a teammate joins a second team, the records from the first team do not follow; each team's database is walled off from the others.</p>
1496      <p><strong>6.2 &mdash; You only see what the team already has on the person.</strong> When the extension lights up on a LinkedIn profile, everything it shows you &mdash; the outreach history, the notes, the HubSpot contact card, the Luma check-ins, the Google Sheets row &mdash; was already in your team's own records before you opened that profile. The App does not enrich profiles with data bought from brokers, scraped from elsewhere on LinkedIn, or fetched from any other team's database. If there is nothing in your team's records for that person, the extension stays empty.</p>
1497      <p><strong>6.3 &mdash; You must accept this scope before the App stores anything about anyone.</strong> By creating an account and accepting the consent gate that appears on first sign-in, you agree not to use the App to expose records to anyone who is not already a member of the team those records belong to. You agree not to copy data between unrelated teams, not to invite strangers into a team solely to leak its records to them, and not to use the App to enlarge the circle of people who can see a given data subject's information beyond what was already the case before the App was involved.</p>
1498      <p><strong>6.4 &mdash; Reporting misuse; enforcement.</strong> If you see someone using Orwell's bad dream to widen the circle beyond the team that already held the data &mdash; e.g. moving contacts between unrelated teams, creating a team for the sole purpose of exposing data to outsiders, or otherwise circumventing the team-scoping described above &mdash; please report it to <a href="mailto:[email protected]" style="color:var(--accent)">[email protected]</a> with as much detail as you can share. We will investigate every report. Teams found to be misusing the App in this way will have their data deleted and their members banned from the App; individual users who organise such misuse will be banned as well. Enforcement is at our sole discretion and does not create any liability to the banned parties.</p>
1499
1500      <h2>7. Data Retention</h2>
1501      <p>We retain your data while your account is active. When you disconnect an integration, the stored credentials and synced data for that integration are deleted immediately. When you leave a team or an admin removes you from it, what you logged stays with the team and remains attributed to you — it is the team's record of who it has spoken to, and losing it because someone changed jobs would destroy the thing the App exists to keep. A team admin can delete a member's contributions at any time, either when removing them or afterwards, and will do so on request. Profile view history is collapsed nightly: we keep the most recent view of each person by each teammate and delete the repeat visits behind it. When you delete your account (Account Settings &rarr; Delete account), all remaining personal data is permanently erased within 30 days, in every team you belonged to. We do not keep backups of deleted accounts.</p>
1502
1503      <h2>8. Your Rights Under GDPR</h2>
1504      <p>Under the General Data Protection Regulation, you have the right to:</p>
1505      <ul>
1506        <li>Access all personal data we hold about you (Article 15)</li>
1507        <li>Rectify any inaccurate or incomplete data (Article 16)</li>
1508        <li>Erase your data and account entirely &mdash; "right to be forgotten" (Article 17)</li>
1509        <li>Restrict processing of your data (Article 18)</li>
1510        <li>Receive your data in a portable, machine-readable format (Article 20)</li>
1511        <li>Object to processing at any time (Article 21)</li>
1512        <li>Withdraw consent at any time, without affecting the lawfulness of processing carried out before withdrawal (Article 7(3))</li>
1513      </ul>
1514      <p>
1514Send requests to <a href="mailto:[email protected]" style="color:var(--accent)">[email protected]</a>; we will respond within 30 days. If you believe your data-protection rights have been violated, you can lodge a complaint with your local supervisory authority. In Denmark, this is Datatilsynet (datatilsynet.dk).</p>
1515
1516      <h2>9. Cookies &amp; Tracking</h2>
1517      <p>The App uses only strictly necessary cookies and browser storage for authentication and session management. We do not use tracking, analytics, or advertising cookies.</p>
1518
1519      <h2>10. Third-Party Services</h2>
1520      <p><strong>Authentication.</strong> You can sign in with LinkedIn OpenID Connect or with email and password. LinkedIn processes your data under its own privacy policy (linkedin.com/legal/privacy-policy). Both methods are handled via Supabase Auth.</p>
1521      <p><strong>Infrastructure.</strong> Supabase (supabase.com) hosts our database, storage, and secrets vault in the EU. See Supabase's Data Processing Agreement for details.</p>
1522      <p><strong>Email summarisation.</strong> Anthropic (anthropic.com) processes the text of an email <em>at the moment you press &ldquo;Track&rdquo;</em>, to produce the three-bullet summary offered as your note. The text is sent for that single request and is not retained by us or used by Anthropic to train models. If you never press Track, no email text is ever sent. This is the only sub-processor that sees message content, and it sees only messages you explicitly choose to track.</p>
1523      <p><strong>Optional integrations.</strong> Only active if you connect them:</p>
1524      <ul>
1525        <li><em>HubSpot</em> (hubspot.com) &mdash; connected via a Private App token you generate. We read contacts and deal-stage data your HubSpot account has access to.</li>
1526        <li><em>Luma</em> (lu.ma) &mdash; connected via an API key you generate, plus an optional iCal subscription URL for co-hosted events. We read events and attendee lists visible to that Luma account.</li>
1527        <li><em>Google</em> (google.com) &mdash; for the Google Sheets connector, the Picker and Sheets APIs are used with the <code>drive.file</code> scope. Google sees only the file you explicitly pick. A refresh token is stored only if you enable background syncing.</li>
1528        <li><em>Microsoft</em> (microsoft.com) &mdash; for the Excel connector. We read the workbooks you pick from OneDrive or SharePoint and nothing else in your account.</li>
1529        <li><em>Attio</em> (attio.com) &mdash; connected via an access token you generate. We read people and their details from your workspace, and never write to it.</li>
1530      </ul>
1531      <p><strong>Payments.</strong> Paid teams are billed through Stripe (stripe.com), which handles the payment itself: card details go to Stripe and never reach us. We store the identifiers Stripe gives us for your team's customer and subscription, the plan status, and how many seats the team is using.</p>
1532      <p><strong>AI assistants.</strong> The App exposes an optional read-only MCP server, so you can ask an assistant such as Claude questions about your own team's records. It runs every query as you, under the same row-level-security rules as the App, so it can never read a team you are not in. It is only reachable if you connect a client to it, and it cannot write anything.</p>
1533      <p>The Chrome extension runs on <code>linkedin.com/in/*</code> profile pages and, for email tracking, on Gmail and Outlook Web. On LinkedIn it reads the profile identifier in the URL, plus the displayed name and profile photo, so it can look up and show your team's records for that person. It does not read your LinkedIn messages, your connections, or your newsfeed.</p>
1534      <p><strong>Email tracking.</strong> In Gmail and Outlook the extension adds a &ldquo;Track&rdquo; button next to Send. Until you press it, the extension reads nothing from your mailbox and sends nothing anywhere. When you do press it, and only for that one message, the extension reads the recipient address, the subject and the body of the email <em>you are writing</em>, and sends them to our server, which passes them to Anthropic&rsquo;s Claude API to produce a three-bullet summary suggested as your outreach note. <strong>The email body is never stored</strong> &mdash; not by us and not by Anthropic for training; it is summarised in transit and discarded. Only the three bullets are saved, and only if you send the email. The extension never reads your inbox, other messages, drafts you have not tracked, or any email you receive.</p>
1535
1536      <h2>11. Security</h2>
1537      <p>We use encrypted transport (TLS), encrypted storage at rest, Supabase Vault for integration secrets, and row-level-security policies so each team's data is only accessible to that team's members. Access is limited to the minimum necessary. While no system is completely secure, we take reasonable technical and organisational measures to safeguard your personal data in accordance with GDPR Article 32.</p>
1538
1539      <h2>12. Children</h2>
1540      <p>The App is not directed at anyone under the age of 16. We do not knowingly collect personal data from children. If we become aware that a child's data has been collected, we will delete it immediately.</p>
1541
1542      <h2>13. Changes to This Policy</h2>
1543      <p>We may update this Privacy Policy from time to time. If we make material changes, we will notify you through the App before they take effect. The "Effective Date" at the top will always reflect the latest revision. Continued use after changes take effect constitutes acceptance of the revised policy.</p>
1544
1545      <h2 id="lia-appendix">Appendix A &mdash; Legitimate-Interests Assessment</h2>
1546      <p style="color:var(--text-3);font-size:13px">Summary balancing test for processing third-party personal data under Article 6(1)(f). Fuller documentation is held on record and is available on request.</p>
1547      <h3 style="font-size:15px;margin-top:20px">Purpose test &mdash; is the interest legitimate?</h3>
1548      <p>The App exists so that a small team doing legitimate professional outreach (recruitment, sales, partnerships, fundraising, investor relations) can coordinate: see who on the team has already contacted a given person, what was said, which events or deals the person came from, and avoid the embarrassment or harm of duplicate or contradictory contact. Cross-referencing the team's own records against the LinkedIn profile the user is looking at is the single feature users rely on daily. Teams use the App instead of (or alongside) a full CRM precisely because a CRM does not surface "has anyone on my team already talked to this person?" at the exact moment a user is on the profile. That interest is lawful, specific, and articulated.</p>
1549      <h3 style="font-size:15px;margin-top:20px">Necessity test &mdash; is processing necessary for that interest?</h3>
1550      <p>The processing is limited to what the feature needs: a public LinkedIn identifier, a display name, a timestamp for profile visits, and whatever a teammate chooses to type (outreach notes, emails pasted from conversations, linkages to attendees or contacts the team already holds). We do not read the profile page's content, messages, or connections list, and we do not enrich the profile with data bought from third parties. A less data-hungry alternative (e.g. manually keeping a spreadsheet) is possible but does not deliver the same coordination benefit and, notably, stores comparable data in a less secure environment. Removing the shared record would remove the App's core utility.</p>
1551      <h3 style="font-size:15px;margin-top:20px">Balancing test &mdash; do the data subject's rights override the interest?</h3>
1552      <p>Factors weighing <em>in favour</em> of processing:</p>
1553      <ul>
1554        <li>The categories processed are non-sensitive business-contact data. No special-category data under Article 9; no criminal-convictions data under Article 10.</li>
1555        <li>The LinkedIn identifier and display name are, by the data subject's own choice, public on LinkedIn; the data subject made them visible in a professional context specifically to be contacted for professional purposes.</li>
1556        <li>Access is strictly limited &mdash; each row is visible only to the members of one team, enforced at the database layer via row-level security, not merely application-level checks.</li>
1557        <li>No automated decision-making, profiling, scoring, advertising, or model training is performed against the data.</li>
1558        <li>
1558Data is hosted in the EU on infrastructure subject to GDPR; credentials for integrations are held in an encrypted vault.</li>
1559      </ul>
1560      <p>Factors weighing <em>against</em> processing:</p>
1561      <ul>
1562        <li>The data subject has not been told directly that we hold their data, so there is an information asymmetry mitigated by (i) the public Article 14 notice at <a href="/privacy/profile-data" style="color:var(--accent)">orwellsbaddream.com/privacy/profile-data</a>, (ii) a documented and accessible objection and erasure workflow, and (iii) a contact address they can reach directly.</li>
1563        <li>Profile-visit logging in particular can feel surveillance-like. We limit this by capping it at one view per profile per user per 24 hours, keeping visits visible only inside the one team, and letting the team member delete their own view record at any time.</li>
1564        <li>If a data subject works in a field where LinkedIn activity is sensitive (e.g. job-seeking in confidence), the logging of visits could prejudice them if the data were leaked. We consider this risk real but low-probability given EU hosting, RLS, and the absence of third-party sharing.</li>
1565      </ul>
1566      <p><strong>Conclusion.</strong> On balance, the legitimate interest of teams in coordinating legitimate professional outreach, combined with the limited scope, non-sensitive categories, EU hosting, strict access controls, and the availability of an unconditional right to object and erase, outweighs the interference with the data subject's rights. Processing is therefore lawful under Article 6(1)(f). A data subject who disagrees with that balance on their own facts can exercise their Article 21 right to object, which we honour without precondition.</p>
1567      <p style="font-size:13px;color:var(--text-3)">Last reviewed: April 22, 2026. This assessment will be re-reviewed whenever a materially new processing activity is introduced.</p>
1568    </div>
1569  </div>
1570</div>
1571
1572<!-- ART 14 NOTICE FOR DATA SUBJECTS WHO ARE NOT USERS OF THE APP -->
1573<div id="page-privacy-profile-data" class="page">
1574  <div class="container" style="max-width:720px;padding-top:48px;padding-bottom:64px">
1575    <a href="#" data-ot-h="h70" style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-2);margin-bottom:24px">&larr; Back</a>
1576    <h1 style="font-size:28px;font-weight:700;margin-bottom:4px">Notice for people whose data we hold</h1>
1577    <p style="color:var(--text-3);font-size:13px;margin-bottom:32px">Published under GDPR Article 14. Effective Date: September 6, 2026</p>
1578
1579    <div style="background:var(--accent-bg);border-radius:var(--radius-lg);padding:20px 24px;margin-bottom:32px;font-size:14px;color:var(--accent-text);line-height:1.7">
1580      <strong>This notice is for you if</strong> you are not a user of Orwell's bad dream yourself, but your name, LinkedIn profile identifier, or email may have been added to one of our teams' shared records by a user of the App. It explains in plain language what we may hold, why, for how long, and how to have it corrected or removed.
1581    </div>
1582
1583    <div class="privacy-content">
1584      <h2>1. Who holds your data</h2>
1585      <p>Orwell's bad dream is the data controller. Contact: <a href="mailto:[email protected]" style="color:var(--accent)">[email protected]</a>. The controller is established in Denmark; the supervisory authority is Datatilsynet (<a href="https://www.datatilsynet.dk" style="color:var(--accent)" target="_blank" rel="noopener">datatilsynet.dk</a>), where you can lodge a complaint at any time.</p>
1586
1587      <h2>2. What data may exist</h2>
1588      <p>A team using the App may hold some or all of the following about you. We say "may" because the App is team-driven &mdash; what is stored depends entirely on what a given team has entered or synced:</p>
1589      <ul>
1590        <li><strong>Your public LinkedIn identifier</strong> (the slug from <code>linkedin.com/in/&lt;slug&gt;</code>) and your <strong>display name</strong> as shown on LinkedIn.</li>
1591        <li><strong>A log of when members of that team opened your LinkedIn profile.</strong> At most one row per team member per day, with a timestamp and the team member's name.</li>
1592        <li><strong>Outreach entries and notes</strong> a team member typed about a conversation with you (e.g. "Reached out on LinkedIn Apr 3, replied interested in a chat").<
1592/li>
1593        <li><strong>Email addresses and phone numbers</strong> linked to your profile by a team member, either typed manually or mirrored from a third-party source (see next point).</li>
1594        <li><strong>Your profile photo</strong> as it appeared on the LinkedIn page a team member opened. LinkedIn's own image links expire, so a copy is kept on the App's storage in the EU and shown next to your name inside that team.</li>
1595        <li><strong>Labels the team applies to you</strong> &mdash; its own tags (each team defines its own), a list it has placed you on, and the stage of that list you sit in. These are the team's opinions about its own process rather than facts about you, but they are personal data because they are attached to you.</li>
1596        <li><strong>Data from a third-party source the team has connected</strong> and where your record appears: a Luma event you attended as a guest of that team's host; a HubSpot contact record the team holds; a row in a Google Sheet or an Excel workbook the team maintains; a person record in the team's Attio workspace. Only the fields of that source are stored, and only for the specific object that matched your LinkedIn profile.</li>
1597        <li><strong>A record that a team member emailed you</strong>, if they chose to track that email: your email address, your display name as it appeared in the message header, the date, and a short note &mdash; often a three-bullet summary &mdash; of what <em>they</em> wrote to you. If we hold no LinkedIn profile for you, your email address itself is the identifier your record is filed under.</li>
1598        <li><strong>Mentions between team members that name you.</strong> If one member tags another in a comment or a note written on your record, we store who was tagged, by whom, which record it was written on, and an excerpt of that text, so it can be shown to them as a notification.</li>
1599      </ul>
1600      <p>We do <strong>not</strong> collect or store: your LinkedIn messages, your connections list, your newsfeed, anything you did not publish on your LinkedIn profile, the contents of any mailbox, or any inference / scoring / profiling about you.</p>
1601      <p>On email specifically: we store only a summary of the outbound message a team member sent <em>to</em> you, and only when they chose to track it. <strong>We never receive, read or store email you send</strong>, your replies, your inbox, or any message a team member did not explicitly track. The full text of a tracked message is used once, in transit, to generate the summary and is then discarded &mdash; it is never stored and never used to train any model.</p>
1602
1603      <h2>3. Where your data came from</h2>
1604      <p>Depending on the team, your data may have come from: (a) the public LinkedIn profile page a team member opened, (b) a Luma attendee list the team's Luma account has access to because you RSVPed to or attended an event, (c) a HubSpot CRM record the team maintains, (d) a row the team pasted or synced from a Google Sheet, (e) something a team member typed manually, or (f) an email a team member sent you from Gmail or Outlook and chose to track, or (g) a person record in the team's Attio workspace.</p>
1605
1606      <h2>4. Why we process it &mdash; legal basis</h2>
1607      <p>Processing is carried out on the basis of <strong>legitimate interests</strong> (GDPR Article 6(1)(f)) &mdash; specifically, the interest of the App's users and their teams in coordinating legitimate professional outreach (recruiting, sales, partnerships, fundraising, investor relations). The documented balancing test is included in our main <a href="/privacy#lia-appendix" style="color:var(--accent)">Privacy Policy appendix</a>. We do not use your data for advertising, profiling, automated decision-making, or AI/ML training.</p>
1608
1609      <h2>5. Who can see it</h2>
1610      <p>Only the members of the single team that entered or synced the data. Teams in the App are isolated at the database layer by row-level security; there is no "cross-team" index and no public directory. We do not sell, rent, or share your data with third parties. The data is stored by Supabase (a data-processing provider) on EU infrastru
1610cture under a Data Processing Agreement.</p>
1611
1612      <h2>6. How long it is kept</h2>
1613      <p>A team's records about you are kept while that team uses the App. A team's data is deleted in the following cases:</p>
1614      <ul>
1615        <li>A team admin removes the member who entered it and chooses to delete their data with them. Removal on its own does not: what a member logged stays with the team.</li>
1616        <li>A team admin disconnects an integration &mdash; the data synced via that integration is deleted.</li>
1617        <li>A team admin deletes the team &mdash; all data within it is deleted.</li>
1618        <li>You exercise your right to erasure (below).</li>
1619      </ul>
1620      <p>We do not keep backups of deleted records.</p>
1621
1622      <h2>7. Your rights and how to exercise them</h2>
1623      <p>Under GDPR you have the right to:</p>
1624      <ul>
1625        <li><strong>Access</strong> the personal data we hold about you (Article 15).</li>
1626        <li><strong>Rectify</strong> inaccurate data (Article 16).</li>
1627        <li><strong>Erase</strong> your data and ask us not to process it further (Article 17).</li>
1628        <li><strong>Restrict</strong> processing while a request is being resolved (Article 18).</li>
1629        <li><strong>Object</strong> to processing based on legitimate interests &mdash; at any time, without justification. When you object, we stop processing unless we can show compelling legitimate grounds that override your rights, which for this App we treat as a high bar (Article 21).</li>
1630      </ul>
1631      <p>To exercise any of these, email <a href="mailto:[email protected]" style="color:var(--accent)">[email protected]</a> with your LinkedIn profile URL so we can identify your records. We will respond within 30 days. If you are not satisfied with our response you can complain to Datatilsynet (<a href="https://www.datatilsynet.dk" style="color:var(--accent)" target="_blank" rel="noopener">datatilsynet.dk</a>) or the supervisory authority in your EU country of residence.</p>
1632
1633      <h2>8. Changes to this notice</h2>
1634      <p>If we materially change the way we process third-party data, we will update this notice and log the change. The "Effective Date" above reflects the latest revision.</p>
1635    </div>
1636  </div>
1637</div>
1638
1639<!-- TERMS OF USE -->
1640<div id="page-terms" class="page">
1641  <div class="container" style="max-width:720px;padding-top:48px;padding-bottom:64px">
1642    <a href="#" data-ot-h="h70" style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-2);margin-bottom:24px">&larr; Back</a>
1643    <h1 style="font-size:28px;font-weight:700;margin-bottom:4px">Terms of Use</h1>
1644    <p style="color:var(--text-3);font-size:13px;margin-bottom:32px">Effective Date: September 6, 2026</p>
1645
1646    <div style="background:var(--accent-bg);border-radius:var(--radius-lg);padding:20px 24px;margin-bottom:16px;font-size:14px;color:var(--accent-text);line-height:1.7">
1647      <strong>In short:</strong> Orwell's bad dream is a small-team collaboration tool for LinkedIn outreach. You can optionally connect HubSpot, Luma, and Google Sheets so relevant data from those accounts shows up on matching LinkedIn profiles. The App does not interact with LinkedIn beyond the profile identifiers you manually log.
1648    </div>
1649
1650    <div style="background:hsla(30,85%,50%,.08);border:1px solid hsla(30,85%,50%,.3);border-radius:var(--radius-lg);padding:16px 20px;margin-bottom:32px;font-size:13.5px;color:var(--text);line-height:1.65">
1651      <strong>Please read this before you sign up.</strong> The App's core feature is a <em>team-visible log of every LinkedIn profile you open</em>
1651. When you use the Chrome extension on a LinkedIn profile page, the App writes a row into your team's shared database with that profile's public identifier, its display name, and the time &mdash; at most once per profile per person per 24 hours. Your teammates can see these rows for the whole team. This is on purpose; it is why the App exists. If you are not comfortable with your teammates seeing which LinkedIn profiles you open, do <strong>not</strong> sign up. You can delete individual view rows and your whole account at any time (see Section 8 and the <a href="#" data-ot-h="h16" style="color:var(--accent);text-decoration:underline">Privacy Policy</a>).
1652    </div>
1653
1654    <div class="privacy-content">
1655      <h2>1. Who These Terms Apply To</h2>
1656      <p>These Terms of Use ("Terms") govern your use of the Orwell's bad dream website and Chrome extension (together, "the App"). By creating an account, accepting these Terms in the signup flow, or otherwise using the App, you agree to be bound by them. If you do not agree, do not create an account or use the App.</p>
1657
1658      <h2>2. What the App Is For</h2>
1659      <p>The App is a lightweight collaboration tool for small teams. It lets you and your teammates keep a shared record of LinkedIn outreach, notes, and contact emails per profile, and surfaces that shared record when any of you visits that profile. You can optionally connect your own HubSpot, Luma, and Google Sheets accounts so matching data from those systems also appears on the profile. The App is intended for legitimate professional outreach (recruiting, sales, partnerships, fundraising) by users who have the right to perform that outreach and to read the data they connect.</p>
1660
1661      <h2>3. Limits and What the App Is NOT</h2>
1662      <p>The App is intentionally limited in scope. The following are <strong>explicitly outside the scope of the App</strong>:</p>
1663      <ul>
1664        <li>The App does <strong>not</strong> read, send, scrape, or interact with LinkedIn messages.</li>
1665        <li>The App does <strong>not</strong> read or copy your LinkedIn connections list.</li>
1666        <li>The App does <strong>not</strong> automate any action on LinkedIn (no auto-connecting, auto-messaging, or similar).</li>
1667        <li>The App does <strong>not</strong> bypass LinkedIn rate limits, terms, or technical protections.</li>
1668        <li>The App does <strong>not</strong> resell, share, or expose your data to third parties.</li>
1669        <li>The App does <strong>not</strong> profile you or run analytics on your behaviour.</li>
1670        <li>The App does <strong>not</strong> read your inbox, your received mail, or any draft you have not explicitly chosen to track.</li>
1671        <li>The App does <strong>not</strong> send an email for you, or alter one you are writing.</li>
1672        <li>Your data is <strong>not</strong> used to train machine-learning models. The one place a model sees your content at all is the optional email summary: when <em>you</em> press &ldquo;Track&rdquo;, that single email is sent to Anthropic&rsquo;s Claude API to draft three bullet points, then discarded. See the <a href="#" data-ot-h="h16">Privacy Policy</a>.</li>
1673      </ul>
1674      <p>Data you log or connect is visible only to you and to the members of teams you join. Nothing is published anywhere else.</p>
1675
1676      <h2>4. Your Account and Sign-In</h2>
1677      <p>You can create an account by signing in with LinkedIn OpenID Connect, with Google, or with an email and password. Whichever you use, we receive only the identity fields described in the <a href="#" data-ot-h="h16" style="color:var(--accent)">Privacy Policy</a>. You are responsible for keeping your sign-in credentials secure; an attacker who controls your LinkedIn or Google login, or your email inbox, can sign in to the App as you. You may add a password to an account created through a provider; doing so requires a code sent to the address on that account.</p>
1678
1679      <h2>5. Acceptable Use</h2>
1680      <p>You agree to use the App only for lawful, legitimate professional outreach activities. You agree <strong>not</strong> to:</p>
1681      <ul>
1682        <li>Use the App to harass, stalk, defame, or harm any person.</li>
1683        <li>Log false outreach data, impersonate someone, or misrepresent who reached out.</li>
1684        <li>Use the App in violation of LinkedIn's terms of service or applicable law.</li>
1685        <li>Attempt to access teams, data, or accounts that do not belong to you.</li>
1686        <li>Reverse-engineer, scrape, or extract data from the App at scale, except for your own data via the in-app export.</li>
1687        <li>Use an integration to bring in data you are not authorised to access, or whose storage outside of the source system would violate your organisation's policies or the source system's terms.</li>
1688      </ul>
1689
1690      <h2>5b. Billing</h2>
1691      <p>A team may be on a free or a paid plan. Paid plans are billed per active member through Stripe, which processes the payment: your card details go to Stripe and never reach us. Adding or removing members changes the seat count, and therefore the amount. If a subscription lapses, the team becomes read-only &mdash; nothing is deleted, and it can be written to again once billing is settled.</p>
1692
1693      <h2>6. Integrations</h2>
1694      <p>The App supports optional, opt-in integrations with HubSpot, Luma, Google Sheets, Microsoft Excel and Attio. When you connect one of these, you confirm that you have the authority to read the data being connected under your organisation's policies and the third party's terms of service. Each integration is connected per team; its data becomes visible to that team's members on matching LinkedIn profiles.</p>
1695      <p>For HubSpot, Luma and Attio, the credentials you paste (Private App token, API key and optional iCal URL, access token) are stored encrypted in Supabase Vault and used only by server-side functions scoped to your team. The same applies to the Microsoft refresh token behind the Excel connector. Every connector reads only: the App writes nothing back into a connected account, Attio included. For Google Sheets, the App uses the Google Picker and Sheets APIs with the <code>drive.file</code> scope, which only grants access to the specific file you pick;
1695 no refresh token is stored, and you re-authenticate on each sync.</p>
1696      <p>The App copies the data you pick from each integration into our database so it can be shown on matching LinkedIn profiles. You are responsible for re-syncing when the source data changes. Disconnecting an integration immediately removes the stored credentials and the data synced from that integration.</p>
1697
1698      <h2>7. Your Data and GDPR Rights</h2>
1699      <p>The App is GDPR-compliant. We process your data only based on the consent you grant when you sign up and when you connect each integration. You may at any time access, correct, export, or permanently delete your data &mdash; see Sections 7&ndash;8 of the <a href="#" data-ot-h="h16" style="color:var(--accent)">Privacy Policy</a>. The "Delete account" button in Account Settings erases all your personal data and your underlying authentication record.</p>
1700
1701      <h2>8. Team Data</h2>
1702      <p>When you create or join a team, the outreaches you log, comments you write, contact details you add, markings and lists you create, and integrations you connect become visible to the other members of that team. This is the core function of the App. If you remove yourself from a team, the data you contributed to that team is also deleted. If a team admin removes the team, all member data within it is deleted.</p>
1703
1704      <h2>9. Service Availability and Modifications</h2>
1705      <p>The App is provided on an "as available" basis. We do not guarantee uninterrupted availability, freedom from bugs, or fitness for a particular purpose. We may modify, suspend, or discontinue parts of the App at any time. If we make material changes that affect your rights, we will notify you in the App before they take effect.</p>
1706
1707      <h2>10. Disclaimers and Limitation of Liability</h2>
1708      <p>To the maximum extent permitted by law, the App is provided "as is" without warranties of any kind, express or implied. We are not liable for any indirect, incidental, special, or consequential damages arising from your use of the App, including loss of data, loss of business opportunities, or damage caused by reliance on team-logged outreach history or integration data. Our total liability, if any, is limited to the amount you have paid to use the App in the preceding twelve months &mdash; which, for the current free tier, is zero.</p>
1709
1710      <h2>11. Termination</h2>
1711      <p>You may stop using the App at any time and delete your account from Account Settings. We may suspend or terminate your access if you materially violate these Terms or the law, or if your continued use poses a security or integrity risk. On termination for any reason, your data is erased per the Privacy Policy.</p>
1712
1713      <h2>12. Governing Law</h2>
1714      <p>These Terms are governed by the laws of Denmark, without regard to its conflict-of-law rules. Any dispute will be resolved by the courts of Denmark, except where mandatory consumer-protection rules in your country of residence grant you the right to bring proceedings locally.</p>
1715
1716      <h2>13. Contact</h2>
1717      <p>For questions about these Terms or to exercise your GDPR rights, contact the data controller at <a href="mailto:[email protected]" style="color:var(--accent)">[email protected]</a>.</p>
1718
1719      <h2>14. Changes to These Terms</h2>
1720      <p>We may update these Terms from time to time. Material changes will be announced in the App, and the "Effective Date" above will reflect the latest revision. Continued use after changes take effect constitutes acceptance of the revised Terms. If you do not agree, you can delete your account at any time.</p>
1721    </div>
1722  </div>
1723</div>
1724
1725<!-- POST-AUTH CONSENT GATE (for first-time users who came via Log in) -->
1726<div class="modal-overlay" id="modal-consent-gate" data-no-dismiss>
1727  <div class="modal" style="max-width:520px">
1728    <h3>One last thing</h3>
1729    <p style="font-size:13.5px;color:var(--text-2);line-height:1.5;margin-top:8px;margin-bottom:12px">Before you start using Orwell's bad dream, please review the Terms of Use and Privacy Policy and confirm your consent to the processing of your data.</p>
1730    <div style="background:hsla(30,85%,50%,.08);border:1px solid hsla(30,85%,50%,.3);border-radius:var(--radius);padding:12px 14px;margin-bottom:16px;font-size:12.5px;color:var(--text);line-height:1.55">
1731      <strong>Heads up on the core feature:</strong> every LinkedIn profile you open with the extension is logged to your team's shared database (profile identifier, display name, timestamp), visible to your teammates. At most one row per profile per day. You can delete individual view rows, leave the team, or delete your account at any time.
1732    </div>
1733    <div style="background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;margin-bottom:16px;font-size:12.5px;color:var(--text-2);line-height:1.55">
1734      <strong style="color:var(--text)">Team-scoped sharing:</strong> records in Orwell's bad dream are only visible to the team they already belong to. Data you or a teammate brings in from HubSpot, Luma, or Google Sheets stays inside that team &mdash; we do not copy it to other teams and we only show you what your team already had on a person. You must agree not to use the App to share these records with anyone outside that team. Teams caught doing so will be deleted and banned.
1735    </div>
1736    <label style="display:flex;align-items:flex-start;gap:10px;padding:12px 14px;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:16px;cursor:pointer">
1737      <input type="checkbox" id="gate-consent" class="check-mark" style="margin-top:2px;flex-shrink:0">
1738      <span style="font-size:13px;color:var(--text);line-height:1.5">
1739        I have read and agree to the <a href="#" data-ot-h="h15" style="color:var(--accent);text-decoration:underline">Terms of Use</a> and <a href="#" data-ot-h="h16" style="color:var(--accent);text-decoration:underline">Privacy Policy</a>. I understand that my profile visits are logged for my team to see, and I agree not to use the App to share records with anyone outside the team those records already belong to.
1740      </span>
1741    </label>
1742    <div class="modal-actions" style="justify-content:space-between">
1743      <button class="btn btn-secondary btn-sm" id="gate-decline-btn" style="color:var(--danger)" data-ot-h="h71">Decline and delete account</button>
1744      <button class="btn btn-primary btn-sm" id="gate-accept-btn" data-ot-h="h72" disabled>Accept and continue</button>
1745    </div>
1746  </div>
1747</div>
1748
1749<!-- ABOUT / EXPLAINER MODAL (linked from landing-page footer) -->
1750<div class="modal-overlay" id="modal-about">
1751  <div class="modal" style="max-width:560px">
1752    <h3>What is Orwell's bad dream?</h3>
1753    <div style="font-size:13.5px;color:var(--text-2);line-height:1.65;margin-top:12px">
1754      <p style="margin-bottom:12px"><strong style="color:var(--text)">It's a shared notepad for small teams doing outreach.</strong> You and your teammates log who you've contacted and what was said. When any of you later opens that person's LinkedIn profile &mdash; or starts writing to them in Gmail or Outlook &mdash; the extension shows you what the team already knows.</p>
1755      <p style="margin-bottom:12px"><strong style="color:var(--text)">Why it exists.</strong> So two people on the same team don't message the same person twice, or say contradictory things. It surfaces the context your team already has, at the moment you need it.</p>
1756      <p style="margin-bottom:12px"><strong style="color:var(--text)">And on the website.</strong> Everyone your team has touched sits in one searchable list, with a page each for the history, comments and tags. Build lists, move people through stages you name yourself, and tag a teammate by name to send them a notification.</p>
1757      <p style="margin-bottom:12px"><strong style="color:var(--text)">What it stores.</strong> Outreach you log, notes, comments, tags, and emails or phone numbers you add. Plus &mdash; only if you connect them &mdash; rows from your own Attio, Excel, Google Sheets, HubSpot or Luma. It also records when a teammate opens a profile (once per person per day), so the team can see someone has already been there.</p>
1758      <p style="margin-bottom:12px"><strong style="color:var(--text)">Email, specifically.</strong> Nothing is read until you press Track. When you do, that one message &mdash; recipient, subject and body &mdash; is summarised into three bullets offered as your note, which you can edit or throw away. The text itself is never stored, and the bullets are saved only if you actually send. Your inbox, your other messages, drafts you didn't track and anything you receive are never touched.</p>
1759      <p style="margin-bottom:12px"><strong style="color:var(--text)">What it doesn't do.</strong> It does not read your LinkedIn messages, your connections, or your newsfeed. It does not buy data from brokers, scrape the wider web, or sell anything. It does not cross-reference teams &mdash; records stay inside the one team that entered them.</p>
1760      <p style="margin-bottom:0"><strong style="color:var(--text)">Team-scoped.</strong> The data your team brings in stays inside that team. Nobody outside the team sees it. Teams found trying to widen that circle are deleted and banned.</p>
1761    </div>
1762    <div class="modal-actions" style="justify-content:flex-end;margin-top:16px">
1763      <button class="btn btn-primary btn-sm" data-ot-h="h34">Got it</button>
1764    </div>
1765  </div>
1766</div>
1767
1768<div class="modal-overlay" id="modal-match-score">
1769  <div class="modal" style="max-width:420px">
1770    <h3 id="match-score-title">Match score</h3>
1771    <p id="match-score-sub" style="font-size:13px;color:var(--text-2);line-height:1.5;margin-top:6px"></p>
1772    <div id="match-score-display" style="display:flex;align-items:center;gap:10px;margin:14px 0 10px"></div>
1773    <ul id="match-score-reasons" style="font-size:13px;color:var(--text-2);line-height:1.55;margin:0;padding-left:18px"></ul>
1774    <div class="modal-actions" style="justify-content:flex-end;margin-top:16px">
1775      <button class="btn btn-primary btn-sm" data-ot-h="h34">Close</button>
1776    </div>
1777  </div>
1778</div>
1779
1780<div class="toast" id="toast"></div>
1781<div class="loading-overlay" id="loading-overlay"><div class="loading-overlay-box"><div class="spinner"></div><span id="loading-overlay-msg">Loading…</span></div></div>
1782
1783<!-- App, split into ordered modules (classic scripts sharing one global
1784     scope). Load order = original source order, so cross-module calls and
1785     hoisting behave exactly as the former single file. -->
1786<script src="/app-ui.js"></script>
vendor: 1 bytes, line 1786
1786
1787<script src="/pixel-eye.js"></script>
vendor: 1 bytes, line 1787
1787
1788<script src="/app-core.js"></script>
vendor: 1 bytes, line 1788
1788
1789<script src="/app-auth.js"></script>
vendor: 1 bytes, line 1789
1789
1790<script src="/app-team.js"></script>
vendor: 1 bytes, line 1790
1790
1791<script src="/app-pipeline.js"></script>
vendor: 1 bytes, line 1791
1791
1792<script src="/app-profile.js"></script>
vendor: 1 bytes, line 1792
1792
1793<script src="/app-connectors.js"></script>
vendor: 1 bytes, line 1793
1793
1794<script src="/app-import.js"></script>
vendor: 1 bytes, line 1794
1794
1795<script src="/app-datepicker.js"></script>
vendor: 1 bytes, line 1795
1795
1796<script src="/app-landing.js"></script>
1796
1797<!-- Last: its table calls functions the files above define. -->
1798<script src="/app-actions.js"></script>
1798
1799</body>
1800</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.