Skip to content

Commit 3d6b0c5

Browse files
Add celebratory completion page with confetti 🎉
- Add completion step after Part 6 with confetti animation - Include 'More Confetti!' button for endless celebration - Add achievement badge and congratulations message - Include social sharing links (Twitter, GitHub, Blazor workshop) - Multi-language support for completion page - Auto-fire confetti burst on page load
1 parent 24929a6 commit 3d6b0c5

File tree

1 file changed

+238
-10
lines changed

1 file changed

+238
-10
lines changed

docs/step.html

Lines changed: 238 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
1313
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/csharp.min.js"></script>
1414
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/xml.min.js"></script>
15+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
1516
<style>
1617
:root {
1718
--bg-dark: #0a0a0f;
@@ -410,6 +411,114 @@
410411
padding: 2rem 1rem;
411412
}
412413
}
414+
415+
/* Completion page styles */
416+
.completion-page {
417+
text-align: center;
418+
padding: 2rem;
419+
}
420+
421+
.completion-page h1 {
422+
font-size: 3rem;
423+
margin-bottom: 1rem;
424+
background: linear-gradient(135deg, var(--maui-purple), var(--neon-blue), #ff6b6b, #feca57);
425+
-webkit-background-clip: text;
426+
-webkit-text-fill-color: transparent;
427+
background-clip: text;
428+
animation: rainbow 3s ease infinite;
429+
background-size: 300% 300%;
430+
}
431+
432+
@keyframes rainbow {
433+
0% { background-position: 0% 50%; }
434+
50% { background-position: 100% 50%; }
435+
100% { background-position: 0% 50%; }
436+
}
437+
438+
.completion-emoji {
439+
font-size: 6rem;
440+
margin: 2rem 0;
441+
animation: bounce 1s ease infinite;
442+
}
443+
444+
@keyframes bounce {
445+
0%, 100% { transform: translateY(0); }
446+
50% { transform: translateY(-20px); }
447+
}
448+
449+
.completion-message {
450+
font-size: 1.25rem;
451+
color: var(--text-secondary);
452+
max-width: 600px;
453+
margin: 0 auto 2rem;
454+
}
455+
456+
.confetti-btn {
457+
display: inline-flex;
458+
align-items: center;
459+
gap: 0.75rem;
460+
padding: 1rem 2.5rem;
461+
font-size: 1.25rem;
462+
background: linear-gradient(135deg, #ff6b6b, #feca57, var(--neon-purple), var(--neon-blue));
463+
background-size: 300% 300%;
464+
animation: rainbow 3s ease infinite;
465+
border: none;
466+
border-radius: 50px;
467+
color: white;
468+
font-weight: 700;
469+
cursor: pointer;
470+
transition: transform 0.2s ease, box-shadow 0.2s ease;
471+
font-family: inherit;
472+
margin: 1rem;
473+
}
474+
475+
.confetti-btn:hover {
476+
transform: scale(1.05);
477+
box-shadow: 0 10px 40px rgba(155,77,255,0.4);
478+
}
479+
480+
.confetti-btn:active {
481+
transform: scale(0.95);
482+
}
483+
484+
.completion-links {
485+
margin-top: 3rem;
486+
display: flex;
487+
flex-wrap: wrap;
488+
justify-content: center;
489+
gap: 1rem;
490+
}
491+
492+
.completion-link {
493+
display: inline-flex;
494+
align-items: center;
495+
gap: 0.5rem;
496+
padding: 0.75rem 1.5rem;
497+
background: var(--bg-card);
498+
border: 1px solid var(--border-color);
499+
border-radius: 8px;
500+
color: var(--text-primary);
501+
text-decoration: none;
502+
transition: all 0.3s ease;
503+
}
504+
505+
.completion-link:hover {
506+
border-color: var(--neon-purple);
507+
background: rgba(81,43,212,0.1);
508+
transform: translateY(-2px);
509+
}
510+
511+
.achievement-badge {
512+
display: inline-block;
513+
padding: 0.5rem 1.5rem;
514+
background: linear-gradient(135deg, #ffd700, #ffaa00);
515+
border-radius: 50px;
516+
color: #1a1a2e;
517+
font-weight: 700;
518+
font-size: 0.9rem;
519+
margin-bottom: 2rem;
520+
box-shadow: 0 4px 15px rgba(255,215,0,0.3);
521+
}
413522
</style>
414523
</head>
415524
<body>
@@ -478,7 +587,8 @@ <h1 class="step-title" id="step-title">Loading...</h1>
478587
{ id: 'part3', title: 'Navigation', badge: 'Navigation', folder: 'Part 3 - Navigation' },
479588
{ id: 'part4', title: 'Platform Features', badge: 'Platform', folder: 'Part 4 - Platform Features' },
480589
{ id: 'part5', title: 'CollectionView', badge: 'CollectionView', folder: 'Part 5 - CollectionView' },
481-
{ id: 'part6', title: 'App Themes', badge: 'Theming', folder: 'Part 6 - AppThemes' }
590+
{ id: 'part6', title: 'App Themes', badge: 'Theming', folder: 'Part 6 - AppThemes' },
591+
{ id: 'complete', title: 'Workshop Complete!', badge: '🎉', folder: null }
482592
];
483593

484594
// Translations for step titles
@@ -491,10 +601,19 @@ <h1 class="step-title" id="step-title">Loading...</h1>
491601
'part4': 'Platform Features',
492602
'part5': 'CollectionView & Beyond',
493603
'part6': 'App Themes',
604+
'complete': '🎉 Workshop Complete!',
494605
'prev': '← Previous',
495606
'next': 'Next →',
496607
'back-home': '← Back to Home',
497-
'complete': '🎉 Workshop Complete!'
608+
'finish': 'Finish! 🎉',
609+
'congrats-title': 'Congratulations!',
610+
'congrats-message': 'You did it! You\'ve successfully completed the .NET MAUI Workshop and built your first cross-platform mobile application. You\'ve learned data binding, MVVM, navigation, platform features, and theming!',
611+
'achievement': '🏆 .NET MAUI Developer Achievement Unlocked!',
612+
'more-confetti': '🎊 More Confetti!',
613+
'share-twitter': 'Share on Twitter',
614+
'blazor-workshop': 'Try Blazor Hybrid Workshop',
615+
'github-repo': 'Star on GitHub',
616+
'maui-docs': '.NET MAUI Docs'
498617
},
499618
'zh-cn': {
500619
'part0': '概述和环境准备',
@@ -504,10 +623,19 @@ <h1 class="step-title" id="step-title">Loading...</h1>
504623
'part4': '平台特性',
505624
'part5': 'CollectionView 进阶',
506625
'part6': '应用主题',
626+
'complete': '🎉 实验完成!',
507627
'prev': '← 上一步',
508628
'next': '下一步 →',
509629
'back-home': '← 返回首页',
510-
'complete': '🎉 实验完成!'
630+
'finish': '完成!🎉',
631+
'congrats-title': '恭喜!',
632+
'congrats-message': '你做到了!你已经成功完成了 .NET MAUI 动手实验,并构建了你的第一个跨平台移动应用程序。你已经学会了数据绑定、MVVM、导航、平台特性和主题设置!',
633+
'achievement': '🏆 .NET MAUI 开发者成就已解锁!',
634+
'more-confetti': '🎊 更多彩带!',
635+
'share-twitter': '分享到 Twitter',
636+
'blazor-workshop': '尝试 Blazor Hybrid 实验',
637+
'github-repo': '在 GitHub 上点赞',
638+
'maui-docs': '.NET MAUI 文档'
511639
},
512640
'zh-tw': {
513641
'part0': '概述和環境準備',
@@ -517,10 +645,19 @@ <h1 class="step-title" id="step-title">Loading...</h1>
517645
'part4': '平台特性',
518646
'part5': 'CollectionView 進階',
519647
'part6': '應用主題',
648+
'complete': '🎉 實驗完成!',
520649
'prev': '← 上一步',
521650
'next': '下一步 →',
522651
'back-home': '← 返回首頁',
523-
'complete': '🎉 實驗完成!'
652+
'finish': '完成!🎉',
653+
'congrats-title': '恭喜!',
654+
'congrats-message': '你做到了!你已經成功完成了 .NET MAUI 動手實驗,並建構了你的第一個跨平台行動應用程式。你已經學會了資料繫結、MVVM、導航、平台特性和主題設定!',
655+
'achievement': '🏆 .NET MAUI 開發者成就已解鎖!',
656+
'more-confetti': '🎊 更多彩帶!',
657+
'share-twitter': '分享到 Twitter',
658+
'blazor-workshop': '嘗試 Blazor Hybrid 實驗',
659+
'github-repo': '在 GitHub 上點讚',
660+
'maui-docs': '.NET MAUI 文件'
524661
}
525662
};
526663

@@ -549,13 +686,16 @@ <h1 class="step-title" id="step-title">Loading...</h1>
549686
const nav = document.getElementById('sidebar-nav');
550687
const trans = stepTranslations[lang];
551688

552-
nav.innerHTML = steps.map((step, index) => `
689+
nav.innerHTML = steps.map((step, index) => {
690+
const isComplete = step.id === 'complete';
691+
const displayNum = isComplete ? '🎉' : index;
692+
return `
553693
<a href="step.html?step=${step.id}&lang=${lang}"
554694
class="nav-item ${step.id === currentStep ? 'active' : ''}">
555-
<span class="nav-number">${index}</span>
695+
<span class="nav-number">${displayNum}</span>
556696
<span class="nav-title">${trans[step.id] || step.title}</span>
557697
</a>
558-
`).join('');
698+
`}).join('');
559699
}
560700

561701
// Build navigation buttons
@@ -573,11 +713,16 @@ <h1 class="step-title" id="step-title">Loading...</h1>
573713
html += `<a href="index.html?lang=${lang}" class="nav-btn">${trans['back-home']}</a>`;
574714
}
575715

576-
if (currentIndex < steps.length - 1) {
716+
if (currentStep === 'complete') {
717+
// On completion page, no next button needed
718+
html += `<a href="index.html?lang=${lang}" class="nav-btn primary">${trans['back-home']}</a>`;
719+
} else if (currentIndex < steps.length - 2) {
720+
// Not on the last content step
577721
const nextStep = steps[currentIndex + 1];
578722
html += `<a href="step.html?step=${nextStep.id}&lang=${lang}" class="nav-btn primary">${trans['next']}</a>`;
579-
} else {
580-
html += `<span class="nav-btn">${trans['complete']}</span>`;
723+
} else if (currentIndex === steps.length - 2) {
724+
// On part6, link to completion
725+
html += `<a href="step.html?step=complete&lang=${lang}" class="nav-btn primary">${trans['finish']}</a>`;
581726
}
582727

583728
navButtons.innerHTML = html;
@@ -591,6 +736,12 @@ <h1 class="step-title" id="step-title">Loading...</h1>
591736
return;
592737
}
593738

739+
// Handle completion page
740+
if (step === 'complete') {
741+
loadCompletionPage(lang);
742+
return;
743+
}
744+
594745
// Update header
595746
const trans = stepTranslations[lang];
596747
document.getElementById('step-title').textContent = trans[step] || stepConfig.title;
@@ -649,6 +800,83 @@ <h1>Content Loading Error</h1>
649800
}
650801
}
651802

803+
// Load completion page with confetti
804+
function loadCompletionPage(lang) {
805+
const trans = stepTranslations[lang];
806+
807+
// Update header
808+
document.getElementById('step-title').textContent = trans['complete'] || 'Workshop Complete!';
809+
document.getElementById('step-badge').textContent = '🎉';
810+
document.title = `${trans['complete'] || 'Workshop Complete!'} | .NET MAUI Workshop`;
811+
812+
const twitterText = encodeURIComponent('I just completed the .NET MAUI Workshop and built my first cross-platform mobile app! 🐒📱 #dotnetmaui #dotnet @jabortwitz');
813+
const twitterUrl = `https://twitter.com/intent/tweet?text=${twitterText}&url=https://dotnet-presentations.github.io/dotnet-maui-workshop/`;
814+
815+
document.getElementById('content').innerHTML = `
816+
<div class="completion-page">
817+
<div class="achievement-badge">${trans['achievement']}</div>
818+
<div class="completion-emoji">🐒</div>
819+
<h1>${trans['congrats-title']}</h1>
820+
<p class="completion-message">${trans['congrats-message']}</p>
821+
822+
<button class="confetti-btn" onclick="fireConfetti()">
823+
${trans['more-confetti']}
824+
</button>
825+
826+
<div class="completion-links">
827+
<a href="${twitterUrl}" target="_blank" class="completion-link">
828+
🐦 ${trans['share-twitter']}
829+
</a>
830+
<a href="https://aka.ms/blazor-hybrid-workshop" target="_blank" class="completion-link">
831+
🔥 ${trans['blazor-workshop']}
832+
</a>
833+
<a href="https://github.com/dotnet-presentations/dotnet-maui-workshop" target="_blank" class="completion-link">
834+
${trans['github-repo']}
835+
</a>
836+
<a href="https://docs.microsoft.com/dotnet/maui" target="_blank" class="completion-link">
837+
📚 ${trans['maui-docs']}
838+
</a>
839+
</div>
840+
</div>
841+
`;
842+
843+
// Fire confetti on page load!
844+
setTimeout(() => {
845+
fireConfetti();
846+
setTimeout(fireConfetti, 500);
847+
setTimeout(fireConfetti, 1000);
848+
}, 300);
849+
}
850+
851+
// Confetti function
852+
function fireConfetti() {
853+
// Fire from left
854+
confetti({
855+
particleCount: 100,
856+
spread: 70,
857+
origin: { x: 0.1, y: 0.6 },
858+
colors: ['#512BD4', '#0078d4', '#9B4DFF', '#00d4ff', '#ff6b6b', '#feca57']
859+
});
860+
861+
// Fire from right
862+
confetti({
863+
particleCount: 100,
864+
spread: 70,
865+
origin: { x: 0.9, y: 0.6 },
866+
colors: ['#512BD4', '#0078d4', '#9B4DFF', '#00d4ff', '#ff6b6b', '#feca57']
867+
});
868+
869+
// Fire from center top
870+
setTimeout(() => {
871+
confetti({
872+
particleCount: 50,
873+
spread: 100,
874+
origin: { x: 0.5, y: 0.3 },
875+
colors: ['#512BD4', '#0078d4', '#9B4DFF', '#00d4ff', '#ff6b6b', '#feca57']
876+
});
877+
}, 200);
878+
}
879+
652880
// Toggle sidebar on mobile
653881
function toggleSidebar() {
654882
document.querySelector('.sidebar').classList.toggle('open');

0 commit comments

Comments
 (0)