44
55import { updateProjectVisibility } from "./modules/utils.js" ;
66
7+ const html = document . documentElement ;
8+ const themeToggle = document . getElementById ( 'themeToggle' ) ;
9+ const themeColorMeta = document . querySelector ( 'meta[name="theme-color"]' ) ;
10+
711function prefersReducedMotion ( ) {
812 return window . matchMedia ( "(prefers-reduced-motion: reduce)" ) . matches ;
913}
@@ -34,6 +38,41 @@ function syncThemeColor(theme) {
3438 meta . setAttribute ( "content" , theme === "light" ? "#f4f6f9" : "#0c0f1a" ) ;
3539}
3640
41+ function updateThemeToggleAria ( isLightTheme ) {
42+ if ( ! themeToggle ) return ;
43+ themeToggle . setAttribute (
44+ 'aria-label' ,
45+ isLightTheme ? 'Switch to dark mode' : 'Switch to light mode'
46+ ) ;
47+ }
48+
49+ if ( themeToggle ) {
50+ themeToggle . addEventListener ( 'click' , ( ) => {
51+ const currentTheme = html . getAttribute ( 'data-theme' ) ;
52+ const newTheme = currentTheme === 'light' ? 'dark' : 'light' ;
53+
54+ html . setAttribute ( 'data-theme' , newTheme ) ;
55+ localStorage . setItem ( 'theme' , newTheme ) ;
56+ syncThemeColor ( newTheme ) ;
57+
58+ themeToggle . innerHTML =
59+ newTheme === 'light'
60+ ? '<i class="fas fa-sun" aria-hidden="true"></i>'
61+ : '<i class="fas fa-moon" aria-hidden="true"></i>' ;
62+ updateThemeToggleAria ( newTheme === 'light' ) ;
63+ } ) ;
64+ }
65+
66+ const savedTheme = localStorage . getItem ( 'theme' ) || 'dark' ;
67+ html . setAttribute ( 'data-theme' , savedTheme ) ;
68+ syncThemeColor ( savedTheme ) ;
69+ if ( themeToggle ) {
70+ themeToggle . innerHTML =
71+ savedTheme === 'light'
72+ ? '<i class="fas fa-sun" aria-hidden="true"></i>'
73+ : '<i class="fas fa-moon" aria-hidden="true"></i>' ;
74+ updateThemeToggleAria ( savedTheme === 'light' ) ;
75+ }
3776function escapeHtml ( str ) {
3877 var d = document . createElement ( "div" ) ;
3978 d . textContent = str ;
@@ -79,6 +118,10 @@ function showInfoModal(title, steps) {
79118 listEl . appendChild ( li ) ;
80119 } ) ;
81120
121+ const toggleBackToTopButton = ( ) => {
122+ if ( ! backToTopButton ) return ;
123+ backToTopButton . classList . toggle ( 'visible' , window . scrollY > 300 ) ;
124+ } ;
82125 overlay . classList . add ( "active" ) ;
83126
84127 function closeModal ( ) {
@@ -88,6 +131,12 @@ function showInfoModal(title, steps) {
88131 overlay . removeEventListener ( "click" , overlayClick ) ;
89132 }
90133
134+ if ( backToTopButton ) {
135+ backToTopButton . addEventListener ( 'click' , ( ) => {
136+ const prefersReducedMotion = window . matchMedia ( '(prefers-reduced-motion: reduce)' ) . matches ;
137+ window . scrollTo ( { top : 0 , behavior : prefersReducedMotion ? 'auto' : 'smooth' } ) ;
138+ } ) ;
139+ }
91140 function overlayClick ( e ) {
92141 if ( e . target === overlay ) closeModal ( ) ;
93142 }
@@ -836,6 +885,30 @@ document.addEventListener("DOMContentLoaded", function () {
836885 if ( noResultsMessage ) noResultsMessage . style . display = "block" ;
837886 return ;
838887 }
888+
889+ resultsList . innerHTML = '' ;
890+ matches . slice ( 0 , 8 ) . forEach ( ( project , index ) => {
891+ const item = document . createElement ( 'button' ) ;
892+ item . type = 'button' ;
893+ item . className = 'dropdown-item' + ( index === selectedSuggestionIndex ? ' selected' : '' ) ;
894+ item . setAttribute ( 'aria-label' , `Select ${ project . title } ` ) ;
895+ item . innerHTML = `
896+ <div class="dropdown-item-icon">
897+ ${ project . card . querySelector ( '.card-icon' ) . textContent }
898+ </div>
899+ <div class="dropdown-item-text">${ highlightMatch ( project . title , query ) } </div>
900+ <span class="dropdown-item-tag">${ project . category } </span>
901+ ` ;
902+ item . addEventListener ( 'click' , ( ) => selectSuggestion ( project . title ) ) ;
903+ item . addEventListener ( 'keydown' , ( e ) => {
904+ if ( e . key === 'Enter' || e . key === ' ' ) {
905+ e . preventDefault ( ) ;
906+ selectSuggestion ( project . title ) ;
907+ }
908+ } ) ;
909+ item . addEventListener ( 'mouseenter' , ( ) => {
910+ selectedSuggestionIndex = index ;
911+ updateSuggestionHighlight ( ) ;
839912
840913 if ( noResultsMessage ) noResultsMessage . style . display = "none" ;
841914
@@ -1192,6 +1265,41 @@ document.addEventListener("DOMContentLoaded", function () {
11921265 removeTrap ( ) ;
11931266 removeTrap = null ;
11941267 }
1268+
1269+ recentSearchesList . innerHTML = '' ;
1270+ recentSearches . slice ( 0 , 5 ) . forEach ( ( search ) => {
1271+ const item = document . createElement ( 'div' ) ;
1272+ item . className = 'dropdown-recent-item' ;
1273+ item . innerHTML = `
1274+ <button type="button" class="dropdown-recent-text" aria-label="Search ${ search } ">
1275+ <i class="fas fa-history" style="opacity: 0.5; font-size: 0.9rem;"></i>
1276+ <span style="flex: 1; color: var(--text-secondary);">${ search } </span>
1277+ </button>
1278+ <button type="button" class="dropdown-recent-remove" aria-label="Remove search">
1279+ <i class="fas fa-x"></i>
1280+ </button>
1281+ ` ;
1282+
1283+ const textButton = item . querySelector ( '.dropdown-recent-text' ) ;
1284+ const removeBtn = item . querySelector ( '.dropdown-recent-remove' ) ;
1285+
1286+ if ( textButton ) {
1287+ textButton . addEventListener ( 'click' , ( ) => {
1288+ searchInput . value = search ;
1289+ currentSearchQuery = search ;
1290+ performSearch ( ) ;
1291+ closeDropdown ( ) ;
1292+ } ) ;
1293+ }
1294+
1295+ if ( removeBtn ) {
1296+ removeBtn . addEventListener ( 'click' , ( e ) => {
1297+ e . stopPropagation ( ) ;
1298+ recentSearches = recentSearches . filter ( s => s !== search ) ;
1299+ localStorage . setItem ( 'recentSearches' , JSON . stringify ( recentSearches ) ) ;
1300+ renderRecentSearches ( ) ;
1301+ } ) ;
1302+ }
11951303
11961304 // Clear content
11971305 if ( modalBody ) {
@@ -1582,6 +1690,38 @@ document.addEventListener("DOMContentLoaded", function () {
15821690 }
15831691 }
15841692
1693+ // Open Project Modal
1694+ projectCards . forEach ( card => {
1695+ card . tabIndex = 0 ;
1696+ card . setAttribute ( 'role' , 'button' ) ;
1697+ card . setAttribute ( 'aria-label' , `Open ${ card . querySelector ( 'h3' ) ?. textContent || 'project' } ` ) ;
1698+
1699+ const playButton = card . querySelector ( '.btn-play' ) ;
1700+
1701+ if ( playButton ) {
1702+ playButton . addEventListener ( 'click' , ( e ) => {
1703+ e . stopPropagation ( ) ;
1704+ const projectName = card . getAttribute ( 'data-project' ) ;
1705+ openProject ( projectName ) ;
1706+ } ) ;
1707+ }
1708+
1709+ card . addEventListener ( 'click' , ( ) => {
1710+ const projectName = card . getAttribute ( 'data-project' ) ;
1711+ openProject ( projectName ) ;
1712+ } ) ;
1713+
1714+ card . addEventListener ( 'keydown' , ( e ) => {
1715+ if ( e . key === 'Enter' || e . key === ' ' ) {
1716+ e . preventDefault ( ) ;
1717+ const projectName = card . getAttribute ( 'data-project' ) ;
1718+ openProject ( projectName ) ;
1719+ }
1720+ } ) ;
1721+ } ) ;
1722+ /* ── Activate item based on viewport center crossing timeline dots ── */
1723+ var activeIdx = - 1 ;
1724+ var dots = document . querySelectorAll ( ".timeline-dot" ) ;
15851725 /* ── Timeline Fill Progress ───────────────────────────── */
15861726 function updateTimelineFill ( ) {
15871727 if ( ! timelineSection ) return ;
0 commit comments