Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
605 lines (586 loc) · 32.8 KB
/
Copy pathindex.html
File metadata and controls
605 lines (586 loc) · 32.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PM Ops Map</title>
<meta name="description" content="Map your property management company's tasks, ownership, and accountability across every department.">
<!-- Social-card scrapers need absolute URLs. These point at the project's GitHub Pages site;
if you host your own copy, change them to your deployment URL. -->
<meta property="og:type" content="website">
<meta property="og:title" content="PM Ops Map — free, open-source property management ops tracker">
<meta property="og:description" content="Map your property management company's tasks, ownership, and accountability across every department. Zero backend, runs entirely in your browser.">
<meta property="og:image" content="https://hypnoticdata777.github.io/pm-ops-map/og-image.png">
<meta property="og:url" content="https://hypnoticdata777.github.io/pm-ops-map/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="PM Ops Map — free, open-source property management ops tracker">
<meta name="twitter:description" content="Map your property management company's tasks, ownership, and accountability across every department. Zero backend, runs entirely in your browser.">
<meta name="twitter:image" content="https://hypnoticdata777.github.io/pm-ops-map/og-image.png">
<link rel="stylesheet" href="css/style.css">
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="icon" href="icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="icon.png">
<link rel="manifest" href="site.webmanifest">
</head>
<body>
<!-- Hosted demo (?demo=1) only: js/demo.js un-hides this. It is deliberately not dismissible. -->
<div id="demo-banner" class="demo-banner" role="region" aria-label="Demo workspace" hidden>
<span class="demo-banner-badge">Demo</span>
<p><strong>Fictional data only.</strong> Nothing here is real and nothing leaves your browser — please don’t enter real tenant information.</p>
<div class="demo-banner-actions">
<button class="btn btn-secondary" onclick="resetDemo()" title="Discard your edits and restore the original fictional workspace">Reset demo</button>
<a class="btn btn-secondary" href="./" title="Open PM Ops Map with your own, empty workspace">Use your own workspace →</a>
</div>
</div>
<div class="container">
<div class="header">
<div>
<h1>PM Ops Map</h1>
<p>Comprehensive Operational Analysis & Strategic Planning</p>
</div>
<button class="nav-toggle-btn" id="nav-toggle-btn" onclick="toggleNavCompact()" aria-pressed="false" title="Make navigation less prominent">Hide nav</button>
</div>
<div class="nav-tabs" id="nav-tabs">
<!-- FIX: pass `this` so switchView() receives the clicked element directly,
avoiding reliance on window.event which is undefined in Firefox. -->
<div class="nav-tab active" onclick="switchView('tracking', this)">📊 Tracking View</div>
<div class="nav-tab" onclick="switchView('map', this)">🗺 Map View</div>
<!-- ★ BEACON: tab-beacon is the pulsing red dot shown when UNOWNED tasks exist.
JS toggles its `hidden` attribute so it appears/disappears automatically. -->
<div class="nav-tab" id="team-tab" onclick="switchView('team', this)">
👥 Team Manager
<span class="tab-beacon" id="tab-beacon" hidden></span>
</div>
<div class="nav-tab" id="wo-tab" onclick="switchView('workorders', this)">
🔧 Work Orders
<span class="tab-beacon" id="wo-tab-beacon" hidden></span>
</div>
<div class="nav-tab" id="portfolio-tab" onclick="switchView('portfolio', this)">Portfolio</div>
</div>
<div class="controls" id="controls">
<!-- Search/filter bar — only shown on Tracking view -->
<div class="filter-bar" id="filter-bar">
<input
type="text"
id="search-input"
class="search-input"
placeholder="Search tasks..."
oninput="debounceApplyFilter()"
aria-label="Search tasks by keyword"
>
<select id="owner-filter" class="owner-select" onchange="applyFilter()" aria-label="Filter by owner">
<option value="">All Owners</option>
</select>
<!-- Status filter — show only tasks in a given lifecycle state -->
<select id="status-filter" class="owner-select" onchange="applyFilter()" aria-label="Filter by status">
<option value="">All Statuses</option>
<option value="todo">To Do</option>
<option value="in-progress">In Progress</option>
<option value="blocked">⚠ Blocked</option>
<option value="done">✓ Done</option>
<option value="overdue">📅 Overdue</option>
</select>
<!-- Priority filter — surface high-priority tasks quickly -->
<select id="priority-filter" class="owner-select" onchange="applyFilter()" aria-label="Filter by priority">
<option value="">All Priorities</option>
<option value="high">🔴 High</option>
<option value="medium">🟡 Medium</option>
<option value="low">🟢 Low</option>
</select>
<button class="btn btn-secondary" onclick="clearFilter()">Clear</button>
<button class="btn btn-secondary" id="expand-all-btn" onclick="toggleExpandAllDepartments()" title="Expand or collapse every department at once">▼ Expand All</button>
<button class="btn btn-secondary" id="bulk-toggle-btn" onclick="toggleBulkMode()" title="Select multiple tasks to reassign or change status in bulk">☑ Bulk Select</button>
<!-- ★ BEACON: Auto-assign button. The badge inside shows the live UNOWNED count.
JS keeps this disabled when count = 0 so managers always know the state. -->
<button
class="btn btn-auto-assign"
id="auto-assign-btn"
onclick="runAutoAssign()"
title="Auto-assign UNOWNED tasks to team members based on their department affinities"
disabled
>
⚡ Auto-Assign
<span class="auto-assign-badge" id="auto-assign-badge" style="display:none">0</span>
</button>
<span id="filter-count" class="filter-count" aria-live="polite"></span>
</div>
<!-- Legend is rendered dynamically by renderLegend() so new employees appear here automatically -->
<div class="legend" id="owner-legend"></div>
</div>
<div class="content">
<!-- TRACKING VIEW -->
<div id="tracking-view" class="view-panel active">
<div class="tracking-hero">
<h2 id="company-heading">YOUR COMPANY</h2>
</div>
<!-- Rendered by js/launchPlan.js. Generated buttons call functions
exposed through app.js window bindings, so update both files together. -->
<section id="launch-plan" class="launch-plan" aria-label="Launch plan"></section>
<!-- Welcome guide: shown once to new users, dismissed to localStorage.
hidden attribute is toggled by initWelcomeGuide() in app.js. -->
<div id="welcome-guide" class="welcome-guide" hidden>
<div class="guide-header">
<span class="guide-title">👋 Welcome to PM Ops Map — here’s how to get started</span>
</div>
<div class="guide-steps">
<div class="guide-step">
<div class="guide-step-num">1</div>
<div class="guide-step-body">
<strong>Start at the top</strong>
<span>The Start Here panel tells you the one best next action based on your current setup.</span>
</div>
</div>
<div class="guide-divider"></div>
<div class="guide-step">
<div class="guide-step-num">2</div>
<div class="guide-step-body">
<strong>Add real people</strong>
<span>Open <strong>Team Manager</strong>, add your team, and choose which departments each person covers.</span>
</div>
</div>
<div class="guide-divider"></div>
<div class="guide-step">
<div class="guide-step-num">3</div>
<div class="guide-step-body">
<strong>Close red gaps</strong>
<span>Assign every <em>UNOWNED</em> task, or use <strong>⚡ Auto-Assign</strong> after affinities are set.</span>
</div>
</div>
<div class="guide-divider"></div>
<div class="guide-step">
<div class="guide-step-num">4</div>
<div class="guide-step-body">
<strong>Export the handbook</strong>
<span>When the map looks right, click <strong>Handbook</strong> to download a starter SOP for the team.</span>
</div>
</div>
</div>
<button class="guide-dismiss" onclick="dismissWelcomeGuide()">Got it — hide this ✕</button>
</div>
<div class="departments" id="departments"></div>
<div id="no-results" class="no-results" style="display:none">
No tasks match your search. <button class="btn-link" onclick="clearFilter()">Clear filters</button>
</div>
</div>
<!-- MAP VIEW -->
<div id="map-view" class="view-panel">
<!-- Owner filter pills + action bar -->
<div id="map-controls" class="map-controls"></div>
<!-- Two-column layout: SVG map + optional task detail panel -->
<div class="map-layout">
<div class="map-scroll-wrapper">
<svg id="flowMap"></svg>
</div>
<div id="map-dept-panel" class="map-dept-panel">
<div class="map-panel-placeholder">
<span>📋</span>
<p>Click a department box on the map to see its task list here.</p>
</div>
</div>
</div>
</div>
<!-- TEAM MANAGER VIEW
Inner content rendered entirely by renderTeamView() in app.js so
the employee list, affinity tags, and workload bars stay in sync
with the live teamData registry. -->
<div id="team-view" class="view-panel">
<div id="team-view-inner"></div>
</div>
<!-- WORK ORDERS VIEW
Inner content rendered entirely by renderWorkOrdersView() in app.js. -->
<div id="workorders-view" class="view-panel">
<div id="workorders-view-inner"></div>
</div>
<!-- PORTFOLIO VIEW
Starter registry for properties, units, owners, and vendors. -->
<div id="portfolio-view" class="view-panel">
<div id="portfolio-view-inner"></div>
</div>
</div>
<div class="stats">
<div class="stat-card"><div class="stat-label">Total Tasks</div><div class="stat-value" id="stat-total">0</div></div>
<div class="stat-card"><div class="stat-label">✓ Assigned</div><div class="stat-value" id="stat-assigned">0</div></div>
<div class="stat-card done"><div class="stat-label">✓ Done</div><div class="stat-value" id="stat-done">0</div></div>
<div class="stat-card progress"><div class="stat-label">⌛ In Progress</div><div class="stat-value" id="stat-in-progress">0</div></div>
<div class="stat-card blocked"><div class="stat-label">⚠ Blocked</div><div class="stat-value" id="stat-blocked">0</div></div>
<div class="stat-card overdue"><div class="stat-label">📅 Overdue</div><div class="stat-value" id="stat-overdue">0</div></div>
<div class="stat-card danger"><div class="stat-label">⚠ Unowned</div><div class="stat-value" id="stat-unowned">0</div></div>
<div class="stat-card"><div class="stat-label">Departments</div><div class="stat-value" id="stat-departments">0</div></div>
<div class="stats-actions">
<button class="btn btn-export" onclick="exportJSON()" title="Export config as JSON to share or back up">↓ Export JSON</button>
<button class="btn btn-export" onclick="exportCSV()" title="Export task list as CSV for spreadsheets">↓ Export CSV</button>
<button class="btn btn-export" onclick="exportPropertiesCSV()" title="Export properties as CSV for spreadsheets">↓ Properties CSV</button>
<button class="btn btn-export" onclick="exportTenantsCSV()" title="Export tenants as CSV for spreadsheets">↓ Tenants CSV</button>
<button class="btn btn-export" onclick="exportVendorsCSV()" title="Export vendors as CSV for spreadsheets">↓ Vendors CSV</button>
<button class="btn btn-export" onclick="exportWorkOrdersCSV()" title="Export work orders as CSV for spreadsheets">↓ Work Orders CSV</button>
<button class="btn btn-export" onclick="downloadOperationsHandbook()" title="Download a Markdown SOP starter handbook from the current map">↓ Handbook .md</button>
<button class="btn btn-export" onclick="downloadOperationsHandbookHTML()" title="Download a self-contained HTML version of the handbook">↓ Handbook .html</button>
<button class="btn btn-export" onclick="printOperationsHandbook()" title="Open a print-friendly handbook view">Print Handbook</button>
<label class="btn btn-import" title="Import a previously exported JSON config">
↑ Import JSON
<input type="file" id="import-file-input" accept=".json" style="display:none" onchange="importJSON(this)">
</label>
<button class="btn btn-export" onclick="copyStateToClipboard()" title="Copy full state to clipboard to paste on another device">📋 Copy State</button>
<button class="btn btn-export" onclick="pasteStateFromClipboard()" title="Paste a state copied from another device">📱 Paste State</button>
<button class="btn btn-secondary" id="sync-status-pill" onclick="openSyncModal()" title="Beta: share this workspace with a small, trusted team through a server you run">↻ Team Sync <small>beta</small></button>
<button class="btn btn-secondary" onclick="openAuditLog()" title="View change history">📋 Audit Log</button>
<button class="btn btn-secondary" onclick="openBackupsModal()" title="View and restore automatic backups">💾 Backups</button>
<button class="btn btn-secondary" id="notif-btn" onclick="requestNotificationPermission()" title="Enable browser alerts for overdue tasks" hidden>🔔 Alerts</button>
<button class="btn btn-secondary" onclick="openPrivacyModal()" title="Where your data is stored, what to enter, and the copies exports create">🔒 Data & Privacy</button>
<button class="btn btn-secondary stats-reset-btn" onclick="resetStorage()" title="Clear all saved changes and restore defaults">↻ Reset</button>
</div>
</div>
</div>
<!-- FIX: paths updated from bare filenames to js/ subdirectory,
matching the actual file locations and making webpack entry consistent. -->
<!-- Setup landing: shown on first visit to configure the company workspace -->
<div id="onboarding-modal" class="onboarding-overlay setup-landing" role="dialog" aria-modal="true" aria-labelledby="onboarding-title">
<div class="onboarding-card">
<div class="setup-copy">
<div class="onboarding-logo">PM Ops Map</div>
<h2 id="onboarding-title" class="onboarding-title">Build your operations map.</h2>
<p class="onboarding-subtitle">Start with your company context. The next screen will show your first recommended action, the setup gaps to close, and the handbook you can export when the map is ready.</p>
<div class="setup-proof-grid" aria-label="What this setup unlocks">
<div><strong>17</strong><span>departments</span></div>
<div><strong>260+</strong><span>tasks</span></div>
<div><strong>0</strong><span>server setup</span></div>
</div>
</div>
<div class="setup-form">
<label for="company-input" class="onboarding-label">Company name</label>
<input id="company-input" type="text" class="onboarding-input" placeholder="e.g. Sunrise Property Management" maxlength="60" autocomplete="off">
<label for="portfolio-size-input" class="onboarding-label">Portfolio size</label>
<select id="portfolio-size-input" class="onboarding-input">
<option value="small">Under 100 doors</option>
<option value="mid" selected>100-500 doors</option>
<option value="large">500+ doors</option>
</select>
<label for="ops-focus-input" class="onboarding-label">Primary operations focus</label>
<select id="ops-focus-input" class="onboarding-input">
<option value="stability" selected>Stabilize ownership</option>
<option value="maintenance">Tighten maintenance flow</option>
<option value="growth">Scale team capacity</option>
<option value="compliance">Reduce compliance risk</option>
</select>
<p class="onboarding-privacy">🔒 Everything you enter stays in this browser, unencrypted — there is no account and no server. Explore with the example workspace first, and read <strong>Data & Privacy</strong> in the toolbar before adding real tenant details.</p>
<button class="onboarding-btn" onclick="submitCompanyName()">Start Mapping →</button>
<button class="onboarding-btn onboarding-btn--secondary" onclick="startWithDemoCompany()">Show me an example first</button>
</div>
</div>
</div>
<!-- Work Order creation modal -->
<div id="wo-modal" class="onboarding-overlay" role="dialog" aria-modal="true" aria-labelledby="wo-modal-title">
<div class="wo-modal-card">
<div class="onboarding-logo">Work Order</div>
<h2 id="wo-modal-title" class="onboarding-title">New Work Order</h2>
<div class="wo-form-grid">
<div class="wo-form-group">
<label class="wo-form-label" for="wo-property">Property *</label>
<input id="wo-property" type="text" class="wo-form-input" placeholder="e.g. 123 Main St" maxlength="80" autocomplete="off" list="wo-property-options">
<datalist id="wo-property-options"></datalist>
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-unit">Unit</label>
<input id="wo-unit" type="text" class="wo-form-input" placeholder="e.g. 2B" maxlength="20" autocomplete="off" list="wo-unit-options">
<datalist id="wo-unit-options"></datalist>
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-tenant">Tenant / Resident</label>
<input id="wo-tenant" type="text" class="wo-form-input" placeholder="e.g. Maya Chen" maxlength="80" autocomplete="off" list="wo-tenant-options">
<datalist id="wo-tenant-options"></datalist>
</div>
<div class="wo-form-group wo-form-group--full">
<label class="wo-form-label" for="wo-title">Issue *</label>
<input id="wo-title" type="text" class="wo-form-input" placeholder="e.g. Leaking faucet in kitchen" maxlength="120" autocomplete="off">
</div>
<div class="wo-form-group wo-form-group--full">
<label class="wo-form-label" for="wo-notes">Notes</label>
<textarea id="wo-notes" class="wo-form-input wo-form-textarea" placeholder="Additional details, access instructions..." rows="2"></textarea>
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-priority">Priority</label>
<select id="wo-priority" class="wo-form-input">
<option value="high">🔴 High</option>
<option value="medium" selected>🟡 Medium</option>
<option value="low">🟢 Low</option>
</select>
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-assignee">Assignee</label>
<select id="wo-assignee" class="wo-form-input">
<option value="UNASSIGNED">— Unassigned —</option>
</select>
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-duedate">Target Date</label>
<input id="wo-duedate" type="date" class="wo-form-input">
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-vendor">Vendor</label>
<input id="wo-vendor" type="text" class="wo-form-input" placeholder="e.g. Ace Plumbing" maxlength="60" autocomplete="off" list="wo-vendor-options">
<datalist id="wo-vendor-options"></datalist>
</div>
<div class="wo-form-group">
<label class="wo-form-label" for="wo-cost">Est. Cost ($)</label>
<input id="wo-cost" type="number" class="wo-form-input" placeholder="0.00" min="0" step="0.01">
</div>
</div>
<div class="wo-modal-actions">
<button class="btn btn-secondary" onclick="closeWorkOrderModal()">Cancel</button>
<button class="onboarding-btn wo-submit-btn" id="wo-submit-btn" onclick="commitNewWorkOrder()">Create Work Order →</button>
</div>
</div>
</div>
<!-- Role Playbook modal -->
<div id="playbook-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="playbook-title">
<div class="playbook-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Role Playbook</div>
<h2 id="playbook-title">Team Member</h2>
</div>
<button class="playbook-close-btn" onclick="closeRolePlaybook()" aria-label="Close role playbook">×</button>
</div>
<div id="playbook-body" class="playbook-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeRolePlaybook()">Close</button>
<button class="btn btn-export" id="playbook-download-btn" onclick="downloadOpenRolePlaybook()">Download Markdown</button>
</div>
</div>
</div>
<!-- Role Template Preview modal -->
<div id="role-template-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="role-template-title">
<div class="role-template-preview-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Role Template</div>
<h2 id="role-template-title">Preview template</h2>
</div>
<button class="playbook-close-btn" onclick="closeRoleTemplatePreview()" aria-label="Close role template preview">×</button>
</div>
<div id="role-template-preview-body" class="role-template-preview-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeRoleTemplatePreview()">Cancel</button>
<button class="btn btn-export" id="role-template-apply-btn" onclick="applyPreviewedRoleTemplate()">Apply Template</button>
</div>
</div>
</div>
<!-- Audit Log modal -->
<div id="audit-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="audit-modal-title">
<div class="audit-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Change History</div>
<h2 id="audit-modal-title">Audit Log</h2>
</div>
<button class="playbook-close-btn" onclick="closeAuditLog()" aria-label="Close">×</button>
</div>
<div id="audit-body" class="audit-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="clearAuditLog()">Clear Log</button>
<button class="btn btn-secondary" onclick="closeAuditLog()">Close</button>
</div>
</div>
</div>
<!-- Reset Options modal -->
<div id="reset-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="reset-modal-title">
<div class="reset-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Local Reset</div>
<h2 id="reset-modal-title">Choose what to clear</h2>
</div>
<button class="playbook-close-btn" onclick="closeResetModal()" aria-label="Close">×</button>
</div>
<div class="reset-body">
<button class="reset-option-card" onclick="confirmResetStorage('1')">
<strong>Tasks only</strong>
<span>Task names, owners, statuses, due dates, dependencies, notes, and custom fields. Automatic backups are kept.</span>
</button>
<button class="reset-option-card" onclick="confirmResetStorage('2')">
<strong>Operating workspace</strong>
<span>Tasks, team, portfolio, work orders, audit log, launch checklist, and automatic backups.</span>
</button>
<button class="reset-option-card" onclick="confirmResetStorage('3')">
<strong>Company setup</strong>
<span>Company profile, nav preference, guide state, notification date, and the saved Team Sync connection.</span>
</button>
<button class="reset-option-card reset-option-card--danger" onclick="confirmResetStorage('4')">
<strong>Everything</strong>
<span>All PM Ops Map data stored in this browser, including automatic backups and the saved Team Sync connection. Exported files and any Team Sync server copy are not affected.</span>
</button>
</div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeResetModal()">Cancel</button>
</div>
</div>
</div>
<!-- Import Review modal -->
<div id="import-review-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="import-review-title">
<div class="import-review-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Import Review</div>
<h2 id="import-review-title">Review workspace import</h2>
</div>
<button class="playbook-close-btn" onclick="cancelPendingImport()" aria-label="Close">×</button>
</div>
<div id="import-review-body" class="import-review-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="cancelPendingImport()">Cancel</button>
<button class="btn btn-export" id="import-confirm-btn" onclick="confirmPendingImport()">Import Workspace</button>
</div>
</div>
</div>
<!-- Custom Task Fields modal -->
<div id="custom-fields-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="custom-fields-title">
<div class="task-notes-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Custom Fields</div>
<h2 id="custom-fields-title" class="task-notes-heading">Task</h2>
</div>
<button class="playbook-close-btn" onclick="closeCustomFields()" aria-label="Close">×</button>
</div>
<div class="task-notes-body" id="custom-fields-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeCustomFields()">Done</button>
</div>
</div>
</div>
<!-- Per-task Notes modal -->
<div id="task-notes-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="task-notes-title">
<div class="task-notes-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Task Notes</div>
<h2 id="task-notes-title" class="task-notes-heading">Task</h2>
</div>
<button class="playbook-close-btn" onclick="closeTaskNotes()" aria-label="Close">×</button>
</div>
<div class="task-notes-body">
<textarea id="task-notes-textarea" class="task-notes-textarea" placeholder="Add context, blockers, links, or anything the team needs to know about this task..." maxlength="1000" rows="8"></textarea>
<p class="task-notes-hint">Max 1000 characters. Saved per-task alongside status and owner.</p>
</div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeTaskNotes()">Cancel</button>
<button class="btn btn-primary" id="task-notes-save-btn">Save Notes</button>
</div>
</div>
</div>
<!-- Recurring Templates modal -->
<div id="recurring-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="recurring-modal-title">
<div class="recurring-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Work Orders</div>
<h2 id="recurring-modal-title">Recurring Templates</h2>
</div>
<button class="playbook-close-btn" onclick="closeRecurringModal()" aria-label="Close">×</button>
</div>
<div id="recurring-body" class="recurring-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeRecurringModal()">Cancel</button>
</div>
</div>
</div>
<!-- Recurring confirm/detail modal -->
<div id="recurring-confirm-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="recurring-confirm-title">
<div class="import-review-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Create Work Orders</div>
<h2 id="recurring-confirm-title">Template</h2>
</div>
<button class="playbook-close-btn" onclick="closeRecurringModal()" aria-label="Close">×</button>
</div>
<div id="recurring-confirm-body" class="import-review-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeRecurringModal()">Cancel</button>
<button class="btn btn-primary" onclick="applyPendingRecurringTemplate()">Create Work Orders →</button>
</div>
</div>
</div>
<!-- Backups modal -->
<div id="backups-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="backups-modal-title">
<div class="audit-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Auto-Backup</div>
<h2 id="backups-modal-title">Restore a Snapshot</h2>
</div>
<button class="playbook-close-btn" onclick="closeBackupsModal()" aria-label="Close">×</button>
</div>
<div id="backups-body" class="audit-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closeBackupsModal()">Close</button>
</div>
</div>
</div>
<!-- Team Sync modal -->
<div id="sync-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="sync-modal-title">
<div class="audit-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Beta · Trusted-team sync · Self-hosted</div>
<h2 id="sync-modal-title">Team Sync</h2>
</div>
<button class="playbook-close-btn" onclick="closeSyncModal()" aria-label="Close">×</button>
</div>
<div class="sync-modal-body">
<p class="sync-modal-intro">
Share one workspace across your team’s browsers through a small server you run
yourself. This is a <strong>beta</strong> built for a few people who trust each other
— it is not enterprise collaboration.
</p>
<ul class="sync-modal-limits">
<li><strong>One shared passphrase.</strong> No individual accounts, roles, or way to remove one person’s access — anyone with the workspace name and passphrase can read and change everything.</li>
<li><strong>Whole-workspace sync.</strong> If two people edit at once, you choose whose version to keep.</li>
<li><strong>Your data leaves this browser.</strong> The workspace goes to the server you enter below (use <code>https://</code>), and the passphrase is saved unencrypted in this browser.</li>
<li>Leave this disconnected and the app works exactly as before, saved only in this browser.</li>
</ul>
<button class="btn btn-secondary sync-privacy-link" onclick="openPrivacyModal()">More about your data</button>
<label class="sync-field">
<span>Sync server URL</span>
<input id="sync-server-url" type="text" class="portfolio-input" placeholder="https://your-sync-server.example.com">
</label>
<label class="sync-field">
<span>Workspace name</span>
<input id="sync-workspace" type="text" class="portfolio-input" placeholder="acme-property-mgmt" maxlength="40" autocomplete="off">
</label>
<label class="sync-field">
<span>Passphrase</span>
<input id="sync-passphrase" type="password" class="portfolio-input" placeholder="Shared with your team" autocomplete="off">
</label>
<p id="sync-modal-status" class="sync-modal-status">Not connected — this device is working offline.</p>
</div>
<div class="playbook-actions">
<button class="btn btn-primary" id="sync-connect-btn" onclick="connectSync()">Connect</button>
<button class="btn btn-secondary" onclick="syncNow()">Sync Now</button>
<button class="btn btn-secondary" onclick="disconnectSync()">Disconnect</button>
<button class="btn btn-secondary" onclick="closeSyncModal()">Close</button>
</div>
</div>
</div>
<!-- Data & Privacy modal -->
<div id="privacy-modal" class="playbook-overlay" role="dialog" aria-modal="true" aria-labelledby="privacy-modal-title">
<div class="audit-panel privacy-panel">
<div class="playbook-header">
<div>
<div class="playbook-kicker">Your data</div>
<h2 id="privacy-modal-title">Data & Privacy</h2>
</div>
<button class="playbook-close-btn" onclick="closePrivacyModal()" aria-label="Close">×</button>
</div>
<div id="privacy-body" class="privacy-body"></div>
<div class="playbook-actions">
<button class="btn btn-secondary" onclick="closePrivacyModal()">Close</button>
</div>
</div>
</div>
<div id="save-toast" class="save-toast"></div>
<!-- Must stay type="module": GitHub Pages serves source files directly, and
js/app.js uses browser-native ES module imports. -->
<script type="module" src="js/app.js"></script>
</body>
</html>