PageSourceSearch

https://webcade.fun/cabpage.js?v=1025436e

js webcade.fun collected 2026-10-08 08:27:22 UTC 9,857 bytes, 195 lines download raw bytes

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
19    .replace(/"/g, '&quot;').replace(/'/g, '&#39;'));
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) : '&mdash;',
141      'side-rank': st ? '#' + st.rank : '&mdash;',
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) : '&mdash;'],
157      ['Today&rsquo;s run', st ? fmt(st.points) : '&mdash;'],
158      ['Cabinets played', st ? fmt(st.games) : '&mdash;'],
159      ['Today&rsquo;s rank', st ? '#' + st.rank : '&mdash;'],
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 &mdash; your first run starts it.</p>'}
186      <a class="btn-text cab-prog-link" href="/leaderboard">View leaderboard <span aria-hidden="true">&rarr;</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.