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 ;
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