website: the language switch becomes a globe like the dashboard's (Magyar / English list, current marked, no JS); site.css v3; gate 13 checks the globe from one LANGS list; 5 decoys
gates / gates (push) Successful in 3m54s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-08 09:13:23 +02:00
parent c7aade4079
commit 803af4798d
26 changed files with 227 additions and 53 deletions
+60 -15
View File
@@ -8,7 +8,7 @@ Gates (all must pass; non-zero exit on any failure):
Windows grep false-negatives multibyte emoji, proven in D0)
3. nav — the <nav>…</nav> and <footer>…</footer> blocks are identical PER LANGUAGE SET
(Hungarian pages against index.html, English pages against en/index.html) after
stripping the active-link marker and the language switch's target
stripping the active-link marker and the language globe (gate 13 checks it)
4. analytics — the umami snippet is present on every public page (nonpublic draft exempt)
5. no-CDN — zero fonts.googleapis.com / fonts.gstatic.com references
6. banned — zero legacy hexes / 999px radius / box-shadow in pages + site.css
@@ -16,7 +16,9 @@ Gates (all must pass; non-zero exit on any failure):
8. cachebust — every site.css / icons.svg reference carries ?v=<N>
12. download — the two download pages name the same installer file and checksum (R-559)
13. twins — every public Hungarian page has its English twin and back; each pair carries the same
hu/en/x-default alternates; each page's language switch points at its own twin
hu/en/x-default alternates; each page's language GLOBE (one per page, every page) lists LANGS in
order, marks only the page's own language current, and points the other items at the twins;
no leftover text switch
14. lang — <html lang="hu"> on the Hungarian set, lang="en" on the English set
15. same apps — the app logos on /alkalmazasok and /en/apps are the same set
16. no HU — no Hungarian in the English pages: accent scan PLUS an ASCII-folded, word-bounded stem
@@ -87,15 +89,21 @@ def is_emoji(ch):
])
SWITCH_RE = re.compile(r'<a href="([^"]*)" lang="(hu|en)" hreflang="(hu|en)" class="lang-switch">([^<]*)</a>')
# The language globe (2026-10-08 ruling, 10-localisation.md §10.7): one <details> per page, its list built from THIS
# list — a third language is one more entry here plus its pages, and nothing else in the switch.
LANGS = [("hu", "Magyar"), ("en", "English")]
GLOBE_LABEL = {"hu": "Nyelv", "en": "Language"}
GLOBE_RE = re.compile(r'<details class="lang-globe">.*?</details>', re.S)
GLOBE_ITEM_RE = re.compile(r'<a href="([^"]*)" lang="([^"]*)" hreflang="([^"]*)" class="lang-globe-item( current)?"'
r'( aria-current="true")?>([^<]*)</a>')
def norm_block(b):
b = b.replace(' class="active"', "")
b = re.sub(r'\s*aria-current="[^"]*"', "", b)
# the language switch is the one nav item whose target differs per page (it points at the page's own
# twin) — gate 13 checks that target; here only its presence and wording must match
b = SWITCH_RE.sub(lambda m: '<a lang-switch="%s">%s</a>' % (m.group(2), m.group(4)), b)
# the globe is the one nav item whose content differs per page (its targets, its current mark, its label) —
# gate 13 checks all of that; here only its presence must match
b = GLOBE_RE.sub('<details class="lang-globe"/>', b)
return b
@@ -225,7 +233,43 @@ for p, s in pages.items():
if not m.group(2).startswith("?v="):
fail("%s: %s referenced without ?v= (got %r)" % (p, m.group(1), m.group(0)))
# gate 13: twins — both ways, matching alternates, the switch at the twin
# gate 13: twins — both ways, matching alternates, the globe pointing at the twins
def lang_of(p):
return "en" if p in EN_PAGES else "hu"
def check_globe(p, lang, urls=None):
"""Exactly one globe; its label in the page's language; one item per LANGS entry, in order, lang/hreflang equal to
the entry; the page's own language is the only current/aria-current item; with `urls`, each item's target."""
s = pages[p]
g = GLOBE_RE.findall(s)
if len(g) != 1:
fail("%s: %d language globes in the page — exactly one, in the nav" % (p, len(g)))
return
lab = re.search(r'<summary class="lang-globe-btn" aria-label="([^"]*)" title="([^"]*)">', g[0])
if not lab or lab.group(1) != GLOBE_LABEL[lang] or lab.group(2) != GLOBE_LABEL[lang]:
fail("%s: the globe's label is %r — on a %s page it is %r" % (p, lab and lab.groups(), lang, GLOBE_LABEL[lang]))
items = GLOBE_ITEM_RE.findall(g[0])
if len(re.findall(r"<a ", g[0])) != len(items):
fail("%s: the globe holds a link the gate cannot read — every item is <a href lang hreflang class=lang-globe-item>" % p)
got = [(it[1], it[5]) for it in items]
if got != LANGS:
fail("%s: the globe lists %r — every page lists exactly %r, in that order" % (p, got, LANGS))
return
for href, code, hreflang, cur, aria, name in items:
if hreflang != code:
fail("%s: the globe's %s item has hreflang=%r" % (p, code, hreflang))
is_cur = code == lang
if bool(cur) != is_cur or bool(aria) != is_cur:
fail("%s: the globe marks %r as current (class current=%s, aria-current=%s) — only the page's own language %r is"
% (p, code, bool(cur), bool(aria), lang))
if urls is not None and href != urls[code]:
fail("%s: the globe's %s item points at %r — %s" % (p, code, href,
"the page itself is %r" % urls[code] if is_cur else "its twin is %r" % urls[code]))
ALT_RE = re.compile(r'<link rel="alternate" hreflang="([^"]+)" href="([^"]+)">')
for hu, en in TWINS.items():
if hu not in pages or en not in pages:
@@ -236,16 +280,17 @@ for hu, en in TWINS.items():
got = dict(ALT_RE.findall(pages[p]))
if got != want:
fail("%s: hreflang alternates %s — the pair %s <-> %s needs exactly %s" % (p, got, hu, en, want))
sw = SWITCH_RE.findall(pages[p])
if len(sw) != 1:
fail("%s: %d language switch links in the page — exactly one, in the nav" % (p, len(sw)))
elif sw[0][0] != URL[other] or sw[0][1] != lang or sw[0][2] != lang:
fail("%s: the language switch points at %r (lang=%s) — its twin is %r (lang=%s)"
% (p, sw[0][0], sw[0][1], URL[other], lang))
check_globe(p, lang_of(p), {"hu": URL[hu], "en": URL[en]})
canon = re.search(r'<link rel="canonical" href="([^"]+)">', pages[p])
if not canon or canon.group(1) != SITE + URL[p]:
fail("%s: canonical %r — it must point at the page itself (%s)" % (p, canon and canon.group(1), SITE + URL[p]))
print(" twins: %d pairs checked both ways (hreflang, switch, canonical)" % len(TWINS))
print(" twins: %d pairs checked both ways (hreflang, globe, canonical)" % len(TWINS))
for p in sorted(NO_TWIN): # no twin: the globe's structure, current mark and label still hold
if p in pages:
check_globe(p, lang_of(p))
for p, s in pages.items():
if 'class="lang-switch"' in s:
fail("%s: a leftover text language switch (class=\"lang-switch\") — the globe replaced it" % p)
# gate 14: the lang attribute
for p, s in pages.items():
@@ -398,4 +443,4 @@ if fails:
print("\nSITE GATES FAILED: %d problem(s)" % len(fails))
sys.exit(1)
print("site gates OK — BOM, emoji=0, nav/footer per language, analytics, no CDN, no legacy tokens, no <style>, "
"cache-busted assets, twins + hreflang + switch, lang, same apps, no Hungarian in English, FAQ JSON-LD, clean tail")
"cache-busted assets, twins + hreflang + globe, lang, same apps, no Hungarian in English, FAQ JSON-LD, clean tail")