rails_onboarding 0.8.6 → 0.8.8
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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/rails_onboarding/tour_controller.js +112 -43
- data/app/assets/stylesheets/rails_onboarding/milestones.css +41 -2
- data/app/assets/stylesheets/rails_onboarding/tour.css +44 -99
- data/app/views/rails_onboarding/milestones/index.html.erb +20 -8
- data/lib/rails_onboarding/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 408fd1e294065de10f66f097028bca6ea0ef929c2da585fb5fe20034a14e0202
|
|
4
|
+
data.tar.gz: 2e015226988b1bda4bf5594d43c00341f4a9fb2c21c020b0607446be87dfe671
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f924adbe61c18fc3b8070653655b04d74bd01a97562ed5eaa274d43276c07a331a11ef233ceab5484f545b20779485cbe55417921516bc9c2cd0557da694884
|
|
7
|
+
data.tar.gz: 678c83c71d204f027f92d65555af8cff1b3aecd53992cda79e5d234f5deffaf3a465e4b786295bc5ca2319b97a74946750e3a98083a45b8f1ad5ea4a37f3d4c1
|
|
@@ -39,6 +39,7 @@ export default class extends Controller {
|
|
|
39
39
|
|
|
40
40
|
this.parseSteps()
|
|
41
41
|
this.setupKeyboardHandlers()
|
|
42
|
+
this.setupViewportHandlers()
|
|
42
43
|
|
|
43
44
|
// Auto-start if configured and not completed
|
|
44
45
|
if (this.autoStartValue && !this.isTourCompleted()) {
|
|
@@ -188,6 +189,8 @@ export default class extends Controller {
|
|
|
188
189
|
// Find target element
|
|
189
190
|
const targetElement = step.selector ? document.querySelector(step.selector) : null
|
|
190
191
|
|
|
192
|
+
this.currentTargetElement = targetElement
|
|
193
|
+
|
|
191
194
|
if (targetElement) {
|
|
192
195
|
this.scrollToElement(targetElement, step)
|
|
193
196
|
this.createHighlight(targetElement, step)
|
|
@@ -277,8 +280,12 @@ export default class extends Controller {
|
|
|
277
280
|
|
|
278
281
|
document.body.appendChild(this.overlay)
|
|
279
282
|
|
|
280
|
-
//
|
|
281
|
-
|
|
283
|
+
// Deliberately no `document.body.style.overflow = 'hidden'` here. The overlay is
|
|
284
|
+
// position:fixed and already covers the viewport, and locking the body made
|
|
285
|
+
// scrollToElement()'s window.scrollTo a no-op - so a target below the fold was
|
|
286
|
+
// spotlit off-screen, since the spotlight and popup are placed from
|
|
287
|
+
// getBoundingClientRect(). Scrolling stays live and repositionCurrentStep()
|
|
288
|
+
// keeps the highlight glued to its element instead.
|
|
282
289
|
|
|
283
290
|
// Trigger fade in
|
|
284
291
|
requestAnimationFrame(() => {
|
|
@@ -290,18 +297,14 @@ export default class extends Controller {
|
|
|
290
297
|
* Remove modal overlay
|
|
291
298
|
*/
|
|
292
299
|
removeOverlay() {
|
|
293
|
-
|
|
300
|
+
const overlay = this.overlay
|
|
301
|
+
if (!overlay) return
|
|
294
302
|
|
|
295
|
-
this.overlay
|
|
303
|
+
this.overlay = null
|
|
304
|
+
overlay.style.opacity = '0'
|
|
296
305
|
|
|
297
306
|
setTimeout(() => {
|
|
298
|
-
if (
|
|
299
|
-
this.overlay.parentNode.removeChild(this.overlay)
|
|
300
|
-
}
|
|
301
|
-
this.overlay = null
|
|
302
|
-
|
|
303
|
-
// Restore body scroll
|
|
304
|
-
document.body.style.overflow = ''
|
|
307
|
+
if (overlay.parentNode) overlay.parentNode.removeChild(overlay)
|
|
305
308
|
}, 300)
|
|
306
309
|
}
|
|
307
310
|
|
|
@@ -372,7 +375,11 @@ export default class extends Controller {
|
|
|
372
375
|
|
|
373
376
|
document.body.appendChild(this.spotlight)
|
|
374
377
|
|
|
375
|
-
// Make highlighted element interactive
|
|
378
|
+
// Make highlighted element interactive, remembering what we overwrote so
|
|
379
|
+
// removeHighlight() can put it back without searching the document.
|
|
380
|
+
this.highlightedElement = element
|
|
381
|
+
this.previousElementPosition = element.style.position
|
|
382
|
+
this.previousElementZIndex = element.style.zIndex
|
|
376
383
|
element.style.position = 'relative'
|
|
377
384
|
element.style.zIndex = '10000'
|
|
378
385
|
}
|
|
@@ -388,10 +395,14 @@ export default class extends Controller {
|
|
|
388
395
|
this.spotlight = null
|
|
389
396
|
}
|
|
390
397
|
|
|
391
|
-
//
|
|
392
|
-
document
|
|
393
|
-
|
|
394
|
-
|
|
398
|
+
// Restore only the element we actually touched. This used to sweep the whole
|
|
399
|
+
// document for [style*="z-index: 10000"], which clears the inline z-index of
|
|
400
|
+
// unrelated host-app elements that happen to carry that value.
|
|
401
|
+
if (this.highlightedElement) {
|
|
402
|
+
this.highlightedElement.style.position = this.previousElementPosition || ''
|
|
403
|
+
this.highlightedElement.style.zIndex = this.previousElementZIndex || ''
|
|
404
|
+
this.highlightedElement = null
|
|
405
|
+
}
|
|
395
406
|
}
|
|
396
407
|
|
|
397
408
|
/**
|
|
@@ -425,20 +436,20 @@ export default class extends Controller {
|
|
|
425
436
|
|
|
426
437
|
<div class="tour-popup-actions">
|
|
427
438
|
${step.showSkip ? `
|
|
428
|
-
<button type="button" class="tour-btn tour-btn-skip"
|
|
439
|
+
<button type="button" class="tour-btn tour-btn-skip">
|
|
429
440
|
${step.skipLabel}
|
|
430
441
|
</button>
|
|
431
442
|
` : '<div></div>'}
|
|
432
443
|
|
|
433
444
|
<div class="tour-popup-nav">
|
|
434
445
|
${step.showPrev && !isFirstStep ? `
|
|
435
|
-
<button type="button" class="tour-btn tour-btn-prev"
|
|
446
|
+
<button type="button" class="tour-btn tour-btn-prev">
|
|
436
447
|
← ${step.prevLabel}
|
|
437
448
|
</button>
|
|
438
449
|
` : ''}
|
|
439
450
|
|
|
440
451
|
${step.showNext ? `
|
|
441
|
-
<button type="button" class="tour-btn tour-btn-next"
|
|
452
|
+
<button type="button" class="tour-btn tour-btn-next">
|
|
442
453
|
${isLastStep ? step.completeLabel : step.nextLabel} →
|
|
443
454
|
</button>
|
|
444
455
|
` : ''}
|
|
@@ -452,32 +463,49 @@ export default class extends Controller {
|
|
|
452
463
|
|
|
453
464
|
document.body.appendChild(this.popup)
|
|
454
465
|
|
|
466
|
+
// The popup lives on document.body, so that host overflow and stacking
|
|
467
|
+
// contexts cannot clip it - which also puts it outside every controller
|
|
468
|
+
// scope, where Stimulus will not bind a data-action. These buttons are
|
|
469
|
+
// wired directly for that reason; a data-action on them would be silently
|
|
470
|
+
// inert, which is what left the tour unable to advance at all.
|
|
471
|
+
this.bindPopupActions()
|
|
472
|
+
|
|
455
473
|
// Position popup relative to target
|
|
456
474
|
this.positionPopup(step, targetElement)
|
|
457
475
|
|
|
458
476
|
// Animate in
|
|
459
477
|
requestAnimationFrame(() => {
|
|
460
|
-
this.popup.
|
|
461
|
-
this.popup.style.transform = 'scale(1)'
|
|
478
|
+
if (this.popup) this.popup.classList.add('onboarding-show')
|
|
462
479
|
})
|
|
463
480
|
}
|
|
464
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Wire the popup's own controls. See the note in createPopup().
|
|
484
|
+
*/
|
|
485
|
+
bindPopupActions() {
|
|
486
|
+
const actions = {
|
|
487
|
+
'.tour-btn-next': () => this.next(),
|
|
488
|
+
'.tour-btn-prev': () => this.previous(),
|
|
489
|
+
'.tour-btn-skip': () => this.skip()
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
for (const [selector, handler] of Object.entries(actions)) {
|
|
493
|
+
this.popup.querySelector(selector)?.addEventListener('click', (event) => {
|
|
494
|
+
event.preventDefault()
|
|
495
|
+
handler()
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
465
500
|
/**
|
|
466
501
|
* Style the popup element
|
|
467
502
|
*/
|
|
468
503
|
stylePopup(step) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
475
|
-
max-width: ${step.width}px;
|
|
476
|
-
width: calc(100% - 2rem);
|
|
477
|
-
opacity: 0;
|
|
478
|
-
transform: scale(0.95);
|
|
479
|
-
transition: all 0.3s ease;
|
|
480
|
-
`
|
|
504
|
+
// Only the per-step width is set inline. Everything else - including the
|
|
505
|
+
// background - belongs to `.tour-popup` in tour.css, so that a host app
|
|
506
|
+
// redefining the --onboarding-* tokens (and the dark-mode block that flips
|
|
507
|
+
// them) actually reaches the popup. An inline background would win over both.
|
|
508
|
+
this.popup.style.maxWidth = `${step.width}px`
|
|
481
509
|
}
|
|
482
510
|
|
|
483
511
|
/**
|
|
@@ -569,17 +597,19 @@ export default class extends Controller {
|
|
|
569
597
|
* Remove popup
|
|
570
598
|
*/
|
|
571
599
|
removePopup() {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
this.popup.style.transform = 'scale(0.95)'
|
|
600
|
+
const popup = this.popup
|
|
601
|
+
if (!popup) return
|
|
575
602
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
603
|
+
// Detach from the instance up front and let the timeout close over the local.
|
|
604
|
+
// createPopup() calls removePopup() and then assigns a new this.popup straight
|
|
605
|
+
// away, so a timeout reading the instance property would remove the popup that
|
|
606
|
+
// replaced this one - every Next and Previous would blank the tour after 300ms.
|
|
607
|
+
this.popup = null
|
|
608
|
+
popup.classList.remove('onboarding-show')
|
|
609
|
+
|
|
610
|
+
setTimeout(() => {
|
|
611
|
+
if (popup.parentNode) popup.parentNode.removeChild(popup)
|
|
612
|
+
}, 300)
|
|
583
613
|
}
|
|
584
614
|
|
|
585
615
|
/**
|
|
@@ -623,6 +653,38 @@ export default class extends Controller {
|
|
|
623
653
|
}
|
|
624
654
|
}
|
|
625
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Re-place the highlight and popup for the current step. The spotlight and popup
|
|
658
|
+
* are position:fixed and derived from getBoundingClientRect(), so they have to be
|
|
659
|
+
* recomputed whenever the viewport moves under them.
|
|
660
|
+
*/
|
|
661
|
+
repositionCurrentStep() {
|
|
662
|
+
if (!this.isActive) return
|
|
663
|
+
|
|
664
|
+
const step = this.tourSteps[this.currentStepIndex]
|
|
665
|
+
if (!step) return
|
|
666
|
+
|
|
667
|
+
const element = this.currentTargetElement
|
|
668
|
+
if (element && element.isConnected) {
|
|
669
|
+
this.createHighlight(element, step)
|
|
670
|
+
}
|
|
671
|
+
this.positionPopup(step, element)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Setup viewport handlers that keep the current step aligned
|
|
676
|
+
*/
|
|
677
|
+
setupViewportHandlers() {
|
|
678
|
+
this.viewportHandler = () => {
|
|
679
|
+
if (!this.isActive) return
|
|
680
|
+
if (this.viewportFrame) cancelAnimationFrame(this.viewportFrame)
|
|
681
|
+
this.viewportFrame = requestAnimationFrame(() => this.repositionCurrentStep())
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
window.addEventListener('scroll', this.viewportHandler, { passive: true })
|
|
685
|
+
window.addEventListener('resize', this.viewportHandler)
|
|
686
|
+
}
|
|
687
|
+
|
|
626
688
|
/**
|
|
627
689
|
* Setup keyboard event handlers
|
|
628
690
|
*/
|
|
@@ -759,5 +821,12 @@ export default class extends Controller {
|
|
|
759
821
|
if (this.keyboardHandler) {
|
|
760
822
|
document.removeEventListener('keydown', this.keyboardHandler)
|
|
761
823
|
}
|
|
824
|
+
|
|
825
|
+
if (this.viewportHandler) {
|
|
826
|
+
window.removeEventListener('scroll', this.viewportHandler)
|
|
827
|
+
window.removeEventListener('resize', this.viewportHandler)
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (this.viewportFrame) cancelAnimationFrame(this.viewportFrame)
|
|
762
831
|
}
|
|
763
832
|
}
|
|
@@ -19,12 +19,46 @@
|
|
|
19
19
|
font-size: 2.5rem;
|
|
20
20
|
font-weight: 700;
|
|
21
21
|
color: #2c3e50;
|
|
22
|
-
margin: 0;
|
|
22
|
+
margin: 0 1rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.milestone-header-actions {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 1.5rem;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
.milestone-stats {
|
|
26
32
|
display: flex;
|
|
27
|
-
gap:
|
|
33
|
+
gap: 1rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.milestone-home-link {
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 0.5rem;
|
|
40
|
+
padding: 0.75rem 1.25rem;
|
|
41
|
+
border: 2px solid #e5e7eb;
|
|
42
|
+
border-radius: 12px;
|
|
43
|
+
background: white;
|
|
44
|
+
color: #2c3e50;
|
|
45
|
+
font-size: 0.9375rem;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
text-decoration: none;
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
transition: border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.milestone-home-link:hover,
|
|
53
|
+
.milestone-home-link:focus-visible {
|
|
54
|
+
border-color: #667eea;
|
|
55
|
+
color: #667eea;
|
|
56
|
+
transform: translateY(-1px);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.milestone-home-icon {
|
|
60
|
+
font-size: 1.125rem;
|
|
61
|
+
line-height: 1;
|
|
28
62
|
}
|
|
29
63
|
|
|
30
64
|
.total-points,
|
|
@@ -490,6 +524,11 @@
|
|
|
490
524
|
text-align: center;
|
|
491
525
|
}
|
|
492
526
|
|
|
527
|
+
.milestone-header-actions {
|
|
528
|
+
flex-direction: column;
|
|
529
|
+
gap: 1rem;
|
|
530
|
+
}
|
|
531
|
+
|
|
493
532
|
.milestone-stats {
|
|
494
533
|
justify-content: center;
|
|
495
534
|
}
|
|
@@ -39,43 +39,43 @@
|
|
|
39
39
|
|
|
40
40
|
/* Border highlight style */
|
|
41
41
|
.tour-spotlight-border {
|
|
42
|
-
border: 3px solid
|
|
43
|
-
box-shadow: 0 0 0 4px
|
|
42
|
+
border: 3px solid var(--onboarding-primary);
|
|
43
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--onboarding-primary) 20%, transparent);
|
|
44
44
|
animation: onboarding-borderPulse 2s ease-in-out infinite;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
@keyframes onboarding-borderPulse {
|
|
48
48
|
0%, 100% {
|
|
49
|
-
border-color:
|
|
50
|
-
box-shadow: 0 0 0 4px
|
|
49
|
+
border-color: var(--onboarding-primary);
|
|
50
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--onboarding-primary) 20%, transparent);
|
|
51
51
|
}
|
|
52
52
|
50% {
|
|
53
|
-
border-color:
|
|
54
|
-
box-shadow: 0 0 0 6px
|
|
53
|
+
border-color: var(--onboarding-primary-hover);
|
|
54
|
+
box-shadow: 0 0 0 6px color-mix(in srgb, var(--onboarding-primary-hover) 30%, transparent);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/* Glow highlight style */
|
|
59
59
|
.tour-spotlight-glow {
|
|
60
60
|
box-shadow:
|
|
61
|
-
0 0 0 4px
|
|
62
|
-
0 0 20px 8px
|
|
63
|
-
inset 0 0 20px
|
|
61
|
+
0 0 0 4px color-mix(in srgb, var(--onboarding-primary) 30%, transparent),
|
|
62
|
+
0 0 20px 8px color-mix(in srgb, var(--onboarding-primary) 40%, transparent),
|
|
63
|
+
inset 0 0 20px color-mix(in srgb, var(--onboarding-primary) 20%, transparent);
|
|
64
64
|
animation: onboarding-glowPulse 2s ease-in-out infinite;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
@keyframes onboarding-glowPulse {
|
|
68
68
|
0%, 100% {
|
|
69
69
|
box-shadow:
|
|
70
|
-
0 0 0 4px
|
|
71
|
-
0 0 20px 8px
|
|
72
|
-
inset 0 0 20px
|
|
70
|
+
0 0 0 4px color-mix(in srgb, var(--onboarding-primary) 30%, transparent),
|
|
71
|
+
0 0 20px 8px color-mix(in srgb, var(--onboarding-primary) 40%, transparent),
|
|
72
|
+
inset 0 0 20px color-mix(in srgb, var(--onboarding-primary) 20%, transparent);
|
|
73
73
|
}
|
|
74
74
|
50% {
|
|
75
75
|
box-shadow:
|
|
76
|
-
0 0 0 6px
|
|
77
|
-
0 0 30px 12px
|
|
78
|
-
inset 0 0 30px
|
|
76
|
+
0 0 0 6px color-mix(in srgb, var(--onboarding-primary) 40%, transparent),
|
|
77
|
+
0 0 30px 12px color-mix(in srgb, var(--onboarding-primary) 50%, transparent),
|
|
78
|
+
inset 0 0 30px color-mix(in srgb, var(--onboarding-primary) 30%, transparent);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -83,7 +83,9 @@
|
|
|
83
83
|
.tour-popup {
|
|
84
84
|
position: fixed;
|
|
85
85
|
z-index: 10001;
|
|
86
|
-
background:
|
|
86
|
+
background: var(--onboarding-surface-elevated);
|
|
87
|
+
color: var(--onboarding-text);
|
|
88
|
+
border: 1px solid var(--onboarding-border);
|
|
87
89
|
border-radius: 12px;
|
|
88
90
|
box-shadow:
|
|
89
91
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
|
@@ -110,7 +112,7 @@
|
|
|
110
112
|
margin: 0 0 1rem;
|
|
111
113
|
font-size: 1.25rem;
|
|
112
114
|
font-weight: 600;
|
|
113
|
-
color:
|
|
115
|
+
color: var(--onboarding-text);
|
|
114
116
|
line-height: 1.4;
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -119,7 +121,7 @@
|
|
|
119
121
|
margin: 0 0 1.5rem;
|
|
120
122
|
font-size: 0.9375rem;
|
|
121
123
|
line-height: 1.6;
|
|
122
|
-
color:
|
|
124
|
+
color: var(--onboarding-text-muted);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
.tour-popup-body p {
|
|
@@ -142,12 +144,12 @@
|
|
|
142
144
|
|
|
143
145
|
.tour-popup-body strong {
|
|
144
146
|
font-weight: 600;
|
|
145
|
-
color:
|
|
147
|
+
color: var(--onboarding-text);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
.tour-popup-body code {
|
|
149
151
|
padding: 0.125rem 0.375rem;
|
|
150
|
-
background:
|
|
152
|
+
background: var(--onboarding-surface);
|
|
151
153
|
border-radius: 0.25rem;
|
|
152
154
|
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
153
155
|
font-size: 0.875em;
|
|
@@ -157,14 +159,14 @@
|
|
|
157
159
|
.tour-popup-progress {
|
|
158
160
|
margin: 1.5rem 0 1rem;
|
|
159
161
|
padding-top: 1rem;
|
|
160
|
-
border-top: 1px solid
|
|
162
|
+
border-top: 1px solid var(--onboarding-border);
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
/* Progress Bar */
|
|
164
166
|
.tour-progress-bar {
|
|
165
167
|
width: 100%;
|
|
166
168
|
height: 6px;
|
|
167
|
-
background:
|
|
169
|
+
background: var(--onboarding-border);
|
|
168
170
|
border-radius: 3px;
|
|
169
171
|
overflow: hidden;
|
|
170
172
|
margin-bottom: 0.5rem;
|
|
@@ -172,7 +174,7 @@
|
|
|
172
174
|
|
|
173
175
|
.tour-progress-fill {
|
|
174
176
|
height: 100%;
|
|
175
|
-
background: linear-gradient(90deg,
|
|
177
|
+
background: linear-gradient(90deg, var(--onboarding-primary-fill), var(--onboarding-primary-fill-hover));
|
|
176
178
|
border-radius: 3px;
|
|
177
179
|
transition: width 0.3s ease;
|
|
178
180
|
}
|
|
@@ -180,7 +182,7 @@
|
|
|
180
182
|
/* Progress Text */
|
|
181
183
|
.tour-progress-text {
|
|
182
184
|
font-size: 0.8125rem;
|
|
183
|
-
color:
|
|
185
|
+
color: var(--onboarding-text-muted);
|
|
184
186
|
text-align: center;
|
|
185
187
|
font-weight: 500;
|
|
186
188
|
}
|
|
@@ -217,7 +219,7 @@
|
|
|
217
219
|
|
|
218
220
|
.tour-btn:focus {
|
|
219
221
|
outline: none;
|
|
220
|
-
box-shadow:
|
|
222
|
+
box-shadow: var(--onboarding-focus-ring);
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
.tour-btn:active {
|
|
@@ -226,47 +228,47 @@
|
|
|
226
228
|
|
|
227
229
|
/* Next Button - Primary */
|
|
228
230
|
.tour-btn-next {
|
|
229
|
-
background:
|
|
230
|
-
color:
|
|
231
|
+
background: var(--onboarding-primary-fill);
|
|
232
|
+
color: var(--onboarding-on-primary);
|
|
231
233
|
padding: 0.625rem 1.25rem;
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
.tour-btn-next:hover {
|
|
235
|
-
background:
|
|
237
|
+
background: var(--onboarding-primary-fill-hover);
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
.tour-btn-next:active {
|
|
239
|
-
background:
|
|
241
|
+
background: var(--onboarding-primary-fill-hover);
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
/* Previous Button - Secondary */
|
|
243
245
|
.tour-btn-prev {
|
|
244
|
-
background:
|
|
245
|
-
color:
|
|
246
|
+
background: var(--onboarding-surface);
|
|
247
|
+
color: var(--onboarding-text);
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
.tour-btn-prev:hover {
|
|
249
|
-
background:
|
|
251
|
+
background: var(--onboarding-border);
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
.tour-btn-prev:active {
|
|
253
|
-
background:
|
|
255
|
+
background: var(--onboarding-border);
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
/* Skip Button - Ghost */
|
|
257
259
|
.tour-btn-skip {
|
|
258
260
|
background: transparent;
|
|
259
|
-
color:
|
|
261
|
+
color: var(--onboarding-text-muted);
|
|
260
262
|
padding: 0.5rem 0.75rem;
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
.tour-btn-skip:hover {
|
|
264
|
-
color:
|
|
265
|
-
background:
|
|
266
|
+
color: var(--onboarding-text);
|
|
267
|
+
background: var(--onboarding-surface);
|
|
266
268
|
}
|
|
267
269
|
|
|
268
270
|
.tour-btn-skip:active {
|
|
269
|
-
background:
|
|
271
|
+
background: var(--onboarding-border);
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
/* Mobile Responsive */
|
|
@@ -330,11 +332,11 @@
|
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
.tour-btn-next {
|
|
333
|
-
background:
|
|
335
|
+
background: var(--onboarding-primary-fill-hover);
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
.tour-btn-prev {
|
|
337
|
-
border: 2px solid
|
|
339
|
+
border: 2px solid var(--onboarding-border);
|
|
338
340
|
}
|
|
339
341
|
}
|
|
340
342
|
|
|
@@ -362,63 +364,6 @@
|
|
|
362
364
|
}
|
|
363
365
|
}
|
|
364
366
|
|
|
365
|
-
/* Dark Mode Support */
|
|
366
|
-
@media (prefers-color-scheme: dark) {
|
|
367
|
-
.tour-popup {
|
|
368
|
-
background: #1f2937;
|
|
369
|
-
box-shadow:
|
|
370
|
-
0 20px 25px -5px rgba(0, 0, 0, 0.3),
|
|
371
|
-
0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
.tour-popup-title {
|
|
375
|
-
color: #f9fafb;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.tour-popup-body {
|
|
379
|
-
color: #d1d5db;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
.tour-popup-body strong {
|
|
383
|
-
color: #f3f4f6;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.tour-popup-body code {
|
|
387
|
-
background: #374151;
|
|
388
|
-
color: #e5e7eb;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.tour-popup-progress {
|
|
392
|
-
border-top-color: #374151;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.tour-progress-bar {
|
|
396
|
-
background: #374151;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
.tour-progress-text {
|
|
400
|
-
color: #9ca3af;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
.tour-btn-prev {
|
|
404
|
-
background: #374151;
|
|
405
|
-
color: #e5e7eb;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
.tour-btn-prev:hover {
|
|
409
|
-
background: #4b5563;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
.tour-btn-skip {
|
|
413
|
-
color: #9ca3af;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
.tour-btn-skip:hover {
|
|
417
|
-
color: #d1d5db;
|
|
418
|
-
background: #374151;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
367
|
/* Print Styles - Hide tour elements when printing */
|
|
423
368
|
@media print {
|
|
424
369
|
.tour-overlay,
|
|
@@ -434,13 +379,13 @@
|
|
|
434
379
|
}
|
|
435
380
|
|
|
436
381
|
.tour-btn:focus-visible {
|
|
437
|
-
box-shadow:
|
|
382
|
+
box-shadow: var(--onboarding-focus-ring);
|
|
438
383
|
}
|
|
439
384
|
|
|
440
385
|
/* Utility classes for custom tour content */
|
|
441
386
|
.tour-highlight {
|
|
442
|
-
background:
|
|
443
|
-
color:
|
|
387
|
+
background: color-mix(in srgb, var(--onboarding-primary-fill) 18%, transparent);
|
|
388
|
+
color: var(--onboarding-text);
|
|
444
389
|
padding: 0.125rem 0.375rem;
|
|
445
390
|
border-radius: 0.25rem;
|
|
446
391
|
font-weight: 500;
|
|
@@ -6,15 +6,27 @@
|
|
|
6
6
|
data-milestone-dashboard-milestones-path-value="<%= milestones_path %>">
|
|
7
7
|
<div class="milestone-header">
|
|
8
8
|
<h1 class="milestone-title">🏆 Your Achievements</h1>
|
|
9
|
-
<div class="milestone-
|
|
10
|
-
<div class="
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
9
|
+
<div class="milestone-header-actions">
|
|
10
|
+
<div class="milestone-stats">
|
|
11
|
+
<div class="total-points">
|
|
12
|
+
<span class="points-value" data-milestone-dashboard-target="pointsValue"><%= @total_points %></span>
|
|
13
|
+
<span class="points-label">Total Points</span>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="achievement-count">
|
|
16
|
+
<span class="count-value" data-milestone-dashboard-target="countValue"><%= @achieved_milestones.length %></span>
|
|
17
|
+
<span class="count-label">Milestones</span>
|
|
18
|
+
</div>
|
|
17
19
|
</div>
|
|
20
|
+
|
|
21
|
+
<%# Leaves the engine for the host app's root. Guarded because a host app
|
|
22
|
+
is not required to define a root route, and a missing one here would
|
|
23
|
+
break the whole page - same reasoning as the admin layout's Home link. %>
|
|
24
|
+
<% if main_app.respond_to?(:root_path) %>
|
|
25
|
+
<%= link_to main_app.root_path, class: "milestone-home-link" do %>
|
|
26
|
+
<span class="milestone-home-icon" aria-hidden="true">🏠</span>
|
|
27
|
+
Home
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
18
30
|
</div>
|
|
19
31
|
</div>
|
|
20
32
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_onboarding
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Lewis
|
|
@@ -282,7 +282,7 @@ licenses:
|
|
|
282
282
|
metadata:
|
|
283
283
|
allowed_push_host: https://rubygems.org
|
|
284
284
|
homepage_uri: https://github.com/bunnahabhain/rails_onboarding
|
|
285
|
-
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.
|
|
285
|
+
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.8
|
|
286
286
|
changelog_uri: https://github.com/bunnahabhain/rails_onboarding/blob/master/docs/CHANGELOG.md
|
|
287
287
|
rdoc_options: []
|
|
288
288
|
require_paths:
|
|
@@ -298,7 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
298
298
|
- !ruby/object:Gem::Version
|
|
299
299
|
version: '0'
|
|
300
300
|
requirements: []
|
|
301
|
-
rubygems_version: 4.0.
|
|
301
|
+
rubygems_version: 4.0.19
|
|
302
302
|
specification_version: 4
|
|
303
303
|
summary: Flexible onboarding engine for Rails applications
|
|
304
304
|
test_files: []
|