@beastmode-develeap/beastmode 0.1.214 → 0.1.216
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/board.html +446 -6
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/web/board.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
17
|
<!--BOARD_DATA-->
|
|
18
|
-
<script>window.__BUILD_STAMP__ = "
|
|
18
|
+
<script>window.__BUILD_STAMP__ = "20260510-004632-5de247d";</script>
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -3563,6 +3563,167 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
3563
3563
|
}
|
|
3564
3564
|
.btn-secondary:hover { background: var(--surface-elevated); border-color: var(--text-muted); }
|
|
3565
3565
|
|
|
3566
|
+
/* ================================================================
|
|
3567
|
+
ONBOARDING TOUR & CHECKLIST
|
|
3568
|
+
================================================================ */
|
|
3569
|
+
|
|
3570
|
+
.tour-backdrop {
|
|
3571
|
+
position: fixed;
|
|
3572
|
+
inset: 0;
|
|
3573
|
+
background: rgba(0, 0, 0, 0.6);
|
|
3574
|
+
z-index: 9000;
|
|
3575
|
+
animation: fadeIn 0.2s ease;
|
|
3576
|
+
}
|
|
3577
|
+
.tour-spotlight {
|
|
3578
|
+
position: fixed;
|
|
3579
|
+
z-index: 9001;
|
|
3580
|
+
border-radius: 8px;
|
|
3581
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.6);
|
|
3582
|
+
pointer-events: none;
|
|
3583
|
+
transition: top 0.3s ease, left 0.3s ease, width 0.3s ease, height 0.3s ease;
|
|
3584
|
+
}
|
|
3585
|
+
.tour-tooltip {
|
|
3586
|
+
position: fixed;
|
|
3587
|
+
z-index: 9002;
|
|
3588
|
+
background: var(--bg-card);
|
|
3589
|
+
border: 1px solid var(--border);
|
|
3590
|
+
border-radius: 12px;
|
|
3591
|
+
padding: 24px;
|
|
3592
|
+
max-width: 380px;
|
|
3593
|
+
width: 90vw;
|
|
3594
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
3595
|
+
animation: slideUp 0.25s ease;
|
|
3596
|
+
}
|
|
3597
|
+
.tour-tooltip h3 {
|
|
3598
|
+
font-size: 18px;
|
|
3599
|
+
font-weight: 600;
|
|
3600
|
+
color: var(--text);
|
|
3601
|
+
margin: 0 0 8px 0;
|
|
3602
|
+
}
|
|
3603
|
+
.tour-tooltip p {
|
|
3604
|
+
font-size: 14px;
|
|
3605
|
+
color: var(--text-secondary);
|
|
3606
|
+
line-height: 1.5;
|
|
3607
|
+
margin: 0 0 20px 0;
|
|
3608
|
+
}
|
|
3609
|
+
.tour-step-indicator {
|
|
3610
|
+
display: flex;
|
|
3611
|
+
gap: 6px;
|
|
3612
|
+
margin-bottom: 16px;
|
|
3613
|
+
}
|
|
3614
|
+
.tour-step-dot {
|
|
3615
|
+
width: 8px;
|
|
3616
|
+
height: 8px;
|
|
3617
|
+
border-radius: 50%;
|
|
3618
|
+
background: var(--border);
|
|
3619
|
+
}
|
|
3620
|
+
.tour-step-dot.active {
|
|
3621
|
+
background: var(--accent);
|
|
3622
|
+
}
|
|
3623
|
+
.tour-actions {
|
|
3624
|
+
display: flex;
|
|
3625
|
+
justify-content: space-between;
|
|
3626
|
+
align-items: center;
|
|
3627
|
+
}
|
|
3628
|
+
.tour-actions .tour-skip {
|
|
3629
|
+
background: none;
|
|
3630
|
+
border: none;
|
|
3631
|
+
color: var(--text-muted);
|
|
3632
|
+
cursor: pointer;
|
|
3633
|
+
font-size: 13px;
|
|
3634
|
+
font-family: var(--font-sans);
|
|
3635
|
+
text-decoration: underline;
|
|
3636
|
+
padding: 0;
|
|
3637
|
+
}
|
|
3638
|
+
.tour-actions .tour-skip:hover { color: var(--text); }
|
|
3639
|
+
.tour-actions-right { display: flex; gap: 8px; }
|
|
3640
|
+
|
|
3641
|
+
.onboarding-checklist {
|
|
3642
|
+
background: var(--bg-card);
|
|
3643
|
+
border: 1px solid var(--border);
|
|
3644
|
+
border-radius: 12px;
|
|
3645
|
+
padding: 20px;
|
|
3646
|
+
margin-bottom: 16px;
|
|
3647
|
+
}
|
|
3648
|
+
.onboarding-checklist h3 {
|
|
3649
|
+
font-size: 15px;
|
|
3650
|
+
font-weight: 600;
|
|
3651
|
+
color: var(--text);
|
|
3652
|
+
margin: 0 0 4px 0;
|
|
3653
|
+
}
|
|
3654
|
+
.onboarding-checklist .subtitle {
|
|
3655
|
+
font-size: 13px;
|
|
3656
|
+
color: var(--text-muted);
|
|
3657
|
+
margin: 0 0 16px 0;
|
|
3658
|
+
}
|
|
3659
|
+
.checklist-item {
|
|
3660
|
+
display: flex;
|
|
3661
|
+
align-items: center;
|
|
3662
|
+
gap: 10px;
|
|
3663
|
+
padding: 8px 0;
|
|
3664
|
+
font-size: 14px;
|
|
3665
|
+
color: var(--text-secondary);
|
|
3666
|
+
}
|
|
3667
|
+
.checklist-item.completed {
|
|
3668
|
+
color: var(--text-muted);
|
|
3669
|
+
text-decoration: line-through;
|
|
3670
|
+
}
|
|
3671
|
+
.checklist-check {
|
|
3672
|
+
width: 20px;
|
|
3673
|
+
height: 20px;
|
|
3674
|
+
border-radius: 50%;
|
|
3675
|
+
border: 2px solid var(--border);
|
|
3676
|
+
display: flex;
|
|
3677
|
+
align-items: center;
|
|
3678
|
+
justify-content: center;
|
|
3679
|
+
flex-shrink: 0;
|
|
3680
|
+
font-size: 12px;
|
|
3681
|
+
line-height: 1;
|
|
3682
|
+
}
|
|
3683
|
+
.checklist-check.done {
|
|
3684
|
+
background: var(--success);
|
|
3685
|
+
border-color: var(--success);
|
|
3686
|
+
color: #fff;
|
|
3687
|
+
}
|
|
3688
|
+
.onboarding-progress {
|
|
3689
|
+
height: 4px;
|
|
3690
|
+
background: var(--border);
|
|
3691
|
+
border-radius: 2px;
|
|
3692
|
+
margin: 16px 0 12px;
|
|
3693
|
+
overflow: hidden;
|
|
3694
|
+
}
|
|
3695
|
+
.onboarding-progress-fill {
|
|
3696
|
+
height: 100%;
|
|
3697
|
+
background: var(--accent);
|
|
3698
|
+
border-radius: 2px;
|
|
3699
|
+
transition: width 0.4s ease;
|
|
3700
|
+
}
|
|
3701
|
+
.onboarding-tour-btn {
|
|
3702
|
+
width: 100%;
|
|
3703
|
+
margin-top: 12px;
|
|
3704
|
+
}
|
|
3705
|
+
.onboarding-dismiss {
|
|
3706
|
+
display: block;
|
|
3707
|
+
text-align: center;
|
|
3708
|
+
margin-top: 8px;
|
|
3709
|
+
background: none;
|
|
3710
|
+
border: none;
|
|
3711
|
+
color: var(--text-muted);
|
|
3712
|
+
font-size: 12px;
|
|
3713
|
+
cursor: pointer;
|
|
3714
|
+
font-family: var(--font-sans);
|
|
3715
|
+
width: 100%;
|
|
3716
|
+
padding: 4px 0;
|
|
3717
|
+
}
|
|
3718
|
+
.onboarding-dismiss:hover { color: var(--text); }
|
|
3719
|
+
|
|
3720
|
+
@media (max-width: 900px) {
|
|
3721
|
+
.tour-tooltip {
|
|
3722
|
+
max-width: 90vw;
|
|
3723
|
+
padding: 20px;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3566
3727
|
/* ================================================================
|
|
3567
3728
|
COSTS PAGE
|
|
3568
3729
|
================================================================ */
|
|
@@ -4043,7 +4204,7 @@ function Sidebar({ currentHash, factoryName, theme, onToggleTheme, selectedProje
|
|
|
4043
4204
|
];
|
|
4044
4205
|
|
|
4045
4206
|
return html`
|
|
4046
|
-
<aside class=${'sidebar' + (mobileOpen ? ' open' : '')}>
|
|
4207
|
+
<aside class=${'sidebar' + (mobileOpen ? ' open' : '')} data-tour="sidebar">
|
|
4047
4208
|
<div class="sidebar-brand">
|
|
4048
4209
|
<svg class="brand-logo-svg" viewBox="0 0 400 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="BeastMode — Dark Factory">
|
|
4049
4210
|
<g transform="translate(200,130)">
|
|
@@ -4093,7 +4254,8 @@ function Sidebar({ currentHash, factoryName, theme, onToggleTheme, selectedProje
|
|
|
4093
4254
|
${links.map(link => html`
|
|
4094
4255
|
<li key=${link.path}>
|
|
4095
4256
|
<a href=${link.path}
|
|
4096
|
-
class=${currentHash === link.path || (link.path !== '#/' && currentHash.startsWith(link.path)) ? 'active' : ''}
|
|
4257
|
+
class=${currentHash === link.path || (link.path !== '#/' && currentHash.startsWith(link.path)) ? 'active' : ''}
|
|
4258
|
+
data-tour=${link.path === '#/help' ? 'help-link' : null}>
|
|
4097
4259
|
<${Icon} name=${link.icon} />
|
|
4098
4260
|
${link.label}
|
|
4099
4261
|
</a>
|
|
@@ -4134,11 +4296,15 @@ function statusBadgeClass(status) {
|
|
|
4134
4296
|
function DashboardPage({ selectedProject, onProjectChange }) {
|
|
4135
4297
|
const [status, setStatus] = useState(null);
|
|
4136
4298
|
const [recentItems, setRecentItems] = useState([]);
|
|
4299
|
+
const [allItems, setAllItems] = useState([]);
|
|
4137
4300
|
const [runs, setRuns] = useState([]);
|
|
4138
4301
|
const [projects, setProjects] = useState([]);
|
|
4139
4302
|
const [runnerData, setRunnerData] = useState({ count: 0, offline: 0 });
|
|
4140
4303
|
const [loading, setLoading] = useState(true);
|
|
4141
4304
|
const [error, setError] = useState(null);
|
|
4305
|
+
const [onboardingDismissed, setOnboardingDismissed] = useState(() => {
|
|
4306
|
+
try { return !!localStorage.getItem('beastmode-onboarding-dismissed'); } catch (_) { return false; }
|
|
4307
|
+
});
|
|
4142
4308
|
|
|
4143
4309
|
useEffect(() => {
|
|
4144
4310
|
function fetchDashboard() {
|
|
@@ -4149,7 +4315,10 @@ function DashboardPage({ selectedProject, onProjectChange }) {
|
|
|
4149
4315
|
api('GET', '/api/projects').catch(() => ({ projects: [] })),
|
|
4150
4316
|
]).then(([s, boardData, runsData, projectsData]) => {
|
|
4151
4317
|
setStatus(s);
|
|
4152
|
-
const
|
|
4318
|
+
const allBoardItems = boardData.items || [];
|
|
4319
|
+
setAllItems(allBoardItems);
|
|
4320
|
+
const sorted = allBoardItems
|
|
4321
|
+
.slice()
|
|
4153
4322
|
.sort((a, b) => new Date(normalizeDt(b.updated_at || b.created_at) || 0) - new Date(normalizeDt(a.updated_at || a.created_at) || 0))
|
|
4154
4323
|
.slice(0, 10);
|
|
4155
4324
|
setRecentItems(sorted);
|
|
@@ -4336,6 +4505,48 @@ function DashboardPage({ selectedProject, onProjectChange }) {
|
|
|
4336
4505
|
</div>
|
|
4337
4506
|
|
|
4338
4507
|
<div class="page-dashboard-sidebar">
|
|
4508
|
+
${!onboardingDismissed && (() => {
|
|
4509
|
+
const hasProject = projects.length >= 1;
|
|
4510
|
+
const hasItem = allItems.length >= 1;
|
|
4511
|
+
const hasDone = allItems.some(i => (i.status || '').toLowerCase() === 'done');
|
|
4512
|
+
const checks = [
|
|
4513
|
+
{ label: 'Add a project', done: hasProject },
|
|
4514
|
+
{ label: 'Create your first task', done: hasItem },
|
|
4515
|
+
{ label: 'Complete a task', done: hasDone },
|
|
4516
|
+
];
|
|
4517
|
+
const completedCount = checks.filter(c => c.done).length;
|
|
4518
|
+
const allDone = completedCount === checks.length;
|
|
4519
|
+
const dismiss = () => {
|
|
4520
|
+
try { localStorage.setItem('beastmode-onboarding-dismissed', 'true'); } catch (_) {}
|
|
4521
|
+
setOnboardingDismissed(true);
|
|
4522
|
+
};
|
|
4523
|
+
return html`
|
|
4524
|
+
<div class="onboarding-checklist" data-testid="onboarding-checklist">
|
|
4525
|
+
<h3>Getting Started</h3>
|
|
4526
|
+
<p class="subtitle">Complete these steps to get up and running</p>
|
|
4527
|
+
${allDone ? html`
|
|
4528
|
+
<p style="font-size:14px;color:var(--success);font-weight:600;margin:8px 0 0;" data-testid="onboarding-congrats">
|
|
4529
|
+
You're all set! BeastMode is ready to build.
|
|
4530
|
+
</p>
|
|
4531
|
+
` : html`
|
|
4532
|
+
<div class="onboarding-progress">
|
|
4533
|
+
<div class="onboarding-progress-fill" style=${'width:' + ((completedCount / checks.length) * 100) + '%'}></div>
|
|
4534
|
+
</div>
|
|
4535
|
+
${checks.map((c, i) => html`
|
|
4536
|
+
<div key=${i} class=${'checklist-item' + (c.done ? ' completed' : '')} data-testid=${'checklist-item-' + i}>
|
|
4537
|
+
<div class=${'checklist-check' + (c.done ? ' done' : '')}>${c.done ? '✓' : ''}</div>
|
|
4538
|
+
<span>${c.label}</span>
|
|
4539
|
+
</div>
|
|
4540
|
+
`)}
|
|
4541
|
+
<button class="btn btn-primary btn-pill onboarding-tour-btn" data-testid="onboarding-tour-btn"
|
|
4542
|
+
onClick=${() => { if (typeof window.__bmStartTour === 'function') window.__bmStartTour(); }}>
|
|
4543
|
+
Take the guided tour
|
|
4544
|
+
</button>
|
|
4545
|
+
`}
|
|
4546
|
+
<button class="onboarding-dismiss" data-testid="onboarding-dismiss" onClick=${dismiss}>Dismiss</button>
|
|
4547
|
+
</div>
|
|
4548
|
+
`;
|
|
4549
|
+
})()}
|
|
4339
4550
|
<div class="card">
|
|
4340
4551
|
<div class="card-header">
|
|
4341
4552
|
<h3>Recent Activity</h3>
|
|
@@ -6981,7 +7192,7 @@ function BoardPage({ selectedProject }) {
|
|
|
6981
7192
|
|
|
6982
7193
|
${error && html`<div class="error-msg">${error}</div>`}
|
|
6983
7194
|
|
|
6984
|
-
<div class="board-header-row">
|
|
7195
|
+
<div class="board-header-row" data-tour="board-header">
|
|
6985
7196
|
<div class="board-stats-bar">
|
|
6986
7197
|
<span class="stat-label">${viewMode === 'pipeline' ? 'Pipeline' : 'Board'}</span>
|
|
6987
7198
|
<span class="stat-item"><strong>${totalItems}</strong>${isFiltered ? '/' + items.length : ''} total</span>
|
|
@@ -7044,7 +7255,7 @@ function BoardPage({ selectedProject }) {
|
|
|
7044
7255
|
`}
|
|
7045
7256
|
</div>
|
|
7046
7257
|
|
|
7047
|
-
<button class="btn btn-primary" onClick=${() => setShowCreateDialog(true)}>
|
|
7258
|
+
<button class="btn btn-primary" data-tour="add-item" onClick=${() => setShowCreateDialog(true)}>
|
|
7048
7259
|
<${Icon} name="plus" size=${14} />
|
|
7049
7260
|
New Task
|
|
7050
7261
|
</button>
|
|
@@ -9032,10 +9243,33 @@ function HooksTab() {
|
|
|
9032
9243
|
// ================================================================
|
|
9033
9244
|
|
|
9034
9245
|
function HelpPage() {
|
|
9246
|
+
const startTour = () => {
|
|
9247
|
+
navigate('#/board');
|
|
9248
|
+
setTimeout(() => {
|
|
9249
|
+
if (typeof window.__bmStartTour === 'function') window.__bmStartTour();
|
|
9250
|
+
}, 300);
|
|
9251
|
+
};
|
|
9035
9252
|
return html`
|
|
9036
9253
|
<div>
|
|
9037
9254
|
<h2 style="margin:0 0 24px;font-size:22px;font-weight:700;color:var(--text-primary);">Help & Documentation</h2>
|
|
9038
9255
|
|
|
9256
|
+
<div style="margin-bottom: 24px; padding: 16px; background: var(--surface-elevated);
|
|
9257
|
+
border: 1px solid var(--border); border-radius: 8px;
|
|
9258
|
+
display: flex; align-items: center; justify-content: space-between;"
|
|
9259
|
+
data-testid="help-tour-card">
|
|
9260
|
+
<div>
|
|
9261
|
+
<div style="font-weight: 600; color: var(--text); margin-bottom: 4px;">
|
|
9262
|
+
Interactive Tour
|
|
9263
|
+
</div>
|
|
9264
|
+
<div style="font-size: 13px; color: var(--text-muted);">
|
|
9265
|
+
Walk through the key features of BeastMode step by step.
|
|
9266
|
+
</div>
|
|
9267
|
+
</div>
|
|
9268
|
+
<button class="btn btn-primary" data-testid="help-start-tour" onClick=${startTour}>
|
|
9269
|
+
Start Tour
|
|
9270
|
+
</button>
|
|
9271
|
+
</div>
|
|
9272
|
+
|
|
9039
9273
|
<div class="settings-section">
|
|
9040
9274
|
<h3>Getting Started</h3>
|
|
9041
9275
|
<div style="line-height:1.8;color:var(--text-secondary);">
|
|
@@ -10861,6 +11095,211 @@ function FactoryStatusBar() {
|
|
|
10861
11095
|
// App Root
|
|
10862
11096
|
// ================================================================
|
|
10863
11097
|
|
|
11098
|
+
// ================================================================
|
|
11099
|
+
// Onboarding Tour
|
|
11100
|
+
// ================================================================
|
|
11101
|
+
|
|
11102
|
+
const TOUR_STEPS = [
|
|
11103
|
+
{
|
|
11104
|
+
target: null,
|
|
11105
|
+
title: 'Welcome to BeastMode',
|
|
11106
|
+
body: "BeastMode turns your ideas into working software — describe what you want in plain language, and the pipeline writes the spec, code, and tests. Let's take a quick tour.",
|
|
11107
|
+
placement: 'center',
|
|
11108
|
+
},
|
|
11109
|
+
{
|
|
11110
|
+
target: '[data-tour="sidebar"]',
|
|
11111
|
+
title: 'Navigation',
|
|
11112
|
+
body: 'Use the sidebar to move between pages. The Board is where your tasks live. The Dashboard shows factory health and stats.',
|
|
11113
|
+
placement: 'right',
|
|
11114
|
+
},
|
|
11115
|
+
{
|
|
11116
|
+
target: '[data-tour="board-header"]',
|
|
11117
|
+
title: 'Your Task Board',
|
|
11118
|
+
body: 'Tasks flow through the pipeline from left to right: Ready → Working on it → Review → Done. BeastMode handles each stage automatically.',
|
|
11119
|
+
placement: 'bottom',
|
|
11120
|
+
},
|
|
11121
|
+
{
|
|
11122
|
+
target: '[data-tour="add-item"]',
|
|
11123
|
+
title: 'Create a Task',
|
|
11124
|
+
body: 'Click here to create a new task. Describe what you want to build — BeastMode writes the spec, plans the work, and implements it.',
|
|
11125
|
+
placement: 'bottom-start',
|
|
11126
|
+
},
|
|
11127
|
+
{
|
|
11128
|
+
target: '[data-tour="help-link"]',
|
|
11129
|
+
title: 'Need Help?',
|
|
11130
|
+
body: 'Visit the Help page anytime for guides on task types, pipeline stages, and troubleshooting tips.',
|
|
11131
|
+
placement: 'right',
|
|
11132
|
+
},
|
|
11133
|
+
{
|
|
11134
|
+
target: null,
|
|
11135
|
+
title: "You're Ready!",
|
|
11136
|
+
body: 'Start by creating your first task. BeastMode takes it from there. You can restart this tour anytime from the Help page.',
|
|
11137
|
+
placement: 'center',
|
|
11138
|
+
},
|
|
11139
|
+
];
|
|
11140
|
+
|
|
11141
|
+
function OnboardingTour() {
|
|
11142
|
+
const [currentStep, setCurrentStep] = useState(null);
|
|
11143
|
+
const [targetRect, setTargetRect] = useState(null);
|
|
11144
|
+
|
|
11145
|
+
// Auto-launch on first visit.
|
|
11146
|
+
useEffect(() => {
|
|
11147
|
+
let cancelled = false;
|
|
11148
|
+
try {
|
|
11149
|
+
if (localStorage.getItem('beastmode-tour-completed')) return;
|
|
11150
|
+
} catch (_) { return; }
|
|
11151
|
+
if (!location.hash || location.hash === '#/' || location.hash === '#') {
|
|
11152
|
+
navigate('#/board');
|
|
11153
|
+
}
|
|
11154
|
+
const t = setTimeout(() => {
|
|
11155
|
+
if (!cancelled) setCurrentStep(0);
|
|
11156
|
+
}, 600);
|
|
11157
|
+
return () => { cancelled = true; clearTimeout(t); };
|
|
11158
|
+
}, []);
|
|
11159
|
+
|
|
11160
|
+
// Expose manual launcher globally for Dashboard checklist + Help page button.
|
|
11161
|
+
useEffect(() => {
|
|
11162
|
+
window.__bmStartTour = () => setCurrentStep(0);
|
|
11163
|
+
return () => { try { delete window.__bmStartTour; } catch (_) {} };
|
|
11164
|
+
}, []);
|
|
11165
|
+
|
|
11166
|
+
// Recalculate target rect on step change and on resize.
|
|
11167
|
+
useEffect(() => {
|
|
11168
|
+
if (currentStep === null) {
|
|
11169
|
+
setTargetRect(null);
|
|
11170
|
+
return;
|
|
11171
|
+
}
|
|
11172
|
+
const step = TOUR_STEPS[currentStep];
|
|
11173
|
+
function measure() {
|
|
11174
|
+
if (!step || !step.target) {
|
|
11175
|
+
setTargetRect(null);
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
const el = document.querySelector(step.target);
|
|
11179
|
+
if (!el) {
|
|
11180
|
+
setTargetRect(null);
|
|
11181
|
+
return;
|
|
11182
|
+
}
|
|
11183
|
+
const r = el.getBoundingClientRect();
|
|
11184
|
+
setTargetRect({ top: r.top, left: r.left, right: r.right, bottom: r.bottom, width: r.width, height: r.height });
|
|
11185
|
+
}
|
|
11186
|
+
measure();
|
|
11187
|
+
let timer = null;
|
|
11188
|
+
const onResize = () => {
|
|
11189
|
+
if (timer) clearTimeout(timer);
|
|
11190
|
+
timer = setTimeout(measure, 100);
|
|
11191
|
+
};
|
|
11192
|
+
window.addEventListener('resize', onResize);
|
|
11193
|
+
window.addEventListener('scroll', onResize, true);
|
|
11194
|
+
return () => {
|
|
11195
|
+
window.removeEventListener('resize', onResize);
|
|
11196
|
+
window.removeEventListener('scroll', onResize, true);
|
|
11197
|
+
if (timer) clearTimeout(timer);
|
|
11198
|
+
};
|
|
11199
|
+
}, [currentStep]);
|
|
11200
|
+
|
|
11201
|
+
const completeTour = useCallback(() => {
|
|
11202
|
+
try { localStorage.setItem('beastmode-tour-completed', 'true'); } catch (_) {}
|
|
11203
|
+
setCurrentStep(null);
|
|
11204
|
+
}, []);
|
|
11205
|
+
|
|
11206
|
+
// Escape to skip.
|
|
11207
|
+
useEffect(() => {
|
|
11208
|
+
if (currentStep === null) return;
|
|
11209
|
+
const onKey = (e) => {
|
|
11210
|
+
if (e.key === 'Escape') {
|
|
11211
|
+
e.preventDefault();
|
|
11212
|
+
completeTour();
|
|
11213
|
+
}
|
|
11214
|
+
};
|
|
11215
|
+
document.addEventListener('keydown', onKey);
|
|
11216
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
11217
|
+
}, [currentStep, completeTour]);
|
|
11218
|
+
|
|
11219
|
+
if (currentStep === null) return null;
|
|
11220
|
+
|
|
11221
|
+
const step = TOUR_STEPS[currentStep];
|
|
11222
|
+
const isLast = currentStep === TOUR_STEPS.length - 1;
|
|
11223
|
+
const padding = 8;
|
|
11224
|
+
|
|
11225
|
+
// Spotlight position
|
|
11226
|
+
const spotlightStyle = targetRect ? {
|
|
11227
|
+
top: (targetRect.top - padding) + 'px',
|
|
11228
|
+
left: (targetRect.left - padding) + 'px',
|
|
11229
|
+
width: (targetRect.width + padding * 2) + 'px',
|
|
11230
|
+
height: (targetRect.height + padding * 2) + 'px',
|
|
11231
|
+
} : null;
|
|
11232
|
+
|
|
11233
|
+
// Tooltip position
|
|
11234
|
+
const tooltipWidth = 380;
|
|
11235
|
+
const tooltipApproxHeight = 220;
|
|
11236
|
+
const margin = 16;
|
|
11237
|
+
let tooltipStyle = {};
|
|
11238
|
+
if (!targetRect || step.placement === 'center') {
|
|
11239
|
+
tooltipStyle = {
|
|
11240
|
+
top: '50%',
|
|
11241
|
+
left: '50%',
|
|
11242
|
+
transform: 'translate(-50%, -50%)',
|
|
11243
|
+
};
|
|
11244
|
+
} else if (step.placement === 'right') {
|
|
11245
|
+
let top = targetRect.top + targetRect.height / 2 - tooltipApproxHeight / 2;
|
|
11246
|
+
let left = targetRect.right + margin;
|
|
11247
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - tooltipApproxHeight - margin));
|
|
11248
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin));
|
|
11249
|
+
tooltipStyle = { top: top + 'px', left: left + 'px' };
|
|
11250
|
+
} else if (step.placement === 'bottom') {
|
|
11251
|
+
let top = targetRect.bottom + margin;
|
|
11252
|
+
let left = targetRect.left + targetRect.width / 2 - tooltipWidth / 2;
|
|
11253
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - tooltipApproxHeight - margin));
|
|
11254
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin));
|
|
11255
|
+
tooltipStyle = { top: top + 'px', left: left + 'px' };
|
|
11256
|
+
} else if (step.placement === 'bottom-start') {
|
|
11257
|
+
let top = targetRect.bottom + margin;
|
|
11258
|
+
let left = targetRect.left;
|
|
11259
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - tooltipApproxHeight - margin));
|
|
11260
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin));
|
|
11261
|
+
tooltipStyle = { top: top + 'px', left: left + 'px' };
|
|
11262
|
+
} else {
|
|
11263
|
+
tooltipStyle = { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };
|
|
11264
|
+
}
|
|
11265
|
+
|
|
11266
|
+
const styleStr = (obj) => Object.entries(obj).map(([k, v]) => {
|
|
11267
|
+
const kebab = k.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
11268
|
+
return kebab + ':' + v;
|
|
11269
|
+
}).join(';');
|
|
11270
|
+
|
|
11271
|
+
return html`
|
|
11272
|
+
<div>
|
|
11273
|
+
<div class="tour-backdrop" onClick=${completeTour}></div>
|
|
11274
|
+
${spotlightStyle ? html`<div class="tour-spotlight" style=${styleStr(spotlightStyle)}></div>` : null}
|
|
11275
|
+
<div class="tour-tooltip" data-testid="tour-tooltip" style=${styleStr(tooltipStyle)} onClick=${(e) => e.stopPropagation()}>
|
|
11276
|
+
<div class="tour-step-indicator">
|
|
11277
|
+
${TOUR_STEPS.map((_, i) => html`
|
|
11278
|
+
<span key=${i} class=${'tour-step-dot' + (i === currentStep ? ' active' : '')}></span>
|
|
11279
|
+
`)}
|
|
11280
|
+
</div>
|
|
11281
|
+
<h3>${step.title}</h3>
|
|
11282
|
+
<p>${step.body}</p>
|
|
11283
|
+
<div class="tour-actions">
|
|
11284
|
+
<button class="tour-skip" data-testid="tour-skip" onClick=${completeTour}>Skip Tour</button>
|
|
11285
|
+
<div class="tour-actions-right">
|
|
11286
|
+
${currentStep > 0 && html`
|
|
11287
|
+
<button class="btn" data-testid="tour-back" onClick=${() => setCurrentStep(s => s - 1)}>Back</button>
|
|
11288
|
+
`}
|
|
11289
|
+
<button class="btn btn-primary" data-testid="tour-next" onClick=${() => {
|
|
11290
|
+
if (isLast) {
|
|
11291
|
+
completeTour();
|
|
11292
|
+
} else {
|
|
11293
|
+
setCurrentStep(s => s + 1);
|
|
11294
|
+
}
|
|
11295
|
+
}}>${isLast ? 'Get Started' : 'Next'}</button>
|
|
11296
|
+
</div>
|
|
11297
|
+
</div>
|
|
11298
|
+
</div>
|
|
11299
|
+
</div>
|
|
11300
|
+
`;
|
|
11301
|
+
}
|
|
11302
|
+
|
|
10864
11303
|
function App() {
|
|
10865
11304
|
const hash = useHash();
|
|
10866
11305
|
const { theme, toggle: toggleTheme } = useTheme();
|
|
@@ -10942,6 +11381,7 @@ function App() {
|
|
|
10942
11381
|
<${FactoryStatusBar} />
|
|
10943
11382
|
${page}
|
|
10944
11383
|
</main>
|
|
11384
|
+
<${OnboardingTour} />
|
|
10945
11385
|
</div>
|
|
10946
11386
|
`;
|
|
10947
11387
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5de247da4bd212025aff128ba45b5fbe9dfc535b
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260510-004632-5de247d
|