1(function () { 2 if (window.DashboardLite) return; 3 4 var DashboardLite = {}; 5 6 // ââ Config ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 7 DashboardLite._apiBase = 'https://api.kai.w3sapi.com/pathfinder'; 8 DashboardLite._profilePicBase = 'https://www.w3profile.com'; 9 DashboardLite._leagueImgBase = 'https://pathfinder.w3schools.com/images/leagues'; 10 DashboardLite._profileBase = 'https://profile.w3schools.com'; 11 // Logged-in league links go to the pathfinder leaderboard; anon/frozen go to 12 // the marketing page (_leagueInfoUrl) since the live board needs an account. 13 DashboardLite._leaderboardUrl = 'https://pathfinder.w3schools.com/leaderboard'; 14 DashboardLite._leagueInfoUrl = '/league/'; 15 DashboardLite._styleId = 'dashboard-lite-style'; 16 DashboardLite._fallbackAvatar = '/images/lynx.svg'; 17 18 // Named formats (ad-unit style). Each maps to a fixed box + a layout mode. 19 // No format -> 'stack' (the original 300px-wide auto-height card on default.asp). 20 DashboardLite.FORMATS = { 21 '960x300': { w: 960, h: 300, mode: 'row' }, 22 '720x250': { w: 720, h: 250, mode: 'row' }, 23 '300x300': { w: 300, h: 300, mode: 'grid' }, 24 '160x300': { w: 160, h: 300, mode: 'col' }, 25 '140x300': { w: 140, h: 300, mode: 'col' } 26 }; 27 28 // Official league tier lookup (code -> display name + badge filename). 29 DashboardLite._tierMap = { 30 wd: { name: 'Wood', icon: 'wood.svg' }, 31 br: { name: 'Bronze', icon: 'bronze.svg' }, 32 si: { name: 'Silver', icon: 'silver.svg' }, 33 go: { name: 'Gold', icon: 'gold.svg' }, 34 pl: { name: 'Platinum', icon: 'platinum.svg' }, 35 di: { name: 'Diamond', icon: 'diamond.svg' }, 36 ll: { name: 'Logic Legend', icon: 'logic_legend.svg' }, 37 ss: { name: 'Script Samurai', icon: 'script_samurai.svg' }, 38 bs: { name: 'Bug Slayer', icon: 'bug_slayer.svg' }, 39 sc: { name: 'Supreme Coder', icon: 'supreme_coder.svg' }, 40 mc: { name: 'Master Coder', icon: 'master_coder.svg' } 41 }; 42 DashboardLite._tier = function (code) { 43 return DashboardLite._tierMap[String(code || '').toLowerCase()] || DashboardLite._tierMap.wd; 44 }; 45 DashboardLite._tierName = function (code) { return DashboardLite._tier(code).name; }; 46 DashboardLite._tierIcon = function (code) { 47 return DashboardLite._leagueImgBase + '/' + DashboardLite._tier(code).icon; 48 }; 49 50 // Mood overlay badge (avatar.layers.mood -> companion svg). 51 DashboardLite._moodBase = 'https://pathfinder.w3schools.com/images/companion/mood'; 52 DashboardLite._moodMap = { 53 'mood-focused': 'direct_hit.svg', 'mood-zen': 'artist_palette.svg', 54 'mood-cool': 'smiling_face_with_sunglasses.svg', 'mood-silly': 'pile_of_poo.svg', 55 'mood-strong': 'flexed_biceps.svg', 'mood-horns': 'smiling_face_with_horns.svg', 56 'mood-unicorn': 'unicorn.svg', 'mood-zany': 'zany_face.svg' 57 }; 58 DashboardLite._moodIcon = function (mood) { 59 var f = DashboardLite._moodMap[mood]; 60 return f ? DashboardLite._moodBase + '/' + f : null; 61 }; 62 63 // ââ Social sign-in (Cognito OAuth - mirrors lib/account/auth-bar.js) âââââââ 64 DashboardLite._authUrl = 'https://auth.w3spaces.com'; 65 DashboardLite._cognitoClientId = 'd1grqml0emh7votkb0gtrrn0'; 66 DashboardLite._socialIcons = { 67 google: '<svg width="20" height="20" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg>', 68 facebook: '<svg width="20" height="20" viewBox="0 0 24 24" fill="#0080ff"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>', 69 github: '<svg width="20" height="20" viewBox="0 0 18 18"><path fill-rule="evenodd" clip-rule="evenodd" d="M9.00194 0C13.9726 0 18.0019 4.13093 18.0019 9.22763C18.0019 13.3037 15.4261 16.7615 11.8522 17.9828C11.3959 18.0737 11.2339 17.7856 11.2339 17.5399C11.2339 17.2357 11.2447 16.2421 11.2447 15.0073C11.2447 14.1469 10.9567 13.5854 10.6336 13.2992C12.6379 13.0706 14.7439 12.2902 14.7439 8.74599C14.7439 7.73799 14.3947 6.91543 13.8169 6.26923C13.9105 6.03613 14.2192 5.09755 13.7287 3.82675C13.7287 3.82675 12.9745 3.5795 11.2564 4.77289C10.5373 4.56859 9.76694 4.4658 9.00194 4.4622C8.23694 4.4658 7.46745 4.56859 6.74925 4.77289C5.02935 3.5795 4.27335 3.82675 4.27335 3.82675C3.78465 5.09755 4.09335 6.03613 4.18605 6.26923C3.61095 6.91543 3.25905 7.73799 3.25905 8.74599C3.25905 12.2812 5.36055 13.0735 7.35945 13.3066C7.10205 13.537 6.86895 13.9435 6.78795 14.5402C6.27495 14.776 4.97175 15.1841 4.16895 13.7738C4.16895 13.7738 3.69285 12.8871 2.78925 12.8223C2.78925 12.8223 1.91175 12.8107 2.72805 13.3831C2.72805 13.3831 3.31755 13.6666 3.72705 14.7331C3.72705 14.7331 4.25535 16.3801 6.75915 15.8221C6.76365 16.5934 6.77175 17.3203 6.77175 17.5399C6.77175 17.7838 6.60614 18.0692 6.15704 17.9837C2.58045 16.7642 0.00195312 13.3046 0.00195312 9.22763C0.00195312 4.13093 4.03215 0 9.00194 0Z" fill="#282A35"/></svg>' 70 }; 71 DashboardLite._genState = function () { 72 var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', r = ''; 73 for (var i = 0; i < 32; i++) r += chars.charAt(Math.floor(Math.random() * chars.length)); 74 return r; 75 }; 76 DashboardLite._socialUrl = function (provider, returnUrl) { 77 var idp = { google: 'Google', facebook: 'Facebook', github: 'Github' }[provider] || provider; 78 return DashboardLite._authUrl + '/oauth2/authorize' +
79 '?redirect_uri=' + encodeURIComponent(DashboardLite._profileBase + '/social-login') + 80 '&response_type=code&client_id=' + DashboardLite._cognitoClientId + 81 '&identity_provider=' + idp + 82 '&scope=openid%20aws.cognito.signin.user.admin' + 83 '&state=' + DashboardLite._genState() + 84 '&final_return=' + encodeURIComponent(returnUrl); 85 }; 86 DashboardLite._openOAuthPopup = function (url, returnUrl) { 87 try { 88 document.cookie = 'socialLoginRedirectUrl=' + encodeURIComponent(returnUrl) + 89 '; path=/; domain=.w3schools.com; secure; samesite=strict'; 90 } catch (e) {} 91 var w = 450, h = 450, l = (window.innerWidth - w) / 2, t = (window.innerHeight - h) / 2; 92 var popup = window.open(url, '_blank', 'width=' + w + ',height=' + h + ',left=' + l + ',top=' + t + ',popup=1'); 93 if (!popup || popup.closed || typeof popup.closed === 'undefined') window.location.href = url; 94 }; 95 DashboardLite._socialBtn = function (provider, returnUrl) { 96 var url = DashboardLite._socialUrl(provider, returnUrl); 97 var safeRet = String(returnUrl).replace(/'/g, "\\'"); 98 var onclick = "event.preventDefault();event.stopPropagation();DashboardLite._openOAuthPopup('" + 99 url.replace(/'/g, "\\'") + "','" + safeRet + "');"; 100 return '<a href="' + DashboardLite._esc(url) + '" onclick="' + onclick + '" ' + 101 'class="dlw-social ga-fp ga-dlw-' + provider + '" ' + 102 'title="Continue with ' + provider.charAt(0).toUpperCase() + provider.slice(1) + '">' + 103 DashboardLite._socialIcons[provider] + '</a>'; 104 }; 105 106 // Brand logos for the continue-card topic icon (product marks, not design tokens). 107 DashboardLite._langIcons = { 108 python: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 32 32"><defs><linearGradient id="dlw-py-b" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#387EB8"/><stop offset="100%" stop-color="#366994"/></linearGradient><linearGradient id="dlw-py-y" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#FFE052"/><stop offset="100%" stop-color="#FFC331"/></linearGradient></defs><path d="M15.885 2.1c-7.1 0-6.651 3.07-6.651 3.07v3.19h6.752v1H6.304s-4.604.208-4.604 6.645c0 6.442 4.013 6.912 4.013 6.912H8.33v-3.361s-.13-4.013 3.9-4.013h6.762s3.772.06 3.772-3.652V5.8s.572-3.712-6.842-3.712zm-3.732 2.137a1.214 1.214 0 1 1-1.183 1.244v-.02a1.214 1.214 0 0 1 1.214-1.214z" fill="url(#dlw-py-b)"/><path d="M16.085 29.91c7.1 0 6.651-3.08 6.651-3.08v-3.18h-6.751v-1h9.47S30 23.158 30 15.995s-4.013-6.912-4.013-6.912H23.64v3.361s.13 4.013-3.9 4.013h-6.765S9.2 16.4 9.2 20.112v6.091s-.572 3.712 6.842 3.712zm3.732-2.147a1.214 1.214 0 1 1 1.214-1.214 1.214 1.214 0 0 1-1.214 1.214z" fill="url(#dlw-py-y)"/></svg>', 109 html: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#E34F26" viewBox="0 0 24 24"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', 110 css: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#1572B6" viewBox="0 0 24 24"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.565-2.438L1.5 0zm17.09 4.413L5.41 4.41l.213 2.622 10.125.002-.255 2.716h-6.64l.24 2.573h6.182l-.366 3.523-2.91.804-2.956-.81-.188-2.11h-2.61l.29 3.855L12 19.288l5.373-1.53L18.59 4.414z"/></svg>', 111 // J first, then S - the two glyphs used to be transposed and read "SJ". 112 javascript: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24"><rect width="24" height="24" fill="#F7DF1E"/><path fill="#000" d="M6.3 18.4c.3.6.7 1 1.4 1 .6 0 1-.2 1-1.2v-6.3h2v6.3c0 2-1.2 2.9-2.9 2.9-1.5 0-2.5-.8-3-1.8zM13.7 18.2c.4.7.9 1.2 1.9 1.2.8 0 1.3-.4 1.3-1 0-.7-.5-.9-1.5-1.3l-.5-.2c-1.5-.6-2.4-1.4-2.4-3 0-1.5 1.1-2.6 2.9-2.6 1.3 0 2.2.4 2.8 1.6l-1.5 1c-.3-.6-.7-.8-1.3-.8-.5 0-.9.3-.9.8 0 .6.4.8 1.3 1.2l.5.2c1.7.7 2.6 1.5 2.6 3.1 0 1.8-1.4 2.7-3.3 2.7-1.8 0-3-.9-3.6-2z"/></svg>', 113 sql: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#00758F" viewB
113ox="0 0 24 24"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5c0 1.7-3.6 3-8 3S4 6.7 4 5z" opacity=".7"/><path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6c0 1.7-3.6 3-8 3s-8-1.3-8-3z" opacity=".5"/></svg>', 114 php: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#777BB4" viewBox="0 0 24 24"><path d="M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 0 1-.305.847c-.143.255-.33.49-.561.703zm4.024.715l.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.265a2.782 2.782 0 0 1-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 0 1-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zM17.766 10.207h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z"/></svg>', 115 java: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 32 32"><path fill="#5382A1" d="M11.622 24.74s-1.264.748.9.998c2.626.308 3.968.264 6.854-.308 0 0 .76.484 1.82.9-6.49 2.802-14.674-.16-9.574-1.59zm-.826-3.924s-1.43 1.056.748 1.278c2.782.308 4.97.33 8.76-.44 0 0 .528.54 1.353.836-7.81 2.296-16.5.176-10.86-1.672z"/><path fill="#E76F00" d="M18.996 1s3.014 3.036-2.858 7.7c-4.708 3.748-1.076 5.894 0 8.338-2.744-2.478-4.774-4.664-3.414-6.692 1.996-2.97 7.53-4.422 6.272-9.346zm-1.576 14.026c1.464 1.68-.374 3.19-.374 3.19s3.702-1.92 2-4.312c-1.584-2.222-2.804-3.326 3.79-7.106 0 0-10.374 2.596-5.412 8.228z"/></svg>', 116 react: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#61DAFB" viewBox="0 0 24 24"><circle cx="12" cy="12" r="2"/><g fill="none" stroke="#61DAFB" stroke-width="1"><ellipse cx="12" cy="12" rx="11" ry="4.2"/><ellipse cx="12" cy="12" rx="11" ry="4.2" transform="rotate(60 12 12)"/><ellipse cx="12" cy="12" rx="11" ry="4.2" transform="rotate(120 12 12)"/></g></svg>' 117 }; 118 DashboardLite._topicKey = function (name) { 119 var k = String(name == null ? '' : name).toLowerCase(); 120 if (k.indexOf('javascript') > -1) return 'javascript'; 121 if (k.indexOf('python') > -1) return 'python'; 122 if (k.indexOf('html') > -1) return 'html'; 123 if (k.indexOf('css') > -1) return 'css'; 124 if (k.indexOf('sql') > -1) return 'sql'; 125 if (k.indexOf('php') > -1) return 'php'; 126 if (k.indexOf('react') > -1) return 'react'; 127 if (k.indexOf('java') > -1) return 'java'; 128 return ''; 129 }; 130 DashboardLite._topicIconHtml = function (name) { 131 var key = DashboardLite._topicKey(name); 132 if (key && DashboardLite._langIcons[key]) return DashboardLite._langIcons[key]; 133 var letter = String(name || '?').trim().charAt(0).toUpperCase() || '?'; 134 return '<span style="font-family:\'Source Sans Pro\',sans-serif;font-weight:700;font-size:18px;color:#04AA6D">' + 135 DashboardLite._esc(letter) + '</span>'; 136 }; 137 138 // ââ Auth / fetch ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 139 DashboardLite._getToken = function () { 140 if (typeof SharedAPI !== 'undefined' && typeof SharedAPI.getAccessToken === 'function') { 141 var t = SharedAPI.getAccessToken(); 142 if (t) return t; 143 } 144 try { 145 var cookies = document.cookie.split(';'); 146 for (var i = 0; i < cookies.length; i++) { 147 var c = cookies[i].trim(); 148 if (c.indexOf('accessToken=') === 0) return c.substring('accessToken='.length); 149 } 150 } catch (e) {} 151 return null; 152 }; 153 154 DashboardLite._get = function (path) { 155 var token = DashboardLite._getToken(); 156 if (!token) return Promise.reject(new Error('No access token')); 157 return fetch(DashboardLite._apiBase + path, { 158 method: 'GET', 159 headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } 160 }).then(function (res) { 161 if (!res.ok) throw new Error('API ' + res.status + ' for ' + path); 162 return res.json(); 163 }); 164 }; 165 166 DashboardLite.fetchProfile = function () { 167 return DashboardLite._get('/profile-api/profile/basic-lite?fields=personal').then(function (data) { 168 var p = (data && data.personal) || {}; 169 var avatar = p.profilePicture 170 ? DashboardLite._profilePicBase + '/' + p.profilePicture : DashboardLite._fallbackAvatar; 171 return { 172 firstName: p.firstName || '', lastName: p.lastName || '', 173 fullName: ((p.firstName || '') + ' ' + (p.lastName || '')).trim(), 174 avatar: avatar 175 }; 176 }); 177 }; 178 179 DashboardLite.fetchStreaks = function () { 180 return DashboardLite._get('/competency-api/streaks').then(function (data) { 181 data = data || {}; 182 return { 183 streaks: typeof data.streaks === 'number' ? data.streaks : 0, 184 freezeUsed: data.freezeUsedThisMonth, 185 freezeRemaining: data.freezeRemainingThisMonth 186 }; 187 }); 188 }; 189 190 DashboardLite.fetchLeagueXp = function () { 191 return DashboardLite._get('/league-api/user/xp').then(function (data) { 192 data = data || {}; 193 var league = data.league || {}; 194 return { 195 xp: typeof data.xp === 'number' ? data.xp : 0, 196 weeklyXp: typeof data.weeklyXp === 'number' ? data.weeklyXp : 0, 197 optedIn: !!league.optedIn, tier: league.tier || null, 198 leagueId: league.leagueId || null, cohortId: league.cohortId || null 199 }; 200 }); 201 }; 202 203 // GET /league-api/user/profile -> rendered avatar URL + current mood. 204 DashboardLite.fetchAvatar = function () { 205 return DashboardLite._get('/league-api/user/profile').then(function (data) { 206 data = data || {}; 207 var layers = (data.avatar && data.avatar.layers) || {}; 208 return { 209 avatar: data.profilePictureUrl 210 ? DashboardLite._profilePicBase + '/' + data.profilePictureUrl : null, 211 mood: layers.mood || null 212 }; 213 }); 214 }; 215 216 // GET /league-api/leaderboard/cohort -> standings for the user's own cohort. 217 DashboardLite.fetchCohort = function () { 218 return DashboardLite._get('/league-api/leaderboard/cohort').then(function (data) { 219 data = data || {}; 220 return { 221 users: Array.isArray(data.users) ? data.users : [], 222 userRank: typeof data.userRank === 'number' ? data.userRank : null, 223 totalUsers: typeof data.totalUsers === 'number' ? data.totalUsers : null 224 }; 225 }); 226 }; 227 228 // GET /competency-api/xp -> {"xp": N}. The canonical total XP - the league 229 // endpoint reports xp:0 for users who have not opted into the league. 230 DashboardLite.fetchXpTotal = function () { 231 return DashboardLite._get('/competency-api/xp').then(function (data) { 232 return (data && typeof data.xp === 'number') ? data.xp : null; 233 }); 234 }; 235 236 // Per-page promise memo so multiple widget instances share one request each. 237 DashboardLite._memo = {}; 238 DashboardLite._once = function (key, fn) { 239 if (!DashboardLite._memo[key]) DashboardLite._memo[key] = fn(); 240 return DashboardLite._memo[key]; 241 }; 242 243 DashboardLite.loadAll = function () { 244 var ff = function (r) { return r.status === 'fulfilled' ? r.value : null; }; 245 return Promise.allSettled([ 246 DashboardLite._once('profile', DashboardLite.fetchProfile), 247 DashboardLite._once('streaks', DashboardLite.fetchStreaks), 248 DashboardLite._once('league', DashboardLite.fetchLeagueXp), 249 DashboardLite._once('avatar', DashboardLite.fetchAvatar), 250 DashboardLite._once('cohort', DashboardLite.fetchCohort), 251 DashboardLite._once('xptotal', DashboardLite.fetchXpTotal) 252 ]).then(function (r) { 253 return { 254 profile: ff(r[0]), streaks: ff(r[1]), league: ff(r[2]), 255 avatarP: ff(r[3]), cohort: ff(r[4]), xpTotal: ff(r[5]) 256 }; 257 }); 258 }; 259 260 // Languages that have a weekly-practice page at /practice/practice_<slug>.php 261 DashboardLite._practiceMap = { 262 python: 'python', javascript: 'javascript', java: 'java', 'c++': 'cpp', 'c#': 'csharp', 263 c: 'c', php: 'php', kotlin: 'kotlin', r: 'r', rust: 'rust', swift: 'swift', typescript: 'typescript' 264 }; 265 DashboardLite._practiceUrl = function (topicName) { 266 var slug = DashboardLite._practiceMap[String(topicName || '').toLowerCase()]; 267 return slug ? '/practice/practice_' + slug + '.php' : null; 268 }; 269 // A topic where the user only hit the landing page (not a real lesson). 270 DashboardLite._isLanding = function (t) {
271 return t.lessonName === 'Home' || /(?:default\.asp|index\.php)$/i.test(t.lessonRelPath || ''); 272 }; 273 274 // Last successful continue result, cached so frozen accounts (whose state API may 275 // refuse them) and transient failures still get their Continue row. 276 DashboardLite._contCacheKey = 'DashboardLite.lastContinue'; 277 DashboardLite._readContCache = function () { 278 try { 279 var o = JSON.parse(localStorage.getItem(DashboardLite._contCacheKey)); 280 return (o && o.title && o.url) ? o : null; 281 } catch (e) { return null; } 282 }; 283 DashboardLite._writeContCache = function (c) { 284 try { localStorage.setItem(DashboardLite._contCacheKey, JSON.stringify(c)); } catch (e) {} 285 }; 286 287 // The user's current learning language + lesson. 288 // Returns {title, topic, next, progress, url, practice} or null. 289 DashboardLite.fetchContinue = function () { 290 if (typeof MyLearning === 'undefined' || typeof MyLearning.asyncFetchUserState !== 'function') { 291 return Promise.resolve(DashboardLite._readContCache()); 292 } 293 return MyLearning.asyncFetchUserState().then(function (reqRes) { 294 var d = (reqRes && reqRes.data) ? reqRes.data : {}; 295 var topics = (d.activity && d.activity.topics) || d.topics || {}; 296 // TODO: swap for the real "selected language" source once provided. Until then, 297 // prefer the most-recent topic where the user is actually mid-lesson (not just a 298 // landing-page visit) so e.g. Python ("Output Variables") wins over an HTML "Home" hit. 299 var best = null, bestReal = null; 300 Object.keys(topics).forEach(function (id) { 301 var t = topics[id]; 302 if (!t || !t.topicName) return; 303 var uts = t.lessonInteractionUts || 0; 304 if (!best || uts > (best.lessonInteractionUts || 0)) best = t; 305 if (!DashboardLite._isLanding(t) && (!bestReal || uts > (bestReal.lessonInteractionUts || 0))) bestReal = t; 306 }); 307 var chosen = bestReal || best; 308 if (!chosen) return DashboardLite._readContCache(); 309 var total = Number(chosen.lessonsTotal) || 0, score = Number(chosen.lessonsScore) || 0; 310 var pct = total > 0 ? Math.max(0, Math.min(100, Math.round(score / total * 100))) : 0; 311 var rel = chosen.lessonRelPath ? String(chosen.lessonRelPath).replace(/^\//, '') : null; 312 var result = { 313 title: chosen.topicName + ' Tutorial', 314 topic: chosen.topicName, 315 next: chosen.lessonName ? 'Next up: ' + chosen.lessonName : 'Continue learning', 316 progress: pct, url: rel ? '/' + rel : '#', 317 practice: DashboardLite._practiceUrl(chosen.topicName) 318 }; 319 DashboardLite._writeContCache(result); 320 return result; 321 })['catch'](function () { return DashboardLite._readContCache(); }); 322 }; 323 324 // ââ View ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 325 DashboardLite._flameSvg = 326 '<svg width="40" height="45" viewBox="0 0 43 48" fill="none" xmlns="http://www.w3.org/2000/svg">' + 327 '<path fill-rule="evenodd" clip-rule="evenodd" d="M0 28.3147C0 39.1866 8.62988 48 20.648 48C32.6662 48 42.4089 39.1866 42.4089 28.3147C42.4089 16.4391 34.0874 7.3021 26.6681 0.526583C26.3281 0.216026 25.8182 0.179203 25.4495 0.455052C23.0134 2.27727 21.0501 4.77236 19.5597 7.94034C18.0644 11.1186 17.0726 14.8936 16.5843 19.2653C16.5189 19.8511 15.971 20.2697 15.4048 20.1058C13.7124 19.6161 12.2029 18.7388 10.8762 17.4737C9.5416 16.2011 8.22419 14.4474 6.92394 12.2125C6.55348 11.5758 5.64722 11.513 5.23907 12.1263C1.82501 17.2561 0 22.9643 0 28.3147Z" fill="#FF9600"></path>' + 328 '<path d="M33.894 28.9292C33.2264 33.1276 29.8718 36.4211 25.5957 37.0766" stroke="#FF5D39" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg>'; 329 330 DashboardLite._puzzleSvg = 331 '<svg width="26" height="26" viewBox="0 0 24 24" fill="#04AA6D"><path d="M20.5 11H19V7a2 2 0 0 0-2-2h-4V3.5a2.5 2.5 0 0 0-5 0V5H4a2 2 0 0 0-2 2v3.8h1.5a2.1 2.1 0 0 1 0 4.2H2V19a2 2 0 0 0 2 2h3.8v-1.5a2.1 2.1 0 0 1 4.2 0V21H17a2 2 0 0 0 2-2v-4h1.5a2.5 2.5 0 0 0 0-5z"/></svg>'; 332 333 DashboardLite._esc = function (s) { 334 return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { 335 return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; 336 }); 337 }; 338 339 // Shrink a single-line title to fit its tile: drop trailing words until it fits, 340 // down to the first word (full text kept as the tooltip). e.g. 341 // "Logic Legend League" -> "Logic Legend" -> "Logic". 342 DashboardLite._fitTitle = function (el) { 343 if (!el) return; 344 var full = el.getAttribute('data-full'); 345 if (full === null) { full = el.textContent; el.setAttribute('data-full', full); } 346 el.title = full; 347 el.textContent = full; 348 if (!el.clientWidth) return; // hidden / not laid out yet 349 var words = full.split(/\s+/); 350 while (words.length > 1 && el.scrollWidth > el.clientWidth) { 351 words.pop(); 352 el.textContent = words.join(' '); 353 } 354 }; 355 DashboardLite._fitContainer = function (container) { 356 var els = container.querySelectorAll('.dlw-title'); 357 for (var i = 0; i < els.length; i++) DashboardLite._fitTitle(els[i]); 358 }; 359 360 DashboardLite._injectCss = function () { 361 if (document.getElementById(DashboardLite._styleId)) return; 362 var css = [ 363 // Base 364 '.dlw{position:relative;box-sizing:border-box;font-family:"Source Sans Pro","Segoe UI",sans-serif;color:#282A35;overflow:hidden;max-width:100%}', 365 '.dlw *,.dlw *::before,.dlw *::after{box-sizing:border-box}', 366 '.dlw-stage{display:flex;gap:10px;width:100%;height:100%}', 367 '.dlw-tile{position:relative;background:#fff;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,.15);border:1px solid rgba(0,0,0,.06);padding:14px;display:flex;align-items:center;gap:10px;text-decoration:none;color:inherit;overflow:hidden}', 368 '.dlw-tile-ico{flex-shrink:0;display:flex;align-items:center;justify-content:center}', 369 '.dlw-tile-body{min-width:0}', 370 '.dlw-title{font-size:15px;font-weight:700;color:#282A35;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}', 371 '.dlw-sub{font-size:12px;color:#888;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}', 372 '.dlw-sub b{color:#04AA6D}', 373 '.dlw-sub:empty{display:none}', 374 '.dlw-league-rank,.dlw-league-xp{white-space:normal}', 375 '.dlw-league-rank b{white-space:nowrap}', 376 '.dlw-avatar{border-radius:50%;object-fit:cover;background:#f1f1f1;width:48px;height:48px}', 377 '.dlw-av-wrap{position:relative;display:inline-block;line-height:0}', 378 '.dlw-mood{position:absolute;top:-3px;right:-3px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.25);padding:2px}', 379 '.dlw-col-mode .dlw-mood{width:16px;height:16px;top:-2px;right:-2px}', 380 '.dlw-streak-num{font-size:26px;font-weight:700;color:#FF6B35;line-height:1}', 381 '.dlw-lang-ico{width:40px;height:40px;border-radius:8px;background:#f1f1f1}', 382 '.dlw-badge{width:48px;height:48px;object-fit:contain;flex-shrink:0}', 383 '.dlw-row-mode .dlw-badge{width:76px;height:76px}',
384 '.dlw-grid-mode .dlw-badge{width:40px;height:40px}', 385 '.dlw-col-mode .dlw-badge{width:32px;height:32px}', 386 '.dlw-ring{position:relative;width:40px;height:40px;flex-shrink:0}', 387 '.dlw-ring-pct{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:9px;font-weight:700;color:#282A35}', 388 '.dlw-pickup{position:absolute;top:6px;left:50%;transform:translateX(-50%);background:#e8f5e9;color:#04AA6D;font-size:9px;font-weight:700;padding:3px 10px;border-radius:4px;text-transform:uppercase;letter-spacing:.02em;white-space:nowrap}', 389 '.dlw-action{flex-shrink:0}', 390 '.dlw-btn{display:inline-block;background:#04AA6D;color:#fff;padding:6px 18px;border-radius:5px;font-family:"Source Sans Pro",sans-serif;font-size:17px;font-weight:400;line-height:1.4;text-decoration:none;border:none;cursor:pointer;white-space:nowrap}', 391 // color repeated on :hover so page-level a:hover rules cannot turn the label green-on-green 392 'a.dlw-tile:hover .dlw-btn,.dlw-btn:hover{background:#059862;color:#fff}', 393 '.dlw-blur{filter:blur(3px);opacity:.5}', 394 395 // ââ stack mode (default, ~300px) - avatar+streak top row, league & continue full rows 396 '.dlw-stack-mode{width:300px}', 397 '.dlw-stack-mode .dlw-stage{flex-direction:row;flex-wrap:wrap;gap:12px}', 398 '.dlw-stack-mode .dlw-t-avatar,.dlw-stack-mode .dlw-t-streak{flex:1 1 calc(50% - 6px);min-width:0;flex-direction:column;justify-content:center;text-align:center;gap:6px;padding:16px}', 399 '.dlw-stack-mode .dlw-t-league,.dlw-stack-mode .dlw-t-continue,.dlw-stack-mode .dlw-t-practice{flex:1 1 100%}', 400 '.dlw-stack-mode .dlw-t-league .dlw-tile-body,.dlw-stack-mode .dlw-t-continue .dlw-tile-body,.dlw-stack-mode .dlw-t-practice .dlw-tile-body{flex:1}', 401 '.dlw-grid-mode .dlw-t-practice,.dlw-col-mode .dlw-t-practice{display:none!important}', 402 '.dlw-stack-mode .dlw-t-continue{padding-top:24px}', 403 '.dlw-stack-mode .dlw-avatar{width:64px;height:64px}', 404 '.dlw-stack-mode .dlw-tile-ico svg{width:40px;height:auto}', 405 406 // ââ row mode (960x300, 720x250) - tiles across, elements scaled up 407 // grid: avatar (row 1) + streak (row 2) stacked in a narrow first column; the 408 // text-heavy tiles (league/continue/practice) span both rows in the wide columns. 409 '.dlw-row-mode .dlw-stage{display:grid;grid-auto-flow:column;grid-template-columns:152px;grid-auto-columns:1fr;grid-template-rows:1fr 1fr;gap:10px}', 410 '.dlw-row-mode .dlw-tile{flex-direction:column;justify-content:center;text-align:center;gap:8px;padding:14px}', 411 '.dlw-row-mode .dlw-tile-body{display:flex;flex-direction:column;gap:5px}', 412 '.dlw-row-mode .dlw-t-avatar{grid-column:1;grid-row:1}', 413 '.dlw-row-mode .dlw-t-streak{grid-column:1;grid-row:2}', 414 '.dlw-row-mode .dlw-t-league,.dlw-row-mode .dlw-t-continue,.dlw-row-mode .dlw-t-practice{grid-row:1 / span 2}', 415 // the stacked cells are half-height - keep avatar + streak compact 416 '.dlw-row-mode .dlw-t-avatar,.dlw-row-mode .dlw-t-streak{padding:8px;gap:2px}', 417 '.dlw-row-mode .dlw-t-avatar .dlw-avatar{width:48px;height:48px}', 418 '.dlw-row-mode .dlw-t-avatar .dlw-mood{width:18px;height:18px}', 419 '.dlw-row-mode .dlw-t-avatar .dlw-title{font-size:13px}', 420 '.dlw-row-mode .dlw-t-streak .dlw-tile-ico svg{width:34px}', 421 '.dlw-row-mode .dlw-t-streak .dlw-streak-num{font-size:28px}', 422 '.dlw-row-mode .dlw-t-avatar .dlw-sub,.dlw-row-mode .dlw-t-streak .dlw-sub{font-size:11px}', 423 // full-height tiles keep the larger sizing 424 '.dlw-row-mode .dlw-tile-ico svg{width:52px;height:auto}', 425 '.dlw-row-mode .dlw-lang-ico{width:56px;height:56px}', 426 '.dlw-row-mode .dlw-title{font-size:19px}', 427 '.dlw-row-mode .dlw-sub{font-size:14px}', 428 '.dlw-row-mode .dlw-ring{width:44px;height:44px}', 429 '.dlw-row-mode .dlw-ring svg{width:44px;height:44px}', 430 '.dlw-row-mode .dlw-ring-pct{font-size:11px}', 431 432 // ââ grid mode (300x300) - 2x2 vertical tiles 433 '.dlw-grid-mode .dlw-stage{flex-direction:row;flex-wrap:wrap;align-content:stretch}',
434 '.dlw-grid-mode .dlw-tile{flex:1 1 calc(50% - 5px);min-width:calc(50% - 5px);flex-direction:column;justify-content:center;text-align:center;gap:4px;padding:10px}', 435 '.dlw-grid-mode .dlw-avatar{width:44px;height:44px}', 436 '.dlw-grid-mode .dlw-tile-ico svg{width:34px;height:auto}', 437 '.dlw-grid-mode .dlw-ring{display:none}', 438 '.dlw-grid-mode .dlw-pickup{display:none}', 439 440 // ââ col mode (160x300, 140x300) - 4 compact horizontal rows 441 '.dlw-col-mode .dlw-stage{flex-direction:column}', 442 '.dlw-col-mode .dlw-tile{flex:1;flex-direction:row;align-items:center;gap:10px;padding:10px}', 443 '.dlw-col-mode .dlw-tile-body{flex:1}', 444 '.dlw-col-mode .dlw-avatar{width:34px;height:34px}', 445 '.dlw-col-mode .dlw-lang-ico{width:34px;height:34px}', 446 '.dlw-col-mode .dlw-tile-ico svg{width:26px;height:auto}', 447 '.dlw-col-mode .dlw-streak-num{font-size:20px}', 448 '.dlw-col-mode .dlw-title{font-size:13px}', 449 '.dlw-col-mode .dlw-sub{font-size:11px}', 450 '.dlw-col-mode .dlw-action,.dlw-col-mode .dlw-ring,.dlw-col-mode .dlw-pickup{display:none}', 451 '.dlw-col-mode a.dlw-tile::after{content:"\\203A";color:#04AA6D;font-size:22px;font-weight:700;line-height:1;flex-shrink:0}', 452 453 // ââ Anon: aspiration carousel (padlocked) + persistent sign-up bar ââ 454 '.dlw-anon2{display:flex;flex-direction:column;width:100%;height:100%;background:#fff;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,.15);border:1px solid rgba(0,0,0,.06);overflow:hidden}', 455 '.dlw-caro{position:relative;flex:1;min-height:150px;overflow:hidden;background:linear-gradient(180deg,#30323f,#23242f)}', 456 '.dlw-caro-lock{position:absolute;top:8px;right:8px;z-index:2;width:26px;height:26px;border-radius:50%;background:rgba(255,255,255,.16);display:flex;align-items:center;justify-content:center}', 457 '.dlw-caro-lock svg{width:14px;height:14px;fill:#fff}', 458 // slow, gentle glide - a fast snap between slides reads as motion sickness 459 '.dlw-caro-track{display:flex;height:100%;transition:transform 1.2s cubic-bezier(.45,.05,.2,1)}', 460 '.dlw-slide{flex:0 0 100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:16px 14px 26px;text-align:center;color:#fff}', 461 // combined XP + streak slide (compact - shares the box with the pinned CTA on anon/frozen) 462 '.dlw-combo{gap:9px}', 463 '.dlw-combo-head{display:flex;align-items:center;justify-content:center;gap:9px}', 464 '.dlw-combo .dlw-vis-num{font-size:34px}', 465 '.dlw-combo-head .dlw-vis-bolt svg{width:26px;height:30px}', 466 '.dlw-combo .dlw-slide-cap b{font-size:16px}', 467 '.dlw-combo .dlw-slide-cap span{font-size:12px;margin-top:2px}', 468 '.dlw-combo-stats{display:flex;align-items:stretch;justify-content:center;gap:30px;border-top:1px solid rgba(255,255,255,.14);padding-top:10px;width:78%}', 469 '.dlw-combo-stat{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;font-weight:700;color:#fff;font-size:17px}', 470 '.dlw-combo-label{font-size:12px;color:#c9cdd6;font-weight:600}', 471 '.dlw-combo-flame svg{width:18px;height:21px}', 472 '.dlw-combo-div{width:1px;background:rgba(255,255,255,.15)}', 473 // button slides share the combo's compact typography 474 // course progress bar above the Continue learning link 475 '.dlw-progressbar{display:block;width:190px;height:8px;border-radius:4px;background:rgba(255,255,255,.16);overflow:hidden}', 476 '.dlw-progressbar-fill{display:block;height:100%;background:#04AA6D;border-radius:4px}', 477 '.dlw-vis-link{text-decoration:none;color:inherit;display:block}', 478 '.dlw-cap-link{color:#fff;text-decoration:none}', 479 '.dlw-cap-link:hover{color:#fff;text-decoration:underline}', 480 '.dlw-vis-link:hover~.dlw-slide-cap .dlw-cap-link{text-decoration:underline}', 481 '.dlw-slide-vis{display:flex;align-items:center;justify-content:center;gap:10px;min-height:78px}', 482 '.dlw-slide-cap b{display:block;font-size:19px;font-weight:700;color:#fff}',
483 '.dlw-slide-cap span{display:block;font-size:13px;color:#c9cdd6;margin-top:4px}', 484 '.dlw-vis-num{font-size:46px;font-weight:700;color:#FF9600}', 485 '.dlw-vis-green{color:#04AA6D}', 486 '.dlw-vis-flame svg{width:64px;height:auto}', 487 '.dlw-vis-league img{width:58px;height:58px}', 488 '.dlw-vis-league .dlw-vis-hi{width:80px;height:80px;filter:drop-shadow(0 0 10px rgba(150,120,255,.7))}', 489 '.dlw-vis-arrow{color:#6b6f7d;font-size:24px;font-weight:700}', 490 '.dlw-vis-bolt svg{width:52px;height:58px}', 491 '.dlw-vis-noads-ico svg{width:66px;height:66px}', 492 '.dlw-vis-av{width:92px;height:92px;border-radius:50%;background:#fff}', 493 '.dlw-vis-chip{width:52px;height:52px;border-radius:50%;background:#fff;display:flex;align-items:center;justify-content:center;margin-left:-16px;box-shadow:0 2px 6px rgba(0,0,0,.35)}', 494 '.dlw-vis-chip img{width:38px;height:38px;object-fit:contain}', 495 // Spaces preview - bare image with alpha-aware shadow, like the practice mockup 496 '.dlw-vis-spaces-img{width:250px;height:auto;filter:drop-shadow(0 10px 22px rgba(0,0,0,.5))}', 497 '.dlw-col-mode .dlw-vis-spaces-img{width:124px}', 498 // anon/frozen reels share the box with the pinned CTA - cap screenshot height 499 // so the caption stays visible above the button 500 '.dlh-anon .dlw-vis-practice-img,.dlh-frozen .dlw-vis-practice-img,' + 501 '.dlh-anon .dlw-vis-spaces-img,.dlh-frozen .dlw-vis-spaces-img{width:auto;max-width:240px;max-height:140px}', 502 // quiet per-slide action link 503 // bare image (the mockup has its own device frame) - alpha-aware shadow, no box chrome 504 '.dlw-vis-practice-img{width:250px;height:auto;filter:drop-shadow(0 10px 22px rgba(0,0,0,.5))}', 505 '.dlw-col-mode .dlw-vis-practice-img{width:124px}', 506 '.dlw-caro-dots{position:absolute;bottom:8px;left:0;right:0;display:flex;gap:6px;justify-content:center;z-index:2}', 507 '.dlw-dot{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.35);border:none;cursor:pointer;padding:0;transition:width .2s,background .2s}', 508 '.dlw-dot.active{background:#04AA6D;width:18px;border-radius:4px}', 509 '.dlw-anon-bar{display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 18px}', 510 '.dlw-anon-signup{align-self:stretch;text-align:center;box-shadow:0 4px 14px rgba(4,170,109,.4);transition:transform .12s,background .15s}', 511 '.dlw-anon-signup:hover{transform:translateY(-1px)}', 512 '.dlw-anon-or{font-size:11px;color:#aaa}', 513 '.dlw-anon-social-row{display:flex;gap:10px;justify-content:center}', 514 '.dlw-social{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;border:1px solid #ddd;background:#fff;cursor:pointer;transition:transform .15s,box-shadow .15s}', 515 '.dlw-social:hover{transform:scale(1.12);box-shadow:0 4px 12px rgba(0,0,0,.18)}', 516 '.dlw-anon-signin{font-size:12px;color:#888}', 517 '.dlw-anon-signin a{color:#04AA6D;font-weight:700;text-decoration:none}', 518 '.dlw-anon-signin a:hover{text-decoration:underline}', 519 '.dlw-cta-s{display:none}', 520 // row (960/720): carousel left, sign-up bar pinned right 521 '.dlw-row-mode .dlw-anon2{flex-direction:row}', 522 '.dlw-row-mode .dlw-anon-bar{flex:0 0 320px;justify-content:center;padding:20px 26px}', 523 '.dlw-row-mode .dlw-slide-cap b{font-size:18px}', 524 // grid (300): compact bar 525 '.dlw-grid-mode .dlw-anon-bar{padding:12px;gap:6px}', 526 '.dlw-grid-mode .dlw-anon-or{display:none}', 527 // col (160/140): tight bar, short button, small socials, no divider/caption-sub 528 '.dlw-col-mode .dlw-anon-bar{padding:10px;gap:7px}', 529 '.dlw-col-mode .dlw-anon-or{display:none}', 530 '.dlw-col-mode .dlw-cta-l{display:none}', 531 '.dlw-col-mode .dlw-cta-s{display:inline}', 532 '.dlw-col-mode .dlw-social{width:30px;height:30px}', 533 '.dlw-col-mode .dlw-slide-cap b{font-size:13px}', 534 '.dlw-col-mode .dlw-slide-cap span{display:none}', 535 '.dlw-col-mode .dlw-vis-num{font-size:26px}', 536 537 // ââ Hero (full-width default.asp banner) ââ 538 '.dlh,.dlh *,.dlh *::before,.dlh *::after{box-sizing:border-box}', 539 // Reserve a stable height up-front so the page does not jump when the hero 540 // resolves (empty -> content) or swaps anon/logged-in layouts. 541 '.dlh-host{display:flex;align-items:center;min-height:400px}', 542 '.dlh{display:flex;align-items:center;gap:48px;width:100%;max-width:1100px;margin:0 auto;padding:10px 20px;text-align:left;font-family:"Source Sans Pro","Segoe UI",sans-serif}', 543 '.dlh-left{flex:2;min-width:0}', 544 '.dlh-right{flex:1.2;min-width:0;max-width:520px}', 545 // one fixed size for the showcase in BOTH states so anon and logged-in heroes match 546 '.dlh-right .dlw-caro{height:340px;border-radius:16px;box-shadow:0 16px 48px rgba(0,0,0,.4)}', 547 // !important margin beats default.asp global `h1{margin:2px 0 -20px 0 !important}` which otherwise overlaps the paragraph. 548 '.dlh-title{font-size:62px!important;font-weight:700;color:#fff;margin:0 0 16px!important;line-height:1.05;font-family:"Source Sans Pro",sans-serif}', 549 '.dlh-free{font-size:21px;color:#fff;margin:0 0 20px;line-height:1.4}', 550 '.dlh-free b{color:#FFF4A3}',
551 '.dlh-support{color:#c9cdd6;font-size:16px;line-height:1.55;margin:0 0 8px;max-width:520px}', 552 '.dlh-perks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 32px;list-style:none;margin:0 0 14px;padding:0;max-width:440px}', 553 '.dlh-perks li{position:relative;padding-left:24px;color:#fff;font-size:15px;line-height:1.5;white-space:nowrap}', 554 '.dlh-perks li::before{content:"\\2713";position:absolute;left:0;color:#04AA6D;font-weight:700}', 555 // "Sign in" CTA pinned to the bottom inside the carousel (anon hero) 556 '.dlw-caro-cta{position:absolute;left:14px;right:14px;bottom:24px;z-index:2;display:block;text-align:center;background:#04AA6D;color:#fff;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:600;padding:10px;border-radius:8px;text-decoration:none;box-shadow:0 4px 14px rgba(0,0,0,.35);transition:background .15s}', 557 '.dlw-caro-cta:hover{background:#059862;color:#fff}', 558 // slides nudge content up just enough to clear the pinned CTA (anon Sign in / 559 // frozen Reactivate), keeping the carousel box identical across states 560 '.dlh-anon .dlw-slide,.dlh-frozen .dlw-slide{padding-bottom:58px}', 561 '.dlh-sub{font-size:14px;color:#aab;margin-top:14px}', 562 '.dlh-dash{display:inline-flex;align-items:center;gap:7px;color:#aab;font-size:14px;font-weight:600;text-decoration:none}', 563 '.dlh-dash:hover{color:#04AA6D}', 564 '.dlh-dash svg{flex-shrink:0}', 565 '.dlh-sub a{color:#04AA6D;font-weight:700;text-decoration:none}', 566 '.dlh-sub a:hover{text-decoration:underline}', 567 // badges variant: stats card on top + three badge cards below 568 '.dlh-badges{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}', 569 '.dlh-stats-card{grid-column:1/-1;display:flex;align-items:stretch;justify-content:space-between;gap:8px;padding:20px 16px;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);border-radius:12px}', 570 '.dlh-stat{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:4px;text-decoration:none;min-width:0}', 571 '.dlh-stat-top{height:38px;display:flex;align-items:center;justify-content:center}', 572 '.dlh-stat b{font-size:20px;color:#fff;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}', 573 '.dlh-stat b i{font-style:normal}', 574 '.dlh-stat-unit{font-size:13px;color:#c9cdd6;font-weight:600}', 575 '.dlh-stat-label{font-size:13px;color:#c9cdd6;font-weight:600;white-space:nowrap}', 576 '.dlh-stat:hover .dlh-stat-label{color:#fff}', 577 '.dlh-stat-ico svg{width:23px;height:26px}', 578 '.dlh-stat-bolt svg{width:29px;height:32px}', 579 '.dlh-stat img{width:36px;height:36px;object-fit:contain}', 580 '.dlh-stat-locked{filter:grayscale(1);opacity:.45}', 581 '.dlh-stat-div{width:1px;background:rgba(255,255,255,.15);flex:0 0 1px}', 582 '.dlh-badge{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:14px 10px;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);border-radius:12px;text-decoration:none;text-align:center;transition:background .15s,transform .12s}', 583 '.dlh-badge:hover{background:rgba(255,255,255,.13);transform:translateY(-2px)}', 584 '.dlh-badge-ico{height:42px;display:flex;align-items:center;justify-content:center}', 585 '.dlh-badge-ico svg{width:34px;height:38px}', 586 '.dlh-badge-ico img{width:42px;height:42px;object-fit:contain}', 587 '.dlh-badge b{font-size:17px;color:#fff;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}', 588 '.dlh-badge b i{font-style:normal}', 589 '.dlh-badge-green{color:#04AA6D}', 590 '.dlh-badge>span:last-child{font-size:12px;color:#c9cdd6;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}', 591 // fallback when there is no continue data: "Or continue reading for free [All Tutorials]" 592 '.dlh-startrow{display:flex;align-items:center;gap:14px;margin-top:4px}', 593 '.dlh-or{color:#c9cdd6;font-size:16px}', 594 '.dlh-or-link{color:#04AA6D;font-size:16px;font-weight:700;text-decoration:none}', 595 '.dlh-or-link:hover{color:#04AA6D;text-decoration:underline}', 596 '.dlh-hname{white-space:nowrap}', 597 '.dlh-hav-wrap{position:relative;display:inline-block;vertical-align:middle;margin-left:16px;margin-top:-8px;line-height:0}', 598 '.dlh-havatar{width:56px;height:56px;border-radius:50%;background:#fff;object-fit:contain}', 599 '.dlh-face{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;pointer-events:none}', 600 // continue row: floating "pick up where you left off" tag + green-tinted border 601 '.dlh-cont2{position:relative;display:flex;width:100%;max-width:400px;box-sizing:border-box;align-items:center;gap:16px;margin-top:22px;padding:16px 18px 14px;border-radius:12px;background:rgba(255,255,255,.07);border:1px solid rgba(4,170,109,.55);text-decoration:none;transition:background .15s}', 602 '.dlh-cont2:hover{background:rgba(255,255,255,.12)}', 603 '.dlh-cont2-tag{position:absolute;top:-10px;left:50%;transform:translateX(-50%);background:#D9EEE1;color:#04AA6D;font-size:9px;font-weight:700;padding:3px 12px;border-radius:4px;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}', 604 '.dlh-cont2-ico{width:46px;height:46px;border-radius:9px;background:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0}', 605 '.dlh-cont2-txt{font-size:18px;font-weight:700;color:#fff;white-space:nowrap}', 606 '.dlh-cont2-mid{flex:1;display:flex;flex-direction:column;gap:4px;min-width:0}', 607 '.dlh-cont2 .dlw-progressbar{width:100%;max-width:170px}',
608 '@media(max-width:900px){.dlh-host{min-height:0;padding:24px 0}.dlh{flex-direction:column;text-align:center;gap:28px}.dlh-left{order:1}.dlh-right{order:2;flex:0 0 auto;width:100%;max-width:380px}.dlh-support{margin-left:auto;margin-right:auto}.dlh-perks{margin-left:auto;margin-right:auto;width:fit-content;text-align:left}.dlh-startrow{justify-content:center}.dlh-cont2{margin-left:auto;margin-right:auto}.dlh-title{font-size:48px!important}}', 609 // small screens: tighter type; the showcase (carousel / badges) stays visible 610 '@media(max-width:600px){.dlh-title{font-size:40px!important}.dlh-free{font-size:18px}}' 611 ].join('\n'); 612 var el = document.createElement('style'); 613 el.id = DashboardLite._styleId; 614 el.textContent = css; 615 document.head.appendChild(el); 616 617 // Re-fit titles on resize (e.g. the responsive homepage hero). 618 if (!DashboardLite._fitBound) { 619 DashboardLite._fitBound = true; 620 var t; 621 window.addEventListener('resize', function () { 622 clearTimeout(t); 623 t = setTimeout(function () { 624 var els = document.querySelectorAll('.dlw-title[data-full]'); 625 for (var i = 0; i < els.length; i++) DashboardLite._fitTitle(els[i]); 626 }, 150); 627 }); 628 } 629 }; 630 631 DashboardLite._ringHtml = function (pct) { 632 var r = 16, circ = 2 * Math.PI * r; 633 return '<div class="dlw-ring"><svg width="40" height="40" viewBox="0 0 40 40" style="transform:rotate(-90deg)">' + 634 '<circle cx="20" cy="20" r="16" fill="none" stroke="#e8e8e8" stroke-width="4"></circle>' + 635 '<circle cx="20" cy="20" r="16" fill="none" stroke="#04AA6D" stroke-width="4" stroke-linecap="round"' + 636 ' stroke-dasharray="' + circ + '" stroke-dashoffset="' + (circ * (1 - pct / 100)) + '"></circle>' + 637 '</svg><div class="dlw-ring-pct">' + pct + '%</div></div>'; 638 }; 639 640 // ââ Individual tile builders (composed by the widget and the hero) ââ 641 DashboardLite._tileAvatar = function (xp) { 642 var fb = DashboardLite._fallbackAvatar; 643 return '<div class="dlw-tile dlw-t-avatar">' + 644 '<div class="dlw-tile-ico"><span class="dlw-av-wrap">' + 645 '<img class="dlw-avatar" src="' + fb + '" alt="Avatar" onerror="this.onerror=null;this.src=\'' + fb + '\'">' + 646 '<img class="dlw-mood" alt="" style="display:none">' + 647 '</span></div>' + 648 '<div class="dlw-tile-body">' + 649 '<div class="dlw-title dlw-name"> </div>' + 650 '<div class="dlw-sub">XP <b class="dlw-xp">' + xp + '</b></div>' + 651 '</div>' + 652 '</div>'; 653 }; 654 DashboardLite._tileStreak = function (streak) { 655 return '<div class="dlw-tile dlw-t-streak">' + 656 '<div class="dlw-tile-ico">' + DashboardLite._flameSvg + '</div>' + 657 '<div class="dlw-tile-body">' + 658 '<div class="dlw-streak-num dlw-streak">' + streak + '</div>' + 659 '<div class="dlw-sub">day streak</div>' + 660 '<div class="dlw-sub dlw-freeze"></div>' + 661 '</div>' + 662 '</div>'; 663 }; 664 DashboardLite._tileLeague = function () { 665 var woodBadge = DashboardLite._leagueImgBase + '/wood.svg'; 666 return '<a class="dlw-tile dlw-t-league ga-fp ga-widget-league" href="' + DashboardLite._leaderboardUrl + '">' + 667 '<div class="dlw-tile-ico"><img class="dlw-badge" src="' + woodBadge + '" alt="League" ' + 668 'onerror="this.onerror=null;this.src=\'' + woodBadge + '\'"></div>' + 669 '<div class="dlw-tile-body">' + 670 '<div class="dlw-title dlw-tier">Wood</div>' + 671 '<div class="dlw-sub dlw-league-rank"></div>' + 672 '<div class="dlw-sub dlw-league-xp"><b class="dlw-weekly">0</b> XP</div>' + 673 '</div>' + 674 '<span class="dlw-action"><span class="dlw-btn">View</span></span>' + 675 '</a>'; 676 }; 677 // Continue is filled once progress resolves; hidden until then. 678 DashboardLite._tileContinue = function () { 679 return '<a class="dlw-tile dlw-t-continue ga-fp ga-widget-continue" href="#" style="display:none"></a>'; 680 }; 681 // Weekly practice - shown only if the selected language has a practice page. 682 DashboardLite._tilePractice = function () { 683 return '<a class="dlw-tile dlw-t-practice ga-fp ga-widget-practice" href="#" style="display:none">
683' + 684 '<div class="dlw-tile-ico dlw-lang-ico">' + DashboardLite._puzzleSvg + '</div>' + 685 '<div class="dlw-tile-body"><div class="dlw-title dlw-practice-title">Weekly practice</div>' + 686 '<div class="dlw-sub">Solve & earn bonus XP</div></div>' + 687 '<span class="dlw-action"><span class="dlw-btn">Solve</span></span>' + 688 '</a>'; 689 }; 690 691 DashboardLite._loggedInHtml = function (seed) { 692 seed = seed || {}; 693 var xp = typeof seed._initialXp === 'number' ? seed._initialXp : 0; 694 var streak = typeof seed._initialStreak === 'number' ? seed._initialStreak : 0; 695 return '<div class="dlw-stage">' + 696 DashboardLite._tileAvatar(xp) + DashboardLite._tileStreak(streak) + 697 DashboardLite._tileLeague() + DashboardLite._tileContinue() + DashboardLite._tilePractice() + 698 '</div>'; 699 }; 700 701 DashboardLite._continueInner = function (c) { 702 return '<div class="dlw-pickup">Pick up where you left off</div>' + 703 '<div class="dlw-tile-ico dlw-lang-ico">' + DashboardLite._topicIconHtml(c.title) + '</div>' + 704 '<div class="dlw-tile-body">' + 705 '<div class="dlw-title">' + DashboardLite._esc(c.title) + '</div>' + 706 '<div class="dlw-sub">' + DashboardLite._esc(c.next) + '</div>' + 707 '</div>' + 708 DashboardLite._ringHtml(typeof c.progress === 'number' ? c.progress : 0) + 709 '<span class="dlw-action"><span class="dlw-btn">Continue</span></span>'; 710 }; 711 712 DashboardLite._lockSvg = 713 '<svg viewBox="0 0 24 24" fill="#04AA6D"><path d="M12 1a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V6a5 5 0 0 0-5-5zm3 8H9V6a3 3 0 0 1 6 0v3z"/></svg>'; 714 // Analytics classes for tracked buttons/links: "ga-fp ga-frontpage-<name>". 715 DashboardLite._gaCls = function (name) { return 'ga-fp ga-frontpage-' + name; }; 716 717 // href links only the main visual and the title - not the whole slide box. 718 DashboardLite._slide = function (visCls, vis, title, sub, href, ga) { 719 var visHtml = '<div class="dlw-slide-vis ' + visCls + '">' + vis + '</div>'; 720 var titleHtml = title; 721 if (href) { 722 var h = DashboardLite._esc(href); 723 var cls = ga ? ' ' + ga : ''; 724 visHtml = '<a class="dlw-vis-link' + cls + '" href="' + h + '">' + visHtml + '</a>'; 725 titleHtml = '<a class="dlw-cap-link' + cls + '" href="' + h + '">' + title + '</a>'; 726 } 727 return '<div class="dlw-slide">' + visHtml + 728 '<div class="dlw-slide-cap"><b>' + titleHtml + '</b><span>' + sub + '</span></div></div>'; 729 }; 730 DashboardLite._boltSvg = '<svg viewBox="0 0 24 24" fill="#FFD23F"><path d="M13 2L4.5 13.5H11l-1 8.5L19.5 10H12.5z"/></svg>'; 731 DashboardLite._noAdsSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="#7ee0b8" stroke-width="2.2"><circle cx="12" cy="12" r="9"/><line x1="5.6" y1="5.6" x2="18.4" y2="18.4"/></svg>'; 732 733 // Combined XP + streak slide: big headline, caption, divided stats row, optional button. 734 DashboardLite._comboSlide = function (head, leftLabel, leftVal, streak, btnHtml) { 735 return '<div class="dlw-slide dlw-combo">' + 736 '<div class="dlw-combo-head"><span class="dlw-vis-bolt">' + DashboardLite._boltSvg + '</span>' + 737 '<span class="dlw-vis-num dlw-vis-green">' + head + '</span></div>' + 738 '<div class="dlw-slide-cap"><b>Keep learning. Keep leveling up.</b>' + 739 '<span>Track your progress and achieve more.</span></div>' + 740 '<div class="dlw-combo-stats">' + 741 '<div class="dlw-combo-stat"><span class="dlw-combo-label">' + leftLabel + '</span><span>' + leftVal + '</span></div>' + 742 '<div class="dlw-combo-div"></div>' + 743 '<div class="dlw-combo-stat"><span class="dlw-combo-flame">' + DashboardLite._flameSvg + '</span>' + 744 '<span>' + streak + '</span><span class="dlw-combo-label">Day streak</span></div>' + 745 '</div>' + 746 (btnHtml || '') + 747 '</div>'; 748 }; 749 // Spaces preview screenshot (own frame in the image, shown bare like the practice mockup). 750 DashboardLite._editorMockHtml = function () { 751 return '<picture><source srcset="/images/img_spaces_preview.webp" type="image/webp">' + 752 '<img class="dlw-vis-spaces-img" src="/images/img_spaces_preview.png" alt="" loading="lazy"></picture>'; 753 }; 754 755 // Aspiration slides (anon) - sell the dream, padlocked. 756 DashboardLite._carouselSlides = function () { 757 var L = DashboardLite._leagueImgBase, S = DashboardLite._slide; 758 var arrow = '<span class="dlw-vis-arrow">âº</span>'; 759 // Same order as the logged-in reel (XP+streak combo, Spaces, practice, league), 760 // then the anon-only extras (ad-free, avatar gear). The pinned carousel CTA 761 // (Sign in / Reactivate) doubles as the combo slide's button on these reels. 762 // dummy XP numbers (no level system) - same structure as the logged-in combo 763 return DashboardLite._comboSlide('12,560', 'XP this week', '850', '7', '') + 764 S('dlw-vis-build', DashboardLite._editorMockHtml(), 'Build & host sites', 'Your own Spaces, live on the web') + 765 S('dlw-vis-practice', '<picture><source srcset="/images/img_mockup.webp" type="image/webp"><img class="dlw-vis-practice-img" src="/images/img_mockup.png" alt="" loading="lazy"></picture>', 'Weekly practice', 'Fresh problems & bonus XP') + 766 S('dlw-vis-league', '<img src="' + L + '/wood.svg" alt="">' + arrow + '<img src="' + L + '/gold.svg" alt="">' + arrow + 767 '<img class="dlw-vis-hi" src="' + L + '/diamond.svg" alt="">', 'Climb the leagues', 'From Wood to Master Coder', 768 DashboardLite._leagueInfoUrl, DashboardLite._gaCls('league-info')) + 769 S('dlw-vis-noads', '<span class="dlw-vis-noads-ico">' + DashboardLite._noAdsSvg + '</span>', 'Ad-free learning', 'Focus with zero distractions') + 770 S('dlw-vis-avatar', '<img class="dlw-vis-av" src="' + DashboardLite._fallbackAvatar + '" alt="">' + 771 '<span class="dlw-vis-chip">
771<img src="/lib/account/img/avatar_shirt.png" alt="" loading="lazy"></span>' + 772 '<span class="dlw-vis-chip"><img src="/lib/account/img/avatar_shorts.png" alt="" loading="lazy"></span>', 773 'Customize your avatar', 'Unlock gear, moods & rare skins'); 774 }; 775 DashboardLite._caroSlideCount = 6; 776 777 DashboardLite._spacesUrl = 'https://spaces.w3schools.com'; 778 DashboardLite._dashboardUrl = 'https://pathfinder.w3schools.com'; 779 780 // Per-topic activity pages (topicName lowercase). Fallback chain for the activity 781 // badge: weekly practice -> challenges -> exercises -> quiz -> generic challenges. 782 DashboardLite._activityMaps = { 783 challenges: { 784 html: '/html/html_challenges.asp', css: '/css/css_challenges.asp', 785 python: '/python/python_challenges.asp', java: '/java/java_challenges.asp', 786 'c++': '/cpp/cpp_challenges.asp' 787 }, 788 exercises: { 789 html: '/html/html_exercises.asp', css: '/css/css_exercises.asp', 790 javascript: '/js/js_exercises.asp', python: '/python/python_exercises.asp', 791 sql: '/sql/sql_exercises.asp', php: '/php/php_exercises.asp', 792 java: '/java/java_exercises.asp', c: '/c/c_exercises.php', 793 'c++': '/cpp/cpp_exercises.asp', 'c#': '/cs/cs_exercises.php', 794 jquery: '/jquery/jquery_exercises.asp', mysql: '/mysql/mysql_exercises.asp', 795 'node.js': '/nodejs/nodejs_exercises.asp', nodejs: '/nodejs/nodejs_exercises.asp', 796 r: '/r/r_exercises.asp', react: '/react/react_exercises.asp', 797 angular: '/angular/angular_exercises.asp', bootstrap: '/bootstrap5/bootstrap_exercises.php', 798 git: '/git/git_exercises.asp', kotlin: '/kotlin/kotlin_exercises.php', 799 go: '/go/go_exercises.php', swift: '/swift/swift_exercises.asp', 800 typescript: '/typescript/typescript_exercises.php', vue: '/vue/vue_exercises.php', 801 dsa: '/dsa/dsa_exercises.php', django: '/django/django_exercises.php', 802 mongodb: '/mongodb/mongodb_exercises.php', postgresql: '/postgresql/postgresql_exercises.php', 803 excel: '/excel/excel_exercises.php', aws: '/aws/aws_exercises.php', 804 bash: '/bash/bash_exercises.php', 'cyber security': '/cybersecurity/cybersecurity_exercises.php' 805 }, 806 quiz: { 807 html: '/html/html_quiz.asp', css: '/css/css_quiz.asp', javascript: '/js/js_quiz.asp', 808 python: '/python/python_quiz.asp', sql: '/sql/sql_quiz.asp', php: '/php/php_quiz.asp', 809 java: '/java/java_quiz.asp', c: '/c/c_quiz.php', 'c++': '/cpp/cpp_quiz.asp', 810 'c#': '/cs/cs_quiz.php', jquery: '/jquery/jquery_quiz.asp', mysql: '/mysql/mysql_quiz.asp', 811 'node.js': '/nodejs/nodejs_quiz.asp', nodejs: '/nodejs/nodejs_quiz.asp', 812 r: '/r/r_quiz.asp', react: '/react/react_quiz.asp', angular: '/angular/angular_quiz.asp', 813 bootstrap: '/bootstrap5/bootstrap_quiz.php', git: '/git/git_quiz.asp', 814 kotlin: '/kotlin/kotlin_quiz.php', swift: '/swift/swift_quiz.asp', 815 typescript: '/typescript/typescript_quiz.php', vue: '/vue/vue_quiz.php', 816 dsa: '/dsa/dsa_quiz.php', django: '/django/django_quiz.php', 817 postgresql: '/postgresql/postgresql_quiz.php', bash: '/bash/bash_quiz.php', 818 aws: '/aws/aws_quiz.php', 'cyber security': '/cybersecurity/cybersecurity_quiz.php', 819 accessibility: '/accessibility/accessibility_quiz.php', xml: '/xml/xml_quiz.asp' 820 } 821 }; 822 DashboardLite._actIcons = { 823 target: '<svg viewBox="0 0 24 24" fill="none" stroke="#04AA6D" stroke-width="2"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1" fill="#04AA6D"/></svg>', 824 pencil: '<svg viewBox="0 0 24 24" fill="#04AA6D"><path d="M3 17.25V21h3.75L17.8 9.94l-3.75-3.75L3 17.25zM20.7 7.04a1 1 0 0 0 0-1.41l-2.33-2.33a1 1 0 0 0-1.41 0l-1.83 1.82 3.75 3.75 1.82-1.83z"/></svg>', 825 question: '<svg viewBox="0 0 24 24" fill="none" stroke="#04AA6D" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M9.6 9.4a2.5 2.5 0 1 1 3.8 2.1c-.8.5-1.4 1-1.4 2"/><circle cx="12" cy="16.8" r=".5" fill="#04AA6D"/></svg>' 826 }; 827 DashboardLite._activityFor = function (topic) { 828 var k = String(topic || '').toLowerCase(); 829 var p = DashboardLite._practiceUrl(topic); 830 if (p) return { label: 'Practice', sub: 'Weekly problem', url: p, ico: DashboardLite._puzzleSvg }; 831 var A = DashboardLite._activityMaps, I = DashboardLite._actIcons; 832 if (A.challenges[k]) return { label: 'Challenges', sub: 'Test your skills', url: A.challenges[k], ico: I.target }; 833 if (A.exercises[k]) return { label: 'Exercises', sub: 'Train as you learn', url: A.exercises[k], ico: I.pencil }; 834 if (A.quiz[k]) return { label: 'Quiz', sub: 'Check your knowledge', url: A.quiz[k], ico: I.question }; 835 return { label: 'Challenges', sub: 'Test your skills', url: '/challenges/index.php', ico: I.target }; 836 }; 837 838 // Alternate logged-in showcase: stats card (total XP, weekly XP, streak, league) 839 // + two badges (Spaces and the topic's activity). 840 DashboardLite._badgesHtml = function (d, c) { 841 var L = DashboardLite._leagueImgBase; 842 d = d || {}; 843 var streak = d.streaks && typeof d.streaks.streaks === 'number' ? d.streaks.streaks : 0; 844 // Total XP from competency (canonical); the league endpoint reports 0 for 845 // users who have not opted into the league. 846 var xp = typeof d.xpTotal === 'number' ? d.xpTotal 847 : (d.league && typeof d.league.xp === 'number' ? d.league.xp : 0); 848 var weekly = d.league && typeof d.league.weeklyXp === 'number' ? d.league.weeklyXp : 0; 849 var optedIn = !!(d.league && d.league.optedIn); 850 var tier = d.league && d.league.tier; 851 var rank = d.cohort && d.cohort.userRank, total = d.cohort && d.cohort.totalUsers; 852 var monitorSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="#7ee0b8" stroke-width="2" stroke-linecap="round"><rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/></svg>'; 853 var act = DashboardLite._activityFor(c && c.topic); 854 855 // Every stat gets the same top icon zone (empty for the XP columns) so the 856 // value and label rows line up across all four columns. 857 var stat = function (href, top, val, label, ga) { 858 return '<a class="dlh-stat ' + ga + '" href="' + DashboardLite._esc(href) + '">' + 859 '<span class="dlh-stat-top">' + (top || '') + '</span>' + 860 '<b>' + val + '</b><span class="dlh-stat-label">' + label + '</span></a>'; 861 }; 862 var b = function (href, ico, val, label, ga) { 863 return '<a class="dlh-badge ' + ga + '" href="' + DashboardLite._esc(href) + '">' + 864 '<span class="dlh-badge-ico">' + ico + '</span><b>' + val + '</b><span>' + label + '</span></a>'; 865 }; 866 867 // Three iconed columns; weekly XP lives with the league (it is the league score). 868 var tierFirst = DashboardLite._esc(DashboardLite._tierName(tier).split(' ')[0]); 869 return '<div class="dlh-badges">' + 870 '<div class="dlh-stats-card">' + 871 stat(DashboardLite._dashboardUrl, '<span class="dlh-stat-ico dlh-stat-bolt">
871' + DashboardLite._boltSvg + '</span>', 872 '<i class="dlh-badge-green">' + xp.toLocaleString() + '</i>', 'Total XP', DashboardLite._gaCls('total-xp')) + 873 '<span class="dlh-stat-div"></span>' + 874 stat(DashboardLite._dashboardUrl, '<span class="dlh-stat-ico">' + DashboardLite._flameSvg + '</span>', 875 String(streak), 'Day streak', DashboardLite._gaCls('streak')) + 876 '<span class="dlh-stat-div"></span>' + 877 (optedIn 878 ? stat(DashboardLite._leaderboardUrl, 879 '<img src="' + DashboardLite._tierIcon(tier) + '" alt="" onerror="this.onerror=null;this.src=\'' + L + '/wood.svg\'">', 880 weekly.toLocaleString() + ' <i class="dlh-stat-unit">XP</i>', 881 tierFirst + (rank ? ' · #' + rank + (total ? ' of ' + total : '') : ' League'), 882 DashboardLite._gaCls('league')) 883 // Not opted into the league: the cell becomes the opt-in CTA. 884 : stat(DashboardLite._profileBase + '/league', 885 '<img class="dlh-stat-locked" src="' + L + '/wood.svg" alt="">', 886 'Join', 'Weekly league', DashboardLite._gaCls('join-league'))) + 887 '</div>' + 888 b(DashboardLite._spacesUrl, monitorSvg, 'Spaces', 'Build & host', DashboardLite._gaCls('spaces')) + 889 b(act.url, act.ico, act.label, act.sub, DashboardLite._gaCls(act.label.toLowerCase())) + 890 b('https://pathfinder.w3schools.com/exams', 891 '<svg viewBox="0 0 24 24" fill="none" stroke="#7ee0b8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="5"/><path d="M8.7 13.4 7 22l5-3 5 3-1.7-8.6"/></svg>', 892 'Certificates', 'Get certified', DashboardLite._gaCls('certificates')) + 893 '</div>'; 894 }; 895 896 DashboardLite._anonHtml = function () { 897 var ret = window.location.href; 898 var retEnc = encodeURIComponent(ret); 899 var signupHref = DashboardLite._profileBase + '/signup?redirect_url=' + retEnc; 900 var signinHref = DashboardLite._profileBase + '/log-in?redirect_url=' + retEnc; 901 var signupClick = "event.preventDefault();event.stopPropagation();" + 902 "if(typeof TopNavBar!=='undefined'&&typeof TopNavBar.showSignupDropdown==='function'){TopNavBar.showSignupDropdown();}" + 903 "else{window.open(this.href,'_blank');}"; 904 var signinClick = "event.preventDefault();event.stopPropagation();" + 905 "if(typeof TopNavBar!=='undefined'&&typeof TopNavBar.showLoginDropdown==='function'){TopNavBar.showLoginDropdown();}" + 906 "else{window.open(this.href,'_blank');}"; 907 908 var dots = ''; 909 for (var k = 0; k < DashboardLite._caroSlideCount; k++) { 910 dots += '<button class="dlw-dot ga-fp ga-frontpage-carousel-dot' + (k === 0 ? ' active' : '') + '" aria-label="Slide ' + (k + 1) + '"></button>'; 911 } 912 913 return '<div class="dlw-anon2">' + 914 '<div class="dlw-caro">' + 915 '<div class="dlw-caro-lock" aria-hidden="true">' + DashboardLite._lockSvg + '</div>' + 916 '<div class="dlw-caro-track">' + DashboardLite._carouselSlides() + '</div>' + 917 '<div class="dlw-caro-dots">' + dots + '</div>' + 918 '</div>' + 919 '<div class="dlw-anon-bar">' + 920 '<a class="dlw-btn dlw-anon-signup ga-fp ga-dlw-signup" href="' + DashboardLite._esc(signupHref) + '" ' + 921 'onclick="' + signupClick + '"><span class="dlw-cta-l">Start free trial</span>' + 922 '<span class="dlw-cta-s">Free trial</span></a>' + 923 '<div class="dlw-anon-or">or continue with</div>' + 924 '<div class="dlw-anon-social-row">' + 925 DashboardLite._socialBtn('google', ret) + 926 DashboardLite._socialBtn('facebook', ret) + 927 DashboardLite._socialBtn('github', ret) + 928 '</div>' + 929 '<div class="dlw-anon-signin">Cancel anytime · ' + 930 '<a class="ga-fp ga-dlw-signin-link" href="' + DashboardLite._esc(signinHref) + '" onclick="' + signinClick + '">Sign in</a></div>' + 931 '</div>' + 932 '</div>'; 933 }; 934 935 // Auto-rotating carousel, scoped to one container (multiple instances run independently). 936 DashboardLite._startCarousel = function (container) { 937 if (container._dlwCaroTimer) { clearInterval(container._dlwCaroTimer); container._dlwCaroTimer = null; } 938 var caro = container.querySelector('.dlw-caro'); 939 if (!caro) return; 940 var track = caro.querySelector('.dlw-caro-track'); 941 var dots = caro.querySelectorAll('.dlw-dot'); 942 var count = track ? track.children.length : 0; 943 if (!track || count < 2) return; 944 945 var idx = 0, timer = null; 946 var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 947 var go = function (n) { 948 idx = (n % count + count) % count; 949 track.style.transform = 'translateX(' + (-idx * 100) + '%)'; 950 for (var d = 0; d < dots.length; d++) dots[d].className = 'dlw-dot ga-fp ga-frontpage-carousel-dot' + (d === idx ? ' active' : ''); 951 }; 952 var stop = function () { if (timer) { clearInterval(timer); timer = null; container._dlwCaroTimer = null; } }; 953 var start = function () { if (!reduce && !timer) { timer = setInterval(function () { go(idx + 1); }, 9000); container._dlwCaroTimer = timer; } }; 954 for (var d = 0; d < dots.length; d++) { 955 (function (i) { dots[i].addEventListener('click', function () { go(i); stop(); start(); }); })(d); 956 } 957 caro.addEventListener('mouseenter', stop); 958 caro.addEventListener('mouseleave', start); 959 go(0); 960 start(); 961 }; 962 963 // ââ Live data (scoped per container; one watcher feeds all instances) âââââ 964 DashboardLite._instances = []; 965 DashboardLite._watching = false;
966 967 DashboardLite._readLiteState = function () { 968 if (typeof TopNavBar !== 'undefined' && TopNavBar.mylUserLiteState) return TopNavBar.mylUserLiteState; 969 return null; 970 }; 971 972 DashboardLite._applyLiteStateTo = function (container, s) { 973 if (!s) return; 974 if (typeof s.xp === 'number') { 975 var x = container.querySelector('.dlw-xp'); 976 if (x) x.textContent = s.xp; 977 } 978 if (typeof s.lightning === 'number') { 979 var st = container.querySelector('.dlw-streak'); 980 if (st) st.textContent = s.lightning; 981 } 982 }; 983 984 DashboardLite._applyLiteStateAll = function () { 985 var s = DashboardLite._readLiteState(); 986 if (!s) return; 987 for (var i = 0; i < DashboardLite._instances.length; i++) { 988 DashboardLite._applyLiteStateTo(DashboardLite._instances[i], s); 989 } 990 }; 991 992 DashboardLite._startLiveWatch = function () { 993 if (DashboardLite._watching) return; 994 DashboardLite._watching = true; 995 DashboardLite._applyLiteStateAll(); 996 if (typeof Util !== 'undefined' && typeof Util.objFieldOnSetCallback === 'function' 997 && typeof TopNavBar !== 'undefined') { 998 try { 999 Util.objFieldOnSetCallback(TopNavBar, 'mylUserLiteState', function () { 1000 DashboardLite._applyLiteStateAll(); 1001 }); 1002 } catch (e) {} 1003 } 1004 var tries = 0; 1005 var timer = setInterval(function () { 1006 tries++; 1007 var s = DashboardLite._readLiteState(); 1008 if (s) { 1009 DashboardLite._applyLiteStateAll(); 1010 if (typeof s.xp === 'number' || typeof s.lightning === 'number') { clearInterval(timer); return; } 1011 } 1012 if (tries >= 10) clearInterval(timer); 1013 }, 1000); 1014 }; 1015 1016 DashboardLite._applyData = function (container, d) { 1017 var qs = function (sel) { return container.querySelector(sel); }; 1018 1019 // Name from basic-lite; avatar prefers the rendered league/user/profile picture. 1020 if (d.profile) { 1021 var n = qs('.dlw-name'); if (n) { n.removeAttribute('data-full'); n.textContent = d.profile.fullName || ''; } 1022 } 1023 var avatarUrl = (d.avatarP && d.avatarP.avatar) || (d.profile && d.profile.avatar); 1024 if (avatarUrl) { var a = qs('.dlw-avatar'); if (a) a.src = avatarUrl; } 1025 1026 // Mood badge overlay 1027 var m = qs('.dlw-mood'); 1028 if (m) { 1029 var moodSrc = d.avatarP && d.avatarP.mood ? DashboardLite._moodIcon(d.avatarP.mood) : null; 1030 if (moodSrc) { m.src = moodSrc; m.style.display = ''; } else { m.style.display = 'none'; } 1031 } 1032 1033 if (d.streaks) { 1034 if (d.streaks.streaks > 0) { var s = qs('.dlw-streak'); if (s) s.textContent = d.streaks.streaks; } 1035 if (typeof d.streaks.freezeRemaining === 'number') { 1036 var f = qs('.dlw-freeze'); if (f) f.textContent = d.streaks.freezeRemaining + ' freezes left'; 1037 } 1038 } 1039 1040 if (d.league) { 1041 if (d.league.xp > 0) { var x = qs('.dlw-xp'); if (x) x.textContent = d.league.xp; } 1042 var t = qs('.dlw-tier'); if (t) { t.removeAttribute('data-full'); t.textContent = DashboardLite._tierName(d.league.tier); } 1043 var b = qs('.dlw-badge'); if (b) b.src = DashboardLite._tierIcon(d.league.tier); 1044 } 1045 1046 // League standing - two stacked lines so narrow tiles don't clip: 1047 // Rank: 2 of 20 1048 // 1015 XP 1049 var weekly = d.league ? d.league.weeklyXp : 0; 1050 var rankEl = qs('.dlw-t-league .dlw-league-rank'); 1051 if (rankEl) { 1052 rankEl.innerHTML = (d.cohort && d.cohort.userRank) 1053 ? 'Rank: <b>' + d.cohort.userRank + (d.cohort.totalUsers ? ' of ' + d.cohort.totalUsers : '') + '</b>' 1054 : ''; 1055 } 1056 var lxpEl = qs('.dlw-t-league .dlw-league-xp'); 1057 if (lxpEl) lxpEl.innerHTML = '<b class="dlw-weekly">' + weekly + '</b> XP'; 1058 1059 DashboardLite._fitContainer(container); 1060 }; 1061 1062 DashboardLite._fillContinue = function (container, c) { 1063 var tile = container.querySelector('.dlw-t-continue'); 1064 if (!tile) return; 1065 if (!c) { tile.style.display = 'none'; return; } 1066 tile.setAttribute('href', c.url || '#'); 1067 tile.innerHTML = DashboardLite._continueInner(c); 1068 tile.style.display = ''; 1069 DashboardLite._fitTitle(tile.querySelector('.dlw-title')); 1070 }; 1071 1072 DashboardLite._fillPractice = function (container, c) { 1073 var tile = container.querySelector('.dlw-t-practice'); 1074 if (!tile) return; 1075 if (!c || !c.practice) { tile.style.display = 'none'; return; } 1076 tile.setAttribute('href', c.practice); 1077 var ti = tile.querySelector('.dlw-practice-title'); 1078 if (ti) { ti.removeAttribute('data-full'); ti.textContent = 'Practice ' + c.topic; } 1079 tile.style.display = ''; 1080 DashboardLite._fitTitle(tile.querySelector('.dlw-title')); 1081 }; 1082 1083 DashboardLite._isLoggedIn = function () { 1084 // Any positive signal counts. UserSession.loggedIn starts as null and only 1085 // flips to true after server/API verification (which may not happen on every 1086 // page/host), so a readable accessToken cookie is the most reliable signal. 1087 if (typeof UserSession !== 'undefined' && UserSession.loggedIn === true) return true; 1088 if (DashboardLite._getToken()) return true; 1089 if (DashboardLite._readLiteState()) return true; 1090 return false;
1091 }; 1092 1093 // Logged in but the subscription/trial has ended (uic cookie plan 'closed'). 1094 DashboardLite._plansUrl = 'https://order.w3schools.com/plans'; 1095 DashboardLite._isFrozen = function () { 1096 try { 1097 if (typeof UserSession !== 'undefined' && typeof UserSession.getUserSubscriptionPlan === 'function') { 1098 return String(UserSession.getUserSubscriptionPlan('free')).toLowerCase() === 'closed'; 1099 } 1100 } catch (e) {} 1101 return false; 1102 }; 1103 1104 // Render the logged-in dashboard tiles into widgetEl and wire live data. 1105 DashboardLite._hydrateLoggedIn = function (widgetEl) { 1106 var seed = {}; 1107 var lite = DashboardLite._readLiteState(); 1108 if (lite) { 1109 if (typeof lite.xp === 'number') seed._initialXp = lite.xp; 1110 if (typeof lite.lightning === 'number') seed._initialStreak = lite.lightning; 1111 } 1112 widgetEl.innerHTML = DashboardLite._loggedInHtml(seed); 1113 1114 if (DashboardLite._instances.indexOf(widgetEl) === -1) DashboardLite._instances.push(widgetEl); 1115 DashboardLite._applyLiteStateTo(widgetEl, lite); 1116 DashboardLite._startLiveWatch(); 1117 1118 DashboardLite.loadAll().then(function (data) { DashboardLite._applyData(widgetEl, data); }); 1119 DashboardLite.fetchContinue().then(function (c) { 1120 DashboardLite._fillContinue(widgetEl, c); 1121 DashboardLite._fillPractice(widgetEl, c); 1122 // Re-fit now that all tiles are visible - showing continue/practice shrinks 1123 // every tile, so titles fit earlier (e.g. league) must re-measure. 1124 DashboardLite._fitContainer(widgetEl); 1125 }); 1126 }; 1127 1128 DashboardLite._paint = function (container) { 1129 if (!DashboardLite._isLoggedIn()) { 1130 container.innerHTML = DashboardLite._anonHtml(); 1131 DashboardLite._startCarousel(container); 1132 return; 1133 } 1134 DashboardLite._hydrateLoggedIn(container); 1135 }; 1136 1137 // Gate: wait for the user session to be verified before deciding logged-in/anon. 1138 // Shared across all instances so the session is verified only once per page. 1139 DashboardLite._sessionReadyP = null; 1140 DashboardLite.whenSessionReady = function (cb) { 1141 if (!DashboardLite._sessionReadyP) { 1142 DashboardLite._sessionReadyP = new Promise(function (resolve) { 1143 var done = false; 1144 var run = function () { if (done) return; done = true; resolve(); }; 1145 var attempts = 0; 1146 var wait = function () { 1147 if (typeof MyLearning !== 'undefined' && typeof MyLearning.loadUser === 'function') { 1148 MyLearning.loadUser('dlw', run); 1149 } else if (attempts++ < 30) { 1150 setTimeout(wait, 100); 1151 } else { run(); } 1152 }; 1153 wait(); 1154 }); 1155 } 1156 DashboardLite._sessionReadyP.then(cb); 1157 }; 1158 1159 // render(containerId, format?) - format is a key of FORMATS, or omitted for the 1160 // default 300px stack layout. 1161 DashboardLite.render = function (containerId, format) { 1162 DashboardLite._injectCss(); 1163 var container = document.getElementById(containerId); 1164 if (!container) return; 1165 1166 var fmt = (format && DashboardLite.FORMATS[format]) || null; 1167 container.classList.add('dlw'); 1168 if (fmt) { 1169 container.classList.add('dlw-' + fmt.mode + '-mode'); 1170 container.style.width = fmt.w + 'px'; 1171 container.style.height = fmt.h + 'px'; 1172 } else { 1173 container.classList.add('dlw-stack-mode'); 1174 } 1175 1176 DashboardLite.whenSessionReady(function () { DashboardLite._paint(container); }); 1177 }; 1178 1179 // Carousel showcase markup. Lock badge only for anon. Custom slides/count for the live 1180 // (logged-in) reel. ctaHtml pins a button to the bottom inside the carousel. 1181 DashboardLite._caroHtml = function (showLock, slidesHtml, count, ctaHtml) { 1182 if (slidesHtml == null) slidesHtml = DashboardLite._carouselSlides(); 1183 if (count == null) count = DashboardLite._caroSlideCount; 1184 var dots = ''; 1185 for (var k = 0; k < count; k++) { 1186 dots += '<button class="dlw-dot ga-fp ga-frontpage-carousel-dot' + (k === 0 ? ' active' : '') + '" aria-label="Slide ' + (k + 1) + '"></button>'; 1187 } 1188 return '<div class="dlw-caro">' + 1189 (showLock ? '<div class="dlw-caro-lock" aria-hidden="true">' + DashboardLite._lockSvg + '</div>' : '') + 1190 '<div class="dlw-caro-track">
1190' + slidesHtml + '</div>' + 1191 (ctaHtml || '') + 1192 '<div class="dlw-caro-dots">' + dots + '</div>' + 1193 '</div>'; 1194 }; 1195 1196 // ââ Hero (full-width default.asp banner) â clean, marketing-style for both states ââ 1197 DashboardLite._paintHero = function (container) { 1198 var ret = window.location.href, retEnc = encodeURIComponent(ret); 1199 var signupHref = DashboardLite._profileBase + '/signup?redirect_url=' + retEnc; 1200 var signinHref = DashboardLite._profileBase + '/log-in?redirect_url=' + retEnc; 1201 var signupClick = "event.preventDefault();event.stopPropagation();" + 1202 "if(typeof TopNavBar!=='undefined'&&typeof TopNavBar.showSignupDropdown==='function'){TopNavBar.showSignupDropdown();}" + 1203 "else{window.open(this.href,'_blank');}"; 1204 var signinClick = "event.preventDefault();event.stopPropagation();" + 1205 "if(typeof TopNavBar!=='undefined'&&typeof TopNavBar.showLoginDropdown==='function'){TopNavBar.showLoginDropdown();}" + 1206 "else{window.open(this.href,'_blank');}"; 1207 1208 if (DashboardLite._isLoggedIn()) { 1209 // Clean front-page look: personal welcome + integrated continue row + live showcase. 1210 // Frozen accounts (trial ended): learning stays free, but the live showcase becomes 1211 // the locked reel with a Reactivate CTA, and the copy reassures progress is saved. 1212 var frozen = DashboardLite._isFrozen(); 1213 container.innerHTML = 1214 '<div class="dlh dlh-in' + (frozen ? ' dlh-frozen' : '') + '">' + 1215 '<div class="dlh-left">' + 1216 // Frozen accounts get the sad expression layered ON TOP of their real avatar. 1217 '<h1 class="dlh-title">Hi<span class="dlh-hname"></span>' + 1218 '<a class="dlh-hav-wrap ga-fp ga-frontpage-avatar" href="https://pathfinder.w3schools.com/profile" title="Your profile">' + 1219 '<img class="dlh-havatar" alt="" style="display:none">' + 1220 (frozen ? '<img class="dlh-face" alt="" title="Your account is paused" src="/lib/account/img/avatar_sad.svg" onerror="this.onerror=null;this.style.display=\'none\'">' : '') + 1221 '</a></h1>' + 1222 '<p class="dlh-free">' + (frozen 1223 ? 'Your trial has ended.<br><b>Reactivate to keep progress, XP and Spaces active.</b>' 1224 : 'Keep your streak alive and pick up right where you left off.') + '</p>' + 1225 (frozen ? '<p class="dlh-support">You can still read every tutorial for free. The moment you reactivate, your streaks, league spot and Spaces pick up right where they paused.</p>' : '') + 1226 '<div class="dlh-startrow dlh-continue-fallback" style="display:none">' + 1227 '<span class="dlh-or">Continue reading for free</span>' + 1228 '<a class="dlh-or-link ga-fp ga-frontpage-all-tutorials" href="/where_to_start.asp" ' + 1229 'onclick="if(typeof TopNavBar!==\'undefined\'&&typeof TopNavBar.openNavItem===\'function\'){event.preventDefault();TopNavBar.openNavItem(\'tutorials\');}">All Tutorials ›</a>' + 1230 '</div>' + 1231 '<a class="dlh-cont2 ga-fp ga-frontpage-continue" href="#" style="display:none"></a>' + 1232 (frozen ? '' : 1233 '<div class="dlh-sub"><a class="dlh-dash ga-fp ga-frontpage-dashboard" href="https://pathfinder.w3schools.com">' + 1234 '<svg width="17" height="17" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><rect width="7" height="18" x="3" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="14" rx="1"></rect></svg>' + 1235 'Go to your dashboard</a></div>') + 1236 '</div>' + 1237 '<div class="dlh-right" id="' + container.id + '-caro">' + 1238 (frozen ? DashboardLite._caroHtml(true, null, null, 1239 '<a class="dlw-caro-cta ga-fp ga-frontpage-reactivate" href="' + DashboardLite._plansUrl + '">Reactivate</a>') : '') + 1240 '</div>' + 1241 '</div>'; 1242 if (frozen) DashboardLite._startCarousel(container); 1243 1244 // Badges paint instantly from cached data (topnav lite state + the localStorage 1245 // continue cache) and re-render when the live fetches resolve - waiting for 1246 // every API to settle leaves the column empty for seconds. 1247 if (!frozen) { 1248 var caroEl0 = document.getElementById(container.id + '-caro'); 1249 var lite0 = DashboardLite._readLiteState() || {}; 1250 if (caroEl0) { 1251 // Seed assumes opted-in so members do not flash the "Join" cell; the 1252 // real opt-in state corrects it when loadAll resolves. 1253 caroEl0.innerHTML = DashboardLite._badgesHtml({ 1254 streaks: { streaks: typeof lite0.lightning === 'number' ? lite0.lightning : 0 }, 1255 league: { xp: typeof lite0.xp === 'number' ? lite0.xp : 0, optedIn: true } 1256 }, DashboardLite._readContCache()); 1257 } 1258 } 1259 1260 var contP = DashboardLite.fetchContinue(); 1261 contP.then(function (c) { 1262 if (c) { 1263 var row = container.querySelector('.dlh-cont2'); 1264 if (row) { 1265 var pct = typeof c.progress === 'number' ? c.progress : 0; 1266 row.setAttribute('href', c.url || '#'); 1267 row.innerHTML = 1268 '<span class="dlh-cont2-tag">Pick up where you left off</span>' + 1269 '<span class="dlh-cont2-ico">' + DashboardLite._topicIconHtml(c.title) + '</span>' + 1270 '<span class="dlh-cont2-mid">' + 1271 '<span class="dlh-cont2-txt">' + DashboardLite._esc(c.title) + '</span>' + 1272 '<span class="dlw-progressbar" title="' + pct + '% complete">' + 1273 '<span class="dlw-progressbar-fill" style="width:' + pct + '%"></span></span>' + 1274 '</span>' + 1275 '<span class="dlw-btn">Continue</span>'; 1276 row.style.display = ''; 1277 } 1278 } else { 1279 var fb = container.querySelector('.dlh-continue-fallback'); 1280 if (fb) fb.style.display = ''; 1281 } 1282 }); 1283 // Personalize the headline + build the showcase once real data loads. 1284 DashboardLite.loadAll().then(function (data) { 1285 var nameEl = container.querySelector('.dlh-hname'); 1286 if (nameEl && data.profile && data.profile.firstName) { 1287 nameEl.textContent = ', ' + data.profile.firstName; 1288 } 1289 var avEl = container.querySelector('.dlh-havatar'); 1290 var avatarUrl = (data.avatarP && data.avatarP.avatar) || (data.profile && data.profile.avatar); 1291 if (avEl && avatarUrl) { avEl.src = avatarUrl; avEl.style.display = ''; } 1292 1293 if (!frozen) { 1294 contP.then(function (c) { 1295 var caroEl = document.getElementById(container.id + '-caro'); 1296 if (caroEl) caroEl.innerHTML = DashboardLite._badgesHtml(data, c); 1297 }); 1298 } 1299 }); 1300 return; 1301 } 1302 1303 container.innerHTML = 1304 '<div class="dlh dlh-anon">' + 1305 '<div class="dlh-left">' + 1306 '<h1 class="dlh-title">Learn to Code</h1>' + 1307 '<p class="dlh-free">Free tutorials, examples and references.<br><b>No sign-up needed, just start learning.</b></p>' + 1308 '<p class="dlh-support">Or sign up to unlock premium features:</p>' + 1309 '<ul class="dlh-perks">' + 1310 '<li>Progress tracking</li><li>Build & host websites</li>' + 1311 '<li>Ad-free learning</li><li>XP & Streaks</li>' + 1312 '<li>Practice problems</li><li>Leagues</li>' + 1313 '<li>Challenges</li><li>Avatar</li>' + 1314 '</ul>' + 1315 '<div class="dlh-sub">Already a member? <a class="ga-fp ga-frontpage-signin-link" href="' + DashboardLite._esc(signinHref) + '" onclick="' + signinClick + '">Sign in</a></div>' + 1316 '</div>' + 1317 '<div class="dlh-right">' + 1318 DashboardLite._caroHtml(true, null, null, 1319 '<a class="dlw-caro-cta ga-fp ga-frontpage-signin" href="' + DashboardLite._esc(signinHref) + '" onclick="' + signinClick + '">Sign in</a>') + 1320 '</div>' + 1321 '</div>'; 1322 DashboardLite._startCarousel(container); 1323 }; 1324 1325 // Logged-in showcase is the badge grid; anon/frozen get the aspiration carousel. 1326 DashboardLite.renderHero = function (containerId) { 1327 DashboardLite._injectCss(); 1328 var container = document.getElementById(containerId); 1329 if (!container) return; 1330 container.classList.add('dlh-host'); // reserve height immediately to avoid layout jump 1331 DashboardLite.whenSessionReady(function () { DashboardLite._paintHero(container); }); 1332 }; 1333 1334 window.DashboardLite = DashboardLite; 1335})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.