1// The parts of a cabinet page that are the same on all eighteen of them: the 2// breadcrumb's category, the HOW TO PLAY panel under the game, and the YOUR 3// PROGRESS panel beside it. 4// 5// None of it is typed into a page. The category comes from GAME_CATS, the tips 6// come from the same table the pre-play tutorial card reads, and the figures 7// come from /api/config and /api/me. That is the point: a cabinet's advice and 8// a player's standing each exist in exactly one place, so a page cannot drift 9// out of step with the game it is describing. 10// 11// Loaded after app.js, and it has to be: app.js is what builds the control 12// hint and publishes it as ARCADE_CONTROL_HINT, which the HOW TO PLAY panel 13// quotes. Every block checks for the element it fills and returns quietly when 14// it is absent, so this file is harmless on a page that is not a cabinet. 15(() => { 16 const $ = id => document.getElementById(id); 17 const esc = window.esc || (v => String(v == null ? '' : v) 18 .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') 19 .replace(/"/g, '"').replace(/'/g, ''')); 20 const fmt = n => Number(n || 0).toLocaleString(undefined, { maximumFractionDigits: 2 }); 21 22 // The cabinet this page is. app.js publishes it; the <html data-game> attr is 23 // the fallback, and it is the same value from the same source. 24 const GAME = window.PAGE_GAME || document.documentElement.dataset.game || null; 25 if (!GAME) return; 26 27 // The breadcrumb and the shelf chip are gone: the crumb named the game's 28 // kind, which the title and the artwork below it already say, and the chip 29 // repeated the crumb. What is left is one link back to the library. 30 31 // --------------------------------------------------------- the controls 32 // 33 // The cabinet's own control hint, split into one chip per instruction. The 34 // hint is a single string built by app.js for the surface actually being held 35 // â "ARROWS OR WASD · â/W ROTATE · SPACE = SLAM" â and its middots are 36 // already the boundaries between instructions, so nothing new has to be 37 // written down per cabinet and nothing here can contradict the line the 38 // overlay shows on the screen. 39 function controls() { 40 const bar = $('cab-controls-bar'); 41 const list = $('cab-controls-list'); 42 if (!bar || !list) return; 43 const hint = window.ARCADE_CONTROL_HINT || ''; 44 const parts = hint.split('\u00b7').map(t => t.trim()).filter(Boolean); 45 if (!parts.length) { bar.hidden = true; return; } 46 list.innerHTML = parts.map(t => `<span class="ccb-item">${esc(t)}</span>`).join(''); 47 bar.hidden = false; 48 } 49 50 // --------------------------------------------------------- how to play 51 // 52 // The goal, then the two or three things that are not obvious until a run has 53 // already been lost to them, then the controls for the surface actually being 54 // held. All three from tables that already existed for the tutorial card. 55 function howto() { 56 const host = $('cab-howto'); 57 const tut = (window.CAB_TUTS || {})[GAME]; 58 if (!host || !tut) return; 59 // No control hint here. This panel used to end with the same string the 60 // CONTROLS strip above it is built from, which on a desktop looked like a 61 // summary and on a phone â where the two panels stack touching â was 62 // plainly the same sentence twice, once as chips and once as a run-on 63 // line. The strip is the better of the two: it splits the hint into one 64 // chip per instruction and it is closer to the machine. All eighteen 65 // cabinet pages carry both elements, so removing it here leaves no page 66 // without its controls. 67 host.innerHTML = ` 68 <p class="cab-goal">${esc(tut.goal)}</p> 69 <ul class="cab-tips">${(tut.tips || []).map(t => `<li>${esc(t)}</li>`).join('')}</ul>`; 70 } 71 howto(); 72 73 // A player can swap stick for pad mid-visit, and on ZIGGURAT the two do not 74 // even behave the same. app.js already re-reads its own hint when they do; 75 // this wraps that so the panel under the game is corrected at the same 76 // moment rather than going on describing the surface they put down. 77 controls(); 78 if (window.ARCADE_HINT_REFRESH) { 79 const inner = window.ARCADE_HINT_REFRESH; 80 window.ARCADE_HINT_REFRESH = () => { inner(); howto(); controls(); }; 81 } 82 83 // -------------------------------------------------------- your progress 84 // 85 // Four figures: this cabinet's best by anyone, where the player stands on 86 // today's board, how many stages they have put on it, and what today is 87 // worth. Signed out, the panel says how to make it say something instead of 88 // showing four dashes, because four dashes look broken and "connect a wallet" 89 // is actionable. 90 async function progress() { 91 const host = $('cab-progress'); 92 if (!host) return; 93 94 // THE CONFIG FETCH IS STILL HERE, but not for a prize any more. It said 95 // what today's board was worth, in $TOKEN, from cfg.daily.budget. The 96 // arcade does not pay out, so the figure beside a player's own progress is 97 // the score they are chasing. 98 // 99 // /api/daily carries the standings, and it is fetched alongside rather 100 // than after: the two are independent, and the sidebar should not wait on 101 // the board to say what it knows about this player. Either may fail on its 102 // own without taking the panel down.
103 let cfg = null, daily = null; 104 try { 105 [cfg, daily] = await Promise.all([ 106 fetch('/api/config?game=' + encodeURIComponent(GAME)).then(r => r.json()).catch(() => null), 107 fetch('/api/daily').then(r => r.json()).catch(() => null), 108 ]); 109 } catch (_) {} 110 111 const lead = ((daily && daily.standings) || [])[0]; 112 113 // Asking who this is requires a signature, and ARCADE_AUTH returns null 114 // when nobody has connected. That is not an error â it is the common case 115 // for a first visit, and it gets the version of the panel that invites one. 116 let me = null; 117 if (window.ARCADE_AUTH) { 118 try { 119 const auth = await window.ARCADE_AUTH(); 120 if (auth) { 121 // The cabinet is named, so the reply carries this player's day on 122 // THIS machine as well as their day overall. 123 me = await fetch('/api/me', { 124 method: 'POST', 125 headers: { 'Content-Type': 'application/json' }, 126 body: JSON.stringify({ ...auth, game: GAME }), 127 }).then(r => (r.ok ? r.json() : null)); 128 } 129 } catch (_) {} 130 } 131 132 const st = me && me.standing; 133 const cab = me && me.cabinet; 134 135 // The sidebar's three figures: the score to beat today, how this player has 136 // done on THIS machine, and where that puts them across the whole arcade. 137 const stages = n => `${fmt(n)} stage${Number(n) === 1 ? '' : 's'}`; 138 const side = { 139 'side-score': lead && lead.points > 0 ? stages(lead.points) : 'Open', 140 'side-best': cab ? stages(cab.stages) : '—', 141 'side-rank': st ? '#' + st.rank : '—', 142 }; 143 for (const [id, v] of Object.entries(side)) { const el = $(id); if (el) el.innerHTML = v; } 144 145 // Four figures, all of them about this player. The day's leading score is 146 // NOT among them: it is the same number the sidebar shows two inches away, 147 // and a panel headed YOUR PROGRESS whose largest figure is not yours is a 148 // panel answering the wrong question. 149 // 150 // Rows rather than tiles. Four big numbers in a 2x2 competed with the 151 // sidebar's three for the same glance and won, which put the emphasis on 152 // a player's own count instead of on the game. As a list the panel is read 153 // when it is looked for and quiet the rest of the time, and the labels get 154 // to be words rather than abbreviations that fit under a tile. 155 const rows = [ 156 ['Best run', cab ? fmt(cab.stages) : '—'], 157 ['Today’s run', st ? fmt(st.points) : '—'], 158 ['Cabinets played', st ? fmt(st.games) : '—'], 159 ['Today’s rank', st ? '#' + st.rank : '—'], 160 ]; 161 162 // The comment at the top of this function has always said the signed-out 163 // panel should invite rather than show four dashes, "because four dashes 164 // look broken and connect a wallet is actionable". It never did: the grid 165 // was drawn unconditionally and the invitation went UNDERNEATH it, so a 166 // visitor arriving from a shared link met four dashes and then, below 167 // them, the reason. That is the page at the moment of commitment, and it 168 // looked half-loaded on it. 169 // 170 // `me` is null only when ARCADE_AUTH found nobody, so this is exactly the 171 // signed-out case â a connected player who has never touched this cabinet 172 // still gets the grid, because for them the zeroes are an answer. 173 const signedOut = !me; 174 175 host.innerHTML = ` 176 ${signedOut ? '' : `<dl class="cab-prog"> 177 ${rows.map(([k, v]) => `<div><dt>${k}</dt><dd>${v}</dd></div>`).join('')} 178 </dl>`} 179 ${st 180 ? `<p class="cab-controls">${st.rank <= st.paying 181 ? 'You are on the podium. The board resets at midnight UTC.' 182 : esc(Math.max(0, (st.podium - st.points) + 1)) + ' more stages puts you on the podium.'}</p>` 183 : signedOut 184 ? '<p class="cab-controls cab-prog-invite">Connect a wallet and this fills in with your best run and your rank.</p>' 185 : '<p class="cab-controls">No stages on the board yet today — your first run starts it.</p>'}
186 <a class="btn-text cab-prog-link" href="/leaderboard">View leaderboard <span aria-hidden="true">→</span></a>`; 187 } 188 189 progress(); 190 // The board moves under a player while they are on the page â their own runs 191 // move it. Re-read when the wallet changes, so connecting one fills the panel 192 // in without a reload. 193 const w = window.ARCADE_WALLET; 194 if (w && w.onChange) w.onChange(() => progress()); 195})();
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.