website: M3 part C - all 18 remaining pages moved into the shell
gates / gates (push) Successful in 8m50s

Staged under /preview/m3/. Nothing live. 19 shell pages in total with the home.

Each page keeps its own <head> byte-for-byte apart from two additions (shell.css
and the no-js/js hook), so title, meta description, canonical, hreflang, Open Graph
and JSON-LD all survive the move untouched. The body loses the website's
<nav>/<footer> and the old hamburger script, and gains the shell.

PROVED, not assumed - a word-level diff of every page's visible text, live vs
shell, with the nav and footer removed from both:
  * 18 of 18 pages: visible text IDENTICAL after the one allowed substitution
  * adatkezeles and feltetelek: identical with NO substitution permitted at all.
    The legal texts are reviewed documents; the frame changed, the words did not.

That check earned itself immediately. The first version of the page-header
transform rebuilt the block from its <h1> and first <p> and SILENTLY DROPPED the
rest: the "Fontos tudnivalok" / "Good to know" disclaimer banner on both apps
pages and the 404's English line - 168 words across three pages. Nothing looked
wrong on the page. The diff found it.

Em dashes: 318 replaced with the Hungarian gondolatjel across 16 pages (gyik and
en/faq 104 each). The FAQ pages' JSON-LD is swept with the SAME substitution, so
gate 17's equality with the visible answers still holds - it does, on both.

scripts/site_gates.py:
  * SHELL now holds all 19 pages.
  * is_en(): a page is English if it is a TWINS value OR lives under an en/
    directory. Without the second half the dash-language, <html lang> and
    no-Hungarian gates all judged preview/m3/en/* as Hungarian - 30 false fails.
  * norm_block() also drops `is-open` and `aria-expanded`, because the shell
    renders the nav group containing the active page open, exactly as the
    controller does, and that legitimately differs per page.

The last change LOOSENS a check, so it was red-proofed BOTH ways: a one-character
difference in one menu label still convicts, and a state-only difference (the
active marker moving to another item) correctly does not.

One generator now owns the shell. The home page's content comes from m3.py and its
shell from the same function every other page uses, so the two cannot drift - which
they already had, and gate 3b caught it.
This commit is contained in:
2026-10-11 14:14:37 +02:00
parent 698ec9b561
commit 0b34be8449
20 changed files with 10696 additions and 30 deletions
+549
View File
@@ -0,0 +1,549 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<script>document.documentElement.className=document.documentElement.className.replace('no-js','js');</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Technology — Docker, the Dashboard and Proxmox for a home server | Felhom.eu</title>
<meta name="description" content="Industry-grade technology at home, in Hungary: Docker containers, a Dashboard in English or Hungarian, Proxmox virtualisation. HTTPS and an encrypted tunnel — with no open port.">
<meta name="keywords" content="Docker, dashboard, Proxmox, container, virtualisation, reverse proxy, HTTPS, encrypted tunnel, home server technology">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://felhom.eu/en/technology">
<link rel="alternate" hreflang="hu" href="https://felhom.eu/technologiak">
<link rel="alternate" hreflang="en" href="https://felhom.eu/en/technology">
<link rel="alternate" hreflang="x-default" href="https://felhom.eu/technologiak">
<meta property="og:type" content="article">
<meta property="og:url" content="https://felhom.eu/en/technology">
<meta property="og:title" content="Professional technology for a home server">
<meta property="og:description" content="Docker, a Dashboard in English or Hungarian, Proxmox — industry-grade tools at home.">
<meta property="og:image" content="https://felhom.eu/assets/og-image-en.png">
<meta property="og:locale" content="en_GB">
<meta property="og:site_name" content="Felhom.eu">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Professional technology for a home server">
<meta name="twitter:description" content="Docker, a Dashboard in English or Hungarian, Proxmox — industry tools, at home.">
<meta name="twitter:image" content="https://felhom.eu/assets/og-image-en.png">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "The technology behind a Felhom home server",
"description": "Docker containers, a Dashboard in English or Hungarian and Proxmox virtualisation for a home server.",
"author": {"@type": "Organization", "name": "Felhom.eu"},
"publisher": {"@type": "Organization", "name": "Felhom.eu", "logo": {"@type": "ImageObject", "url": "https://felhom.eu/assets/logo.png"}},
"mainEntityOfPage": "https://felhom.eu/en/technology"
}
</script>
<!-- shared assets: bump ?v= together on any site.css/icons.svg change -->
<link rel="stylesheet" href="/assets/site.css?v=19">
<link rel="stylesheet" href="/assets/shell.css?v=19">
<link rel="icon" type="image/png" href="/assets/logo_favicon_2.svg">
<script defer src="/assets/gallery.js?v=2"></script>
<script defer src="https://stats.felhom.eu/script.js" data-website-id="d419db57-5e1c-48df-bdb3-c29f25fb1661"></script>
</head>
<body class="shell page-technologiak">
<header class="mobile-topbar">
<a href="/preview/m3/en/" class="mobile-topbar-logo"><img src="/assets/logo_notext_white.svg" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" id="navBurger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
<span class="hamburger-box"><span class="hamburger-line"></span><span class="hamburger-line"></span><span class="hamburger-line"></span></span>
</button>
</header>
<div class="nav-backdrop" id="navBackdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<a href="/preview/m3/en/" class="sidebar-logo-link">
<img src="/assets/logo_notext_white.svg" alt="" class="sidebar-logo" aria-hidden="true">
<img src="/assets/logo-wordmark.png" alt="felhom.eu" class="sidebar-word">
</a>
</div>
<ul class="nav-links">
<li><a href="/preview/m3/en/"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-home"/></svg>Home</a></li>
<li><a href="/preview/m3/en/#services"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-rocket"/></svg>Services</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-apps"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg>Applications<svg class="ico nav-chevron" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-chevron-down"/></svg></button>
<ul id="nav-group-apps" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/preview/m3/en/apps">All applications</a></li>
<li><a href="/preview/m3/en/apps#files-office">Files &amp; Office</a></li>
<li><a href="/preview/m3/en/apps#media-entertainment">Media &amp; Entertainment</a></li>
<li><a href="/preview/m3/en/apps#home-lifestyle">Home &amp; Lifestyle</a></li>
<li><a href="/preview/m3/en/apps#productivity-knowledge">Productivity &amp; Knowledge</a></li>
<li><a href="/preview/m3/en/apps#system-development">System &amp; Development</a></li>
</ul>
</li>
<li><a href="/preview/m3/en/technology" class="active"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-layers"/></svg>Technology</a></li>
<li><a href="/preview/m3/en/backups"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-shield-check"/></svg>Backups</a></li>
</ul>
<div class="sidebar-bottom">
<a href="/preview/m3/en/testing" class="btn btn-outline sidebar-cta">Apply as a tester →</a>
<div class="nav-group-label">About Felhom</div>
<ul class="nav-links nav-links-sub">
<li><a href="/preview/m3/en/#how-it-works">How it works</a></li>
<li><a href="/preview/m3/en/faq">FAQ</a></li>
<li><a href="/preview/m3/en/contact">Contact</a></li>
</ul>
<div class="sidebar-footer">
<details class="lang-globe"><summary class="lang-globe-btn" aria-label="Language" title="Language"><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-globe"/></svg></summary><ul class="lang-globe-menu"><li><a href="/preview/m3/technologiak" lang="hu" hreflang="hu" class="lang-globe-item">Magyar</a></li><li><a href="/preview/m3/en/technology" lang="en" hreflang="en" class="lang-globe-item current" aria-current="true">English</a></li></ul></details>
</div>
</div>
</nav>
<main class="content">
<div class="page-header">
<h1>The <span>technology</span></h1>
</div>
<p class="page-lede">Modern, proven tools for running a home server. This is how we build the system.</p>
<!-- Table of Contents -->
<section class="content-section" style="padding-top: 0;">
<div class="container">
<div class="toc-nav">
<h3>Contents</h3>
<ul class="toc-links">
<li><a href="#containers">Why containers?</a></li>
<li><a href="#dashboard">The control layer (Dashboard)</a></li>
<li><a href="#proxmox">Virtualisation (Proxmox)</a></li>
<li><a href="#network">Network and security</a></li>
<li><a href="#storage">Storage</a></li>
<li><a href="#hardware">Hardware set-ups</a></li>
</ul>
</div>
</div>
</section>
<!-- Miért konténerek? -->
<section class="content-section alt-bg" id="containers">
<div class="container">
<div class="section-header">
<h2>Why <span>containers?</span></h2>
<p>The cornerstone of running a modern server</p>
</div>
<div class="info-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg></span> What is a container?</h3>
<p>Imagine that every app runs in its own sealed "box". The box holds the app and everything it depends on – so it works the same way anywhere, on any server.</p>
<p>Containers (Docker) changed how servers are run: simpler installs, easier updates, and more reliable operation.</p>
<div class="highlight-box">
<p><strong>An analogy:</strong> as if every app lived in its own flat in a block of flats. It has its own front door and its own meters, and it does not bother the neighbours – but they still share one building.</p>
</div>
</div>
<div class="comparison-grid">
<div class="comparison-card">
<div class="card-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-monitor"/></svg></div>
<h3>Traditional install</h3>
<div class="subtitle">"Straight onto the system"</div>
<p>The apps are installed straight onto the operating system. They can clash with each other, and they are hard to remove.</p>
<ul class="feature-list cons">
<li>Dependencies can get tangled</li>
<li>Hard to reproduce on another server</li>
<li>Update = risk</li>
<li>Removing an app leaves "rubbish" behind</li>
</ul>
</div>
<div class="comparison-card recommended">
<div class="card-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-container"/></svg></div>
<h3>Container install</h3>
<div class="subtitle">Docker</div>
<p>Every app runs on its own. A clean, tidy system that is easy to manage.</p>
<ul class="feature-list">
<li>Fully isolated apps</li>
<li>One command = installed</li>
<li>Simple updates, and a simple way back</li>
<li>Reproducible anywhere</li>
</ul>
</div>
</div>
</div>
</section>
<!-- Single Node - vezérlőpult -->
<section class="content-section" id="dashboard">
<div class="container">
<div class="section-header">
<h2>The control layer: <span>the Dashboard</span></h2>
<p>The interface of every Felhom box, in English or Hungarian</p>
</div>
<div class="info-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-target"/></svg></span> What does the Dashboard do?</h3>
<p>The Dashboard is on every Felhom box: it is the interface, in English or Hungarian, that runs your system on its Proxmox base. It was designed for home servers: you install apps in a few clicks and never touch a command line – Docker and Proxmox do the work in the background.</p>
<ul class="feature-list">
<li>A web interface in English or Hungarian – the household chooses, and no command-line knowledge is needed</li>
<li>App catalog – 58 apps to install in a few clicks</li>
<li>Resource view – memory and storage use as it happens</li>
<li>Automatic backups – databases and files, with their status watched</li>
<li>App updates – only versions we have tried, a fresh backup first, and if the new version does not start, the box goes back to the previous one by itself</li>
<li>System monitoring – health watched all the time, with alerts</li>
</ul>
<div class="dash-grid">
<figure class="dash-shot">
<a href="/assets/dashboard-apps-en.webp" data-gallery="dashboard"><img src="/assets/dashboard-apps-en.webp" alt="The Apps page: cards of installed and installable apps, with their status" width="1440" height="900" loading="lazy"></a>
<figcaption><strong>Apps</strong> – install in a few clicks, and see what runs and what is up to date.</figcaption>
</figure>
<figure class="dash-shot">
<a href="/assets/dashboard-backups-en.webp" data-gallery="dashboard"><img src="/assets/dashboard-backups-en.webp" alt="The backups page: each app's database backed up overnight, and checked" width="1440" height="900" loading="lazy"></a>
<figcaption><strong>Backups</strong> – every night each app's database is backed up and checked.</figcaption>
</figure>
</div>
<div class="highlight-box">
<p><strong>Good for:</strong> a home media server, a family cloud, personal projects, a small office.</p>
</div>
</div>
<div class="stack-diagram">
<h3>How a Felhom box is built</h3>
<div class="stack-layers">
<div class="stack-layer">
<div class="layer-label">Control + apps</div>
<div class="layer-content highlight">
<div class="layer-name">Dashboard + Apps</div>
<div class="layer-tech">An interface in English or Hungarian · Nextcloud, Jellyfin, Immich…</div>
</div>
</div>
<div class="stack-layer">
<div class="layer-label">Container engine</div>
<div class="layer-content">
<div class="layer-name">Docker</div>
<div class="layer-tech">The apps run in isolated containers</div>
</div>
</div>
<div class="stack-layer">
<div class="layer-label">System environment</div>
<div class="layer-content">
<div class="layer-name">Isolated system environment</div>
<div class="layer-tech">Your box – a cleanly separated system</div>
</div>
</div>
<div class="stack-layer">
<div class="layer-label">Base layer</div>
<div class="layer-content">
<div class="layer-name">Proxmox VE</div>
<div class="layer-tech">Virtualisation base · backing up the whole box, restoring</div>
</div>
</div>
<div class="stack-layer">
<div class="layer-label">Hardware</div>
<div class="layer-content">
<div class="layer-name">The server machine</div>
<div class="layer-tech">Mini PC, tower or server</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Proxmox -->
<section class="content-section alt-bg" id="proxmox">
<div class="container">
<div class="section-header">
<h2>The base layer: <span>Proxmox</span></h2>
<p>Every Felhom machine is built on this stable base</p>
</div>
<div class="info-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-layers"/></svg></span> What is Proxmox?</h3>
<p>Proxmox VE is a free, professional virtualisation platform – the same kind of technology data centres and companies use. It is the base of every Felhom system: we build every machine on it, and the isolated system environment your apps run in sits on top of it.</p>
<div class="highlight-box">
<p><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-layers"/></svg> <strong>The base, not an option:</strong> Proxmox is on every Felhom box. You do not need to know about it or deal with it – it works quietly in the background, and it is what makes your system safe and able to be backed up.</p>
</div>
</div>
<div class="info-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-target"/></svg></span> What does the Proxmox base give you?</h3>
<ul class="feature-list">
<li><strong>A backup before each update</strong> – before an app is updated, a fresh backup is made, and if the new version does not start, the box puts the previous one back by itself</li>
<li><strong>The whole system is backed up</strong> – not only your data: the whole box goes into a backup too, and it can be restored from it</li>
<li><strong>Clean separation</strong> – the control layer and your apps run well apart, which makes the system more stable and safer</li>
<li><strong>A fast, repeatable install</strong> – setting up a new box is quick and predictable, the same way every time</li>
</ul>
<div class="highlight-box">
<p><strong>One base, one job:</strong> on a Felhom box, Proxmox runs a single isolated system environment – the one your apps work in.</p>
</div>
</div>
</div>
</section>
<!-- Hálózat -->
<section class="content-section alt-bg" id="network">
<div class="container">
<div class="section-header">
<h2>Network and <span>security</span></h2>
<p>How do you reach your server safely, from anywhere?</p>
</div>
<div class="info-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-rotate-cw"/></svg></span> Reverse Proxy</h3>
<p>A reverse proxy is a "doorkeeper" that takes incoming requests and passes them to the right app. It also handles the HTTPS encryption.</p>
<div class="network-flow">
<div class="flow-node">
<div class="node-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-globe"/></svg></div>
<div class="node-label">Internet</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-node">
<div class="node-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-lock"/></svg></div>
<div class="node-label">Reverse Proxy</div>
<div class="node-tech">Traefik</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-node">
<div class="node-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg></div>
<div class="node-label">App</div>
</div>
</div>
<ul class="feature-list">
<li><strong>Traefik</strong> – every app on its own subdomain, over HTTPS</li>
<li><strong>Encrypted tunnel</strong> – the box itself builds the connection outwards, so no port has to be opened at home</li>
</ul>
</div>
<div class="info-card">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-shield"/></svg></span> Protection</h3>
<ul class="feature-list">
<li><strong>No open port</strong> – from outside, the box is reached only through the encrypted tunnel</li>
<li><strong>Own sign-in</strong> – every app has its own accounts, and the Dashboard has a password</li>
<li><strong>Family gate</strong> – some apps sit behind each family member's own sign-in, so a stranger never even reaches the app</li>
<li><strong>Security updates</strong> – the box's system gets them by itself</li>
</ul>
</div>
</div>
</section>
<!-- Tárolás -->
<section class="content-section" id="storage">
<div class="container">
<div class="section-header">
<h2>Storage and <span>data safety</span></h2>
<p>Where and how should you keep your data?</p>
</div>
<div class="comparison-grid">
<div class="comparison-card">
<div class="card-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-zap"/></svg></div>
<h3>SSD (NVMe/SATA)</h3>
<div class="subtitle">Fast storage</div>
<ul class="feature-list">
<li>Operating system</li>
<li>Databases</li>
<li>Apps</li>
</ul>
</div>
<div class="comparison-card">
<div class="card-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-database"/></svg></div>
<h3>HDD</h3>
<div class="subtitle">Large capacity</div>
<ul class="feature-list">
<li>Media library</li>
<li>Photo archive</li>
<li>Backups</li>
</ul>
</div>
</div>
<div class="info-card" style="margin-top: 32px;">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-rotate-cw"/></svg></span> Backups on three levels – how we protect your data</h3>
<p>Every night a Felhom box makes a <strong>backup on the box</strong>, a <strong>second copy on a second drive</strong>, and an <strong>encrypted copy on remote storage</strong>. The levels protect together: the local one is fast, and the remote one survives even if the box itself is physically harmed.</p>
<div class="warning-box">
<p><svg class="ico" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-triangle-alert"/></svg> <strong>More disks are not a backup on their own!</strong> RAID-like set-ups protect against hardware failure, but not against an accidental delete or ransomware. That is why our backups always sit on a separate level, not in the disks.</p>
</div>
</div>
</div>
</section>
<!-- Hardver konfigurációk -->
<section class="content-section alt-bg" id="hardware">
<div class="container">
<div class="section-header">
<h2>Hardware <span>set-ups</span></h2>
<p>Which machine do you need? A few example set-ups</p>
</div>
<div class="hardware-grid">
<div class="hardware-card">
<div class="hardware-card-header">
<div class="hw-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg></div>
<h3>Mini PC + NAS</h3>
<div class="hw-subtitle">Starter / Home</div>
</div>
<div class="hardware-card-body">
<p>Compact, quiet and energy-saving.</p>
<ul class="spec-list">
<li><span class="spec-label">CPU</span><span class="spec-value">Intel N100</span></li>
<li><span class="spec-label">RAM</span><span class="spec-value">16-32 GB</span></li>
<li><span class="spec-label">SSD</span><span class="spec-value">256-512 GB</span></li>
<li><span class="spec-label">Power use</span><span class="spec-value">15-35W</span></li>
</ul>
<div class="price-range">
<div class="label">Estimated cost</div>
<div class="price">80.000 - 150.000 Ft</div>
</div>
</div>
</div>
<div class="hardware-card">
<div class="hardware-card-header">
<div class="hw-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-radio-tower"/></svg></div>
<h3>Tower server</h3>
<div class="hw-subtitle">Advanced / Family</div>
</div>
<div class="hardware-card-body">
<p>More power and more storage.</p>
<ul class="spec-list">
<li><span class="spec-label">CPU</span><span class="spec-value">Intel i5/i7</span></li>
<li><span class="spec-label">RAM</span><span class="spec-value">32-64 GB</span></li>
<li><span class="spec-label">Storage</span><span class="spec-value">4-8 TB</span></li>
<li><span class="spec-label">Power use</span><span class="spec-value">50-120W</span></li>
</ul>
<div class="price-range">
<div class="label">Estimated cost</div>
<div class="price">200.000 - 400.000 Ft</div>
</div>
</div>
</div>
<div class="hardware-card">
<div class="hardware-card-header">
<div class="hw-icon"><svg class="ico-tile" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-building-2"/></svg></div>
<h3>Rack server</h3>
<div class="hw-subtitle">Business / on request</div>
</div>
<div class="hardware-card-body">
<p>Industrial quality, high reliability.</p>
<ul class="spec-list">
<li><span class="spec-label">CPU</span><span class="spec-value">Xeon / EPYC</span></li>
<li><span class="spec-label">RAM</span><span class="spec-value">64-256 GB ECC</span></li>
<li><span class="spec-label">Storage</span><span class="spec-value">8-24 bay</span></li>
<li><span class="spec-label">Extra</span><span class="spec-value">IPMI, red. PSU</span></li>
</ul>
<div class="price-range">
<div class="label">Estimated cost (used)</div>
<div class="price">150.000 - 600.000 Ft</div>
</div>
</div>
</div>
</div>
<div class="info-card" style="margin-top: 48px;">
<h3><span class="icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-lightbulb"/></svg></span> Which one should you choose?</h3>
<div class="highlight-box">
<p><strong>Our advice:</strong> start small! A good mini PC is enough for most homes, and you can expand it later. A rack server is a business / on-request set-up – a home rarely needs one.</p>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section class="cta-section">
<div class="container">
<h2>Not sure which <span>set-up is right for you?</span></h2>
<p>Let's talk through what you need, and we will help you find the right set-up.</p>
<a href="/en/contact" class="cta-button">Get in touch →</a>
<a href="/en/apps" class="cta-button-secondary">Browse the apps →</a>
</div>
</section>
</main>
<script>
(function() {
var hamburger = document.getElementById('hamburger');
var navLinks = document.querySelector('.nav-links');
var overlay = document.getElementById('mobileOverlay');
var body = document.body;
if (!hamburger || !navLinks || !overlay) {
console.warn('[mobile-menu] Missing elements:', {
hamburger: !!hamburger,
navLinks: !!navLinks,
overlay: !!overlay
});
return;
}
function openMenu() {
hamburger.classList.add('is-active');
navLinks.classList.add('is-open');
overlay.classList.add('is-visible');
body.classList.add('menu-open');
hamburger.setAttribute('aria-expanded', 'true');
hamburger.setAttribute('aria-label', 'Close menu');
}
function closeMenu() {
hamburger.classList.remove('is-active');
navLinks.classList.remove('is-open');
overlay.classList.remove('is-visible');
body.classList.remove('menu-open');
hamburger.setAttribute('aria-expanded', 'false');
hamburger.setAttribute('aria-label', 'Open menu');
}
hamburger.addEventListener('click', function() {
if (navLinks.classList.contains('is-open')) {
closeMenu();
} else {
openMenu();
}
});
overlay.addEventListener('click', closeMenu);
navLinks.querySelectorAll('a').forEach(function(link) {
link.addEventListener('click', closeMenu);
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && navLinks.classList.contains('is-open')) {
closeMenu();
}
});
window.addEventListener('resize', function() {
if (window.innerWidth > 900 && navLinks.classList.contains('is-open')) {
closeMenu();
}
});
})();
</script>
<script>
(function(){
var b=document.getElementById('navBurger'),s=document.getElementById('sidebar'),k=document.getElementById('navBackdrop');
if(!b||!s||!k)return;
function open(){s.classList.add('is-open');k.hidden=false;document.body.classList.add('nav-open');b.setAttribute('aria-expanded','true');}
function close(){s.classList.remove('is-open');k.hidden=true;document.body.classList.remove('nav-open');b.setAttribute('aria-expanded','false');}
b.addEventListener('click',function(){s.classList.contains('is-open')?close():open();});
k.addEventListener('click',close);
document.addEventListener('keydown',function(e){if(e.key==='Escape'&&s.classList.contains('is-open')){close();b.focus();}});
s.querySelectorAll('a').forEach(function(l){l.addEventListener('click',close);});
window.addEventListener('resize',function(){if(window.innerWidth>768)close();});
})();
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var g=btn.closest('.nav-group');if(!g)return;
var willOpen=!g.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(x){
x.classList.remove('is-open');
var t=x.querySelector('.nav-group-toggle');if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){g.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
</script>
</body>
</html>