game4 / site / index.html
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>séance — a p2p haunted-house party game</title>
<style>
  :root {
    --bg: #0d0a14; --panel: #171224; --panel-2: #1d1730; --border: #2c2140;
    --text: #e8dbc5; --muted: #8a7d99; --amber: #ffb347; --ghost: #9fe8dc;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: var(--bg); color: var(--text);
    font: 16px/1.65 Georgia, 'Iowan Old Style', 'Times New Roman', serif;
    padding: 40px 20px 80px;
  }
  main { max-width: 780px; margin: 0 auto; }
  h1 { font-size: 36px; letter-spacing: 8px; color: var(--amber); text-shadow: 0 0 22px #ffb34766; }
  .tag { color: var(--muted); margin: 6px 0 30px; font-size: 17px; }
  h2 { margin: 44px 0 12px; font-size: 22px; color: var(--amber); letter-spacing: 1px; }
  h3 { margin: 24px 0 8px; font-size: 17px; color: var(--ghost); }
  p, li { color: #cdc0ad; }
  ul, ol { padding-left: 22px; margin: 10px 0; }
  li { margin: 5px 0; }
  a { color: var(--ghost); }
  pre {
    background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
    padding: 14px 16px; overflow-x: auto; margin: 12px 0;
    font: 13.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
  }
  code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px;
    background: var(--panel-2); border-radius: 5px; padding: 1px 6px; }
  pre code { background: none; padding: 0; }
  .card {
    background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
    padding: 18px 20px; margin: 14px 0;
  }
  .btns { display: flex; gap: 10px; flex-wrap: wrap; margin: 18px 0 6px; }
  .btn {
    display: inline-block; padding: 10px 18px; border-radius: 8px; text-decoration: none;
    background: #241a30; border: 1px solid #4a3860; color: var(--amber);
    letter-spacing: 1px; font-size: 14px;
  }
  .btn:hover { background: #31234a; box-shadow: 0 0 14px #ffb34744; }
  footer { margin-top: 60px; color: var(--muted); font-size: 13px; }
  .langs {
    position: fixed; top: 12px; right: 14px; color: var(--muted); font-size: 13px;
    letter-spacing: .06em; user-select: none;
  }
  .langs button {
    font: inherit; color: inherit; background: none; border: 0; padding: 3px 4px;
    cursor: pointer; opacity: .55;
  }
  .langs button.on { color: var(--amber); opacity: 1; }
</style>
</head>
<body>
<nav class="langs" id="langs"></nav>

<main data-lang-block lang="en">
  <h1>SÉANCE</h1>
  <p class="tag">a haunted parlour game for one house, sixteen phones and three real
  rooms — this page is also the git repo.</p>

  <div class="btns">
    <a class="btn" href="https://game4.ardegazu.ro/">🕯 hold a séance</a>
    <a class="btn" href="browse/index.html">browse the code</a>
    <a class="btn" href="#clone">clone the repo</a>
  </div>

  <h2 id="clone">Clone</h2>
  <p>This site serves the repository over git's dumb-HTTP protocol as plain static
  files (plain files — there is no git server here, or any server at all):</p>
  <pre><code>git clone https://git.ardegazu.ro/game4.git
cd seance</code></pre>

  <h2>What it is</h2>
  <p>A party game you play with your feet. Gather 1–16 people in a home, agree
  which real room is <em>the Study</em>, <em>the Parlour</em> and <em>the
  Cellar</em>, and open one shared link on every phone. Each time the bell tolls
  you have ~20 seconds to physically walk to a room and tap it. A ghost sweeps
  one room — and it favours wherever the crowd stood last bell. Everyone caught
  loses a candle; lose all three and you fade. Last lit candle wins the séance.</p>
  <p>Every bell, one player is secretly the <strong>medium</strong>: their phone
  alone names a room the spirit will shun — truthfully. They can share it,
  keep it, or lie about it out loud. Where you <em>actually</em> stand is the
  honor system; the walking is the point, not the enforcement. Installable PWA,
  works from an iOS home screen.</p>

  <h2>How it works</h2>
  <ul>
    <li><strong>No game server.</strong> Browsers are
      <a href="https://libp2p.io">js-libp2p</a> peers: they dial a shared
      circuit-relay-v2 peer over WSS, discover each other through gossipsub,
      and upgrade to direct WebRTC — the circuit is signaling plus a
      rate-limited fallback path. The room secret lives in the URL fragment
      (<code>#…</code>) and never leaves the browser.</li>
    <li><strong>Rooms are cryptographic.</strong> Discovery is app-wide, so a
      room is enforced by a room-scoped protocol id
      (<code>/seance/2/&lt;HKDF(secret)&gt;</code> — wrong rooms fail protocol
      negotiation) plus a mutual sealed hello: one AES-256-GCM envelope keyed
      from the room secret and bound to both noise-authenticated peer ids.
      After the hello, every pair's frames ride libp2p's noise encryption,
      end-to-end even when routed through the relay. The p2p layer
      (<code>client/src/net/</code>) is copied from
      <a href="https://git.ardegazu.ro/game1/">neon-grid</a>.</li>
    <li><strong>One peer conducts.</strong> The lowest peer id runs the bell
      timers and the ghost's dice, collects secret room picks, and broadcasts
      tiny phase frames; everyone renders from them. Séances are seat-indexed so
      guests joining or leaving can't corrupt one in flight. If the conductor's
      phone dies the séance is voided and the next-lowest id takes over; late
      joiners get a snapshot and watch from the veil until the next séance.</li>
    <li><strong>Hosting without an origin server.</strong> The client is a static
      Vite build served as plain files — there is no backend anywhere. This page
      you're reading works the same way.</li>
  </ul>

  <h2>What's in the repo</h2>
  <div class="card">
    <h3>client/src/game/ — the game</h3>
    <p>TypeScript, no framework: typed wire protocol, host-authoritative séance
    logic (bells, crowd-hungry ghost, medium hints, candle elimination),
    WebAudio bell tolls, candlelit DOM/CSS scenes.</p>
  </div>
  <div class="card">
    <h3>client/src/net/ — the p2p layer</h3>
    <p>js-libp2p assembly copied from neon-grid: relay bootstrap (identity
    fetched from <code>/.well-known/ap2p</code> with a compiled-in fallback),
    room crypto, the sealed-hello membership gate, and one long-lived
    varint-framed stream per pair. Only the protocol id is game-specific.</p>
  </div>
  <div class="card">
    <h3>deploy/ — the publish pipeline</h3>
    <p><code>publish-repo.sh</code> builds this very site: landing page + bare
    mirror exploded to loose objects (static file servers and git's dumb-HTTP protocol
    agree on "every path must exist"), with an anonymity gate that refuses to
    publish identity-bearing bytes.</p>
  </div>

  <h2>Run it yourself</h2>
  <pre><code>cd client && npm install && npm run dev   # :4173
# open two tabs on the same #room URL and haunt yourself (keys 1/2/3 pick rooms)</code></pre>
  <p>Deploy: <code>npm run build</code>, then publish <code>client/dist/</code> as
  static files anywhere — IPFS, a CDN, a folder behind nginx. It needs nothing but
  a signaling relay it's allowed to talk to (see <code>src/game/config.ts</code>).</p>

  <h2>Guarantees &amp; limits, honestly</h2>
  <ul>
    <li>The relay operator sees connection metadata, peer ids, and (until the
      WebRTC upgrade lands) opaque noise ciphertext and its sizes/timing. Not
      content, not names, not who the ghost took.</li>
    <li>Anyone with the room link can join and play — capability model. The
      16-guest cap is enforced by the conductor (the mesh is O(n²); ~12 is the
      practical ceiling).</li>
    <li>The conductor is one of the players; a cheating conductor could load the
      ghost's dice. Between friends that's the same trust as letting someone
      shuffle the deck. Other players' picks can't be forged.</li>
    <li>Your tapped room is taken on faith — the game can't see your feet.
      That's a feature: the sprint to the cellar is the game.</li>
    <li>MIT licensed. Built on js-libp2p, WebRTC and WebCrypto.</li>
  </ul>

  <footer>
    Live game: <a href="https://game4.ardegazu.ro/">game4.ardegazu.ro</a> ·
    this repo: <code>git clone https://git.ardegazu.ro/game4.git</code> ·
    p2p core from <a href="https://git.ardegazu.ro/chat/">sueta</a>
  </footer>
</main>

<main data-lang-block lang="ro" hidden>
  <h1>SÉANCE</h1>
  <p class="tag">un joc de salon bântuit pentru o casă, șaisprezece telefoane și trei
  încăperi adevărate — pagina asta e și repo-ul git.</p>

  <div class="btns">
    <a class="btn" href="https://game4.ardegazu.ro/">🕯 ține o ședință</a>
    <a class="btn" href="browse/index.html">răsfoiește codul</a>
    <a class="btn" href="#clone-ro">clonează repo-ul</a>
  </div>

  <h2 id="clone-ro">Clonare</h2>
  <p>Situl servește repository-ul prin protocolul dumb-HTTP al lui git, ca simple
  fișiere statice (chiar simple fișiere — nu există aici niciun server git, de fapt
  niciun server):</p>
  <pre><code>git clone https://git.ardegazu.ro/game4.git
cd seance</code></pre>

  <h2>Ce este</h2>
  <p>Un joc de petrecere pe care îl joci cu picioarele. Adună 1–16 oameni într-o
  casă, stabiliți care încăpere e <em>Biroul</em>, care <em>Salonul</em> și care
  <em>Pivnița</em>, și deschideți același link pe fiecare telefon. De fiecare dată
  când bate clopotul ai ~20 de secunde să mergi fizic într-o încăpere și să o
  atingi. O fantomă mătură o încăpere — și o atrage locul unde a stat mulțimea la
  clopotul trecut. Toți cei prinși pierd o lumânare; le pierzi pe toate trei și te
  stingi. Ultima lumânare aprinsă câștigă ședința.</p>
  <p>La fiecare clopot, un jucător e în secret <strong>mediumul</strong>: doar
  telefonul lui numește o încăpere pe care spiritul o va ocoli — adevărat. Poate
  să spună, să tacă sau să mintă cu voce tare. Unde stai <em>de fapt</em> rămâne
  pe cuvânt de onoare; mersul e miza, nu controlul. PWA instalabil, merge de pe
  ecranul de start al unui iPhone.</p>

  <h2>Cum funcționează</h2>
  <ul>
    <li><strong>Fără server de joc.</strong> Browserele sunt peers
      <a href="https://libp2p.io">js-libp2p</a>: sună un peer circuit-relay-v2
      comun prin WSS, se descoperă prin gossipsub și trec pe WebRTC direct —
      circuitul e semnalizare plus o cale de rezervă cu limită de trafic.
      Secretul camerei stă în fragmentul URL-ului (<code>#…</code>) și nu pleacă
      niciodată din browser.</li>
    <li><strong>Camerele sunt criptografice.</strong> Descoperirea e la nivel de
      aplicație, așa că o cameră e impusă printr-un id de protocol derivat din
      cameră (<code>/seance/2/&lt;HKDF(secret)&gt;</code> — camerele greșite pică
      la negocierea protocolului) plus un hello sigilat reciproc: un plic
      AES-256-GCM cu cheia din secretul camerei, legat de ambele peer id-uri
      autentificate prin noise. După hello, cadrele fiecărei perechi merg pe
      criptarea noise a lui libp2p, cap-la-cap chiar și prin releu. Stratul p2p
      (<code>client/src/net/</code>) e copiat din
      <a href="https://git.ardegazu.ro/game1/">neon-grid</a>.</li>
    <li><strong>Un peer conduce.</strong> Cel mai mic peer id ține ceasul
      clopotelor și zarurile fantomei, strânge alegerile secrete de încăperi și
      difuzează cadre de fază minuscule; restul doar randează. Ședințele sunt
      indexate pe scaune, așa că oaspeții care vin sau pleacă nu pot corupe una
      în desfășurare. Dacă telefonul maestrului moare, ședința se anulează și
      preia următorul id; cine intră târziu primește un snapshot și privește de
      dincolo de văl până la ședința următoare.</li>
    <li><strong>Găzduire fără server de origine.</strong> Clientul e un build Vite
      static servit ca simple fișiere — nu există backend nicăieri. Pagina pe care
      o citești funcționează la fel.</li>
  </ul>

  <h2>Ce e în repo</h2>
  <div class="card">
    <h3>client/src/game/ — jocul</h3>
    <p>TypeScript, fără framework: protocol de rețea tipat, logică de ședință
    autoritativă la gazdă (clopote, fantomă atrasă de aglomerație, șoapte pentru
    medium, eliminare pe lumânări), dangăte WebAudio, scene DOM/CSS la lumina
    lumânării.</p>
  </div>
  <div class="card">
    <h3>client/src/net/ — stratul p2p</h3>
    <p>Asamblaj js-libp2p copiat din neon-grid: bootstrap de releu (identitatea
    citită din <code>/.well-known/ap2p</code> cu rezervă compilată), criptografia
    camerei, poarta de apartenență cu hello sigilat, și un singur stream
    persistent cu cadre varint per pereche. Doar id-ul de protocol e specific
    jocului.</p>
  </div>
  <div class="card">
    <h3>deploy/ — conducta de publicare</h3>
    <p><code>publish-repo.sh</code> construiește chiar situl ăsta: pagină de
    prezentare + oglindă bare explodată în loose objects (serverele de fișiere
    statice și dumb-HTTP-ul lui git cad de acord că „orice cale trebuie să
    existe"), cu o poartă de anonimat care refuză să publice octeți care poartă
    identitate.</p>
  </div>

  <h2>Rulează-l singur</h2>
  <pre><code>cd client && npm install && npm run dev   # :4173
# deschide două taburi pe același URL #cameră și bântuie-te singur (tastele 1/2/3 aleg încăperile)</code></pre>
  <p>Deploy: <code>npm run build</code>, apoi publică <code>client/dist/</code> ca
  fișiere statice oriunde — IPFS, un CDN, un director în spatele lui nginx. N-are
  nevoie decât de un releu de semnalizare cu care are voie să vorbească (vezi
  <code>src/game/config.ts</code>).</p>

  <h2>Garanții și limite, pe bune</h2>
  <ul>
    <li>Operatorul releului vede metadate de conexiune, peer id-uri și (până se
      face trecerea pe WebRTC) ciphertext noise opac cu dimensiunile și ritmul
      lui. Nu conținut, nu nume, nu pe cine a luat fantoma.</li>
    <li>Oricine are linkul camerei poate intra și juca — model de capabilitate.
      Plafonul de 16 oaspeți îl impune maestrul (mesh-ul e O(n²); ~12 e plafonul
      practic).</li>
    <li>Maestrul e unul dintre jucători; un maestru care trișează poate măslui
      zarurile fantomei. Între prieteni e aceeași încredere ca atunci când lași
      pe cineva să amestece cărțile. Alegerile celorlalți nu pot fi falsificate.</li>
    <li>Încăperea atinsă e luată pe cuvânt — jocul nu-ți vede picioarele. E o
      calitate: sprintul spre pivniță e chiar jocul.</li>
    <li>Licență MIT. Construit pe js-libp2p, WebRTC și WebCrypto.</li>
  </ul>

  <footer>
    Jocul live: <a href="https://game4.ardegazu.ro/">game4.ardegazu.ro</a> ·
    repo-ul: <code>git clone https://git.ardegazu.ro/game4.git</code> ·
    nucleul p2p din <a href="https://git.ardegazu.ro/chat/">sueta</a>
  </footer>
</main>

<main data-lang-block lang="hu" hidden>
  <h1>SÉANCE</h1>
  <p class="tag">kísértetjárta szalonjáték egy házra, tizenhat telefonra és három
  igazi szobára — ez az oldal egyben a git repó is.</p>

  <div class="btns">
    <a class="btn" href="https://game4.ardegazu.ro/">🕯 tarts szeánszot</a>
    <a class="btn" href="browse/index.html">böngészd a kódot</a>
    <a class="btn" href="#clone-hu">repó klónozása</a>
  </div>

  <h2 id="clone-hu">Klónozás</h2>
  <p>Az oldal a repót a git dumb-HTTP protokollján át szolgálja ki, sima statikus
  fájlokként (tényleg sima fájlok — nincs itt git szerver, sőt semmilyen szerver):</p>
  <pre><code>git clone https://git.ardegazu.ro/game4.git
cd seance</code></pre>

  <h2>Mi ez</h2>
  <p>Egy partijáték, amit a lábaddal játszol. Gyűjts össze 1–16 embert egy házban,
  beszéljétek meg, melyik igazi szoba <em>a dolgozószoba</em>, melyik <em>a
  szalon</em> és melyik <em>a pince</em>, aztán nyissátok meg ugyanazt a linket
  minden telefonon. Ahányszor megkondul a harang, ~20 másodperced van ténylegesen
  átsétálni egy szobába és rákoppintani. Egy szellem végigsöpör egy szobán — és
  oda húz, ahol a tömeg állt az előző harangszónál. Akit elkap, elveszít egy
  gyertyát; ha mindhármat elveszted, elenyészel. Az utolsó égő gyertya nyeri a
  szeánszot.</p>
  <p>Minden harangszónál egy játékos titokban a <strong>médium</strong>: egyedül
  az ő telefonja nevez meg egy szobát, amit a szellem elkerül — igazat mondva.
  Elmondhatja, magában tarthatja, vagy hazudhat róla fennhangon. Hogy
  <em>valójában</em> hol állsz, becsületbeli ügy; a séta a lényeg, nem az
  ellenőrzés. Telepíthető PWA, iOS kezdőképernyőről is megy.</p>

  <h2>Hogyan működik</h2>
  <ul>
    <li><strong>Nincs játékszerver.</strong> A böngészők
      <a href="https://libp2p.io">js-libp2p</a> peerek: egy közös circuit-relay-v2
      peert hívnak WSS-en, gossipsubbal fedezik fel egymást, majd közvetlen
      WebRTC-re váltanak — a circuit jelzésre való, plusz egy korlátozott sávú
      tartalék útvonal. A szoba titka az URL fragmentben él (<code>#…</code>),
      és soha nem hagyja el a böngészőt.</li>
    <li><strong>A szobák kriptográfiaiak.</strong> A felfedezés app-szintű, ezért
      a szobát egy szoba-hatókörű protokoll-id kényszeríti ki
      (<code>/seance/2/&lt;HKDF(secret)&gt;</code> — rossz szobával megbukik a
      protokoll-egyeztetés), plusz egy kölcsönös lepecsételt hello: egyetlen
      AES-256-GCM boríték a szoba titkából származtatott kulccsal, mindkét
      noise-hitelesített peer id-hez kötve. A hello után minden pár keretei a
      libp2p noise-titkosításán mennek, végponttól végpontig, a relén át is. A
      p2p réteg (<code>client/src/net/</code>) a
      <a href="https://git.ardegazu.ro/game1/">neon-grid</a>ből van másolva.</li>
    <li><strong>Az egyik peer vezényel.</strong> A legkisebb peer id kezeli a
      harang időzítőit és a szellem kockáit, begyűjti a titkos szobaválasztásokat,
      és apró fázis-kereteket sugároz; a többiek ezekből rajzolnak. A szeánszok
      szék-indexeltek, így az érkező-távozó vendégek nem ronthatnak el egy
      folyamatban lévőt. Ha a mester telefonja kimúlik, a szeánsz érvénytelen, és
      a következő legkisebb id veszi át; a későn érkezők pillanatképet kapnak, és
      a fátyol mögül néznek a következő szeánszig.</li>
    <li><strong>Hosztolás origin-szerver nélkül.</strong> A kliens statikus Vite
      build, sima fájlokként kiszolgálva — nincs sehol backend. Ez az oldal is
      pontosan így működik.</li>
  </ul>

  <h2>Mi van a repóban</h2>
  <div class="card">
    <h3>client/src/game/ — a játék</h3>
    <p>TypeScript, framework nélkül: típusos wire-protokoll, hoszt-mérvadó
    szeánsz-logika (harangok, tömegre éhes szellem, médium-súgások,
    gyertya-kiesés), WebAudio harangkondulás, gyertyafényes DOM/CSS jelenetek.</p>
  </div>
  <div class="card">
    <h3>client/src/net/ — a p2p réteg</h3>
    <p>A neon-gridből másolt js-libp2p összeállítás: relé-bootstrap (az identitás
    a <code>/.well-known/ap2p</code>-ből jön, beforgatott tartalékkal),
    szoba-kriptográfia, a lepecsételt hello tagsági kapu, és páronként egyetlen
    hosszú életű, varint-keretezett stream. Csak a protokoll-id játékspecifikus.</p>
  </div>
  <div class="card">
    <h3>deploy/ — a publikáló futószalag</h3>
    <p>A <code>publish-repo.sh</code> építi ezt az oldalt: nyitólap + bare tükör
    loose objectekre bontva (a statikus fájlszerverek és a git dumb-HTTP-je
    egyetértenek abban, hogy „minden útvonalnak léteznie kell"), és egy
    anonimitás-kapu, amely nem enged identitáshordozó bájtokat publikálni.</p>
  </div>

  <h2>Futtasd magad</h2>
  <pre><code>cd client && npm install && npm run dev   # :4173
# nyiss két fület ugyanarra a #szoba URL-re, és kísértsd magad (az 1/2/3 billentyű szobát választ)</code></pre>
  <p>Telepítés: <code>npm run build</code>, aztán tedd ki a
  <code>client/dist/</code>-et statikus fájlokként bárhová — IPFS, CDN, egy mappa
  nginx mögött. Semmi másra nincs szüksége, csak egy jelző-relére, amivel
  beszélhet (lásd <code>src/game/config.ts</code>).</p>

  <h2>Garanciák és korlátok, őszintén</h2>
  <ul>
    <li>A relé üzemeltetője kapcsolati metaadatokat, peer id-kat és (amíg a
      WebRTC-váltás meg nem történik) átlátszatlan noise-ciphertextet lát, annak
      méretével és ütemével. Tartalmat, neveket, a szellem áldozatát nem.</li>
    <li>Bárki, akinél ott a szoba linkje, beléphet és játszhat — capability
      modell. A 16 vendéges plafont a mester tartatja be (a mesh O(n²); ~12 a
      gyakorlati plafon).</li>
    <li>A mester az egyik játékos; egy csaló mester cinkelheti a szellem
      kockáit. Barátok közt ez ugyanaz a bizalom, mint amikor hagyod, hogy valaki
      megkeverje a paklit. A többiek választásait nem lehet hamisítani.</li>
    <li>A rákoppintott szobát elhisszük — a játék nem látja a lábadat. Ez nem
      hiba: a sprint a pincéig maga a játék.</li>
    <li>MIT licenc. js-libp2p-re, WebRTC-re és WebCryptóra épül.</li>
  </ul>

  <footer>
    Az élő játék: <a href="https://game4.ardegazu.ro/">game4.ardegazu.ro</a> ·
    a repó: <code>git clone https://git.ardegazu.ro/game4.git</code> ·
    a p2p mag a <a href="https://git.ardegazu.ro/chat/">suetá</a>ból
  </footer>
</main>

<script>
(function () {
  var LANGS = ["en", "ro", "hu"];
  var KEY = "ardegazu-site:lang";
  var lang = null;
  try { lang = localStorage.getItem(KEY); } catch (e) {}
  if (LANGS.indexOf(lang) < 0) {
    var cands = navigator.languages || [navigator.language];
    for (var i = 0; i < cands.length; i++) {
      var p = (cands[i] || "").slice(0, 2).toLowerCase();
      if (LANGS.indexOf(p) >= 0) { lang = p; break; }
    }
  }
  if (LANGS.indexOf(lang) < 0) lang = "en";

  var nav = document.getElementById("langs");
  function apply(l) {
    lang = l;
    document.documentElement.lang = l;
    try { localStorage.setItem(KEY, l); } catch (e) {}
    var blocks = document.querySelectorAll("[data-lang-block]");
    for (var i = 0; i < blocks.length; i++) blocks[i].hidden = blocks[i].getAttribute("lang") !== l;
    var btns = nav.querySelectorAll("button");
    for (var j = 0; j < btns.length; j++) btns[j].className = btns[j].textContent === l ? "on" : "";
  }
  LANGS.forEach(function (l, i) {
    if (i) nav.appendChild(document.createTextNode(" · "));
    var b = document.createElement("button");
    b.type = "button";
    b.textContent = l;
    b.addEventListener("click", function () { apply(l); });
    nav.appendChild(b);
  });
  apply(lang);
})();
</script>
</body>
</html>

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/game4.git