This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE networker_worlds (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
slug VARCHAR(150) NOT NULL UNIQUE,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
description VARCHAR(1200),
|
||||
difficulty VARCHAR(30) NOT NULL DEFAULT 'junior',
|
||||
exam_scope VARCHAR(20) NOT NULL DEFAULT 'AP1',
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
CONSTRAINT chk_networker_exam_scope CHECK (exam_scope IN ('AP1','AP2_FIAE','AP2_FISI','CROSS'))
|
||||
);
|
||||
|
||||
CREATE TABLE networker_levels (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
world_id BIGINT NOT NULL REFERENCES networker_worlds(id) ON DELETE CASCADE,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
level_type VARCHAR(30) NOT NULL,
|
||||
points INT NOT NULL DEFAULT 100,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
payload VARCHAR(8000) NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_networker_levels_world ON networker_levels(world_id);
|
||||
@@ -0,0 +1,23 @@
|
||||
{"worlds":[
|
||||
{"slug":"ein-netz-fuer-alle","title":"Welt 1: Ein Netz für alle","description":"Grundlagen der IPv4-Adressierung: Hostadressen, Masken und ein gemeinsames LAN.","difficulty":"junior","examScope":"AP1","levels":[
|
||||
{"title":"Erste Verbindung","type":"assign","points":100,"payload":{"nodes":[{"id":"PC1","label":"PC-01","type":"pc","x":150,"y":270,"ip":"192.168.10.0","prefix":24,"editable":["ip","prefix"]},{"id":"SW1","label":"SW-01","type":"switch","x":450,"y":270},{"id":"SRV1","label":"SRV-01","type":"server","x":760,"y":270,"ip":"192.168.10.20","prefix":24}],"links":[["PC1","SW1"],["SW1","SRV1"]],"goals":[{"id":"pc-zu-server","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","ip":"192.168.10.10","prefix":24}]}}},
|
||||
{"title":"Die richtige Maske","type":"fix","points":100,"payload":{"nodes":[{"id":"PC1","label":"PC-02","type":"pc","x":150,"y":270,"ip":"192.168.20.10","prefix":25,"editable":["prefix"]},{"id":"SW1","label":"SW-02","type":"switch","x":450,"y":270},{"id":"SRV1","label":"SRV-02","type":"server","x":760,"y":270,"ip":"192.168.20.200","prefix":24}],"links":[["PC1","SW1"],["SW1","SRV1"]],"goals":[{"id":"maske","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","prefix":24}]}}},
|
||||
{"title":"Host statt Broadcast","type":"fix","points":100,"payload":{"nodes":[{"id":"PC1","label":"PC-03","type":"pc","x":150,"y":270,"ip":"192.168.30.255","prefix":24,"editable":["ip"]},{"id":"SW1","label":"SW-03","type":"switch","x":450,"y":270},{"id":"SRV1","label":"SRV-03","type":"server","x":760,"y":270,"ip":"192.168.30.50","prefix":24}],"links":[["PC1","SW1"],["SW1","SRV1"]],"goals":[{"id":"broadcast","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","ip":"192.168.30.30"}]}}},
|
||||
{"title":"Doppelte Adresse","type":"fix","points":120,"payload":{"nodes":[{"id":"PC1","label":"PC-04","type":"pc","x":150,"y":270,"ip":"192.168.40.20","prefix":24,"editable":["ip"]},{"id":"SW1","label":"SW-04","type":"switch","x":450,"y":270},{"id":"SRV1","label":"SRV-04","type":"server","x":760,"y":270,"ip":"192.168.40.20","prefix":24}],"links":[["PC1","SW1"],["SW1","SRV1"]],"goals":[{"id":"duplikat","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","ip":"192.168.40.10"}]}}},
|
||||
{"title":"Das /26-Labor","type":"assign","points":120,"payload":{"nodes":[{"id":"PC1","label":"PC-05","type":"pc","x":150,"y":270,"ip":"10.0.5.70","prefix":24,"editable":["prefix"]},{"id":"SW1","label":"SW-05","type":"switch","x":450,"y":270},{"id":"SRV1","label":"SRV-05","type":"server","x":760,"y":270,"ip":"10.0.5.100","prefix":26}],"links":[["PC1","SW1"],["SW1","SRV1"]],"goals":[{"id":"cidr","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","prefix":26}]}}}
|
||||
]},
|
||||
{"slug":"ueber-den-router","title":"Welt 2: Über den Router","description":"Gateways verbinden getrennte Netze. Prüfe Adresse, Maske und den Ausgang aus dem LAN.","difficulty":"senior","examScope":"AP1","levels":[
|
||||
{"title":"Tor zum Serverraum","type":"assign","points":130,"payload":{"nodes":[{"id":"PC1","label":"PC-A","type":"pc","x":120,"y":270,"ip":"192.168.1.10","prefix":24,"gateway":"192.168.1.254","editable":["gateway"]},{"id":"R1","label":"RTR-01","type":"router","x":450,"y":270,"gateways":["192.168.1.1","192.168.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-A","type":"server","x":780,"y":270,"ip":"192.168.2.20","prefix":24}],"links":[["PC1","R1"],["R1","SRV1"]],"goals":[{"id":"gateway","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","gateway":"192.168.1.1"}]}}},
|
||||
{"title":"Gateway im falschen Netz","type":"fix","points":130,"payload":{"nodes":[{"id":"PC1","label":"PC-B","type":"pc","x":120,"y":270,"ip":"192.168.11.10","prefix":24,"gateway":"192.168.12.1","editable":["gateway"]},{"id":"R1","label":"RTR-02","type":"router","x":450,"y":270,"gateways":["192.168.11.1","192.168.12.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-B","type":"server","x":780,"y":270,"ip":"192.168.12.60","prefix":24}],"links":[["PC1","R1"],["R1","SRV1"]],"goals":[{"id":"local-gateway","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","gateway":"192.168.11.1"}]}}},
|
||||
{"title":"Netzadresse am Ausgang","type":"fix","points":140,"payload":{"nodes":[{"id":"PC1","label":"PC-C","type":"pc","x":120,"y":270,"ip":"172.20.1.0","prefix":24,"gateway":"172.20.1.1","editable":["ip"]},{"id":"R1","label":"RTR-03","type":"router","x":450,"y":270,"gateways":["172.20.1.1","172.20.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-C","type":"server","x":780,"y":270,"ip":"172.20.2.30","prefix":24}],"links":[["PC1","R1"],["R1","SRV1"]],"goals":[{"id":"hostadresse","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","ip":"172.20.1.10"}]}}},
|
||||
{"title":"Abteilung Nord","type":"assign","points":140,"payload":{"nodes":[{"id":"PC1","label":"PC-N","type":"pc","x":120,"y":270,"ip":"10.20.1.25","prefix":24,"gateway":"10.20.1.1","editable":["prefix"]},{"id":"R1","label":"RTR-04","type":"router","x":450,"y":270,"gateways":["10.20.1.1","10.20.2.1"],"gatewayPrefix":25},{"id":"SRV1","label":"SRV-N","type":"server","x":780,"y":270,"ip":"10.20.2.44","prefix":25}],"links":[["PC1","R1"],["R1","SRV1"]],"goals":[{"id":"praefix","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","prefix":25}]}}},
|
||||
{"title":"Freier Host","type":"assign","points":150,"payload":{"nodes":[{"id":"PC1","label":"PC-E","type":"pc","x":120,"y":270,"ip":"192.168.50.1","prefix":24,"gateway":"192.168.50.1","editable":["ip"]},{"id":"R1","label":"RTR-05","type":"router","x":450,"y":270,"gateways":["192.168.50.1","192.168.60.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-E","type":"server","x":780,"y":270,"ip":"192.168.60.15","prefix":24}],"links":[["PC1","R1"],["R1","SRV1"]],"goals":[{"id":"freier-host","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"PC1","ip":"192.168.50.10"}]}}}
|
||||
]},
|
||||
{"slug":"routen-und-fehlersuche","title":"Welt 3: Routen & Fehlersuche","description":"Zwei Router, statische Routen und echte Diagnose auf AP2-FISI-Niveau.","difficulty":"principal","examScope":"AP2_FISI","levels":[
|
||||
{"title":"Der fehlende Weg","type":"route","points":180,"payload":{"nodes":[{"id":"PC1","label":"PC-OPS","type":"pc","x":100,"y":270,"ip":"10.10.1.10","prefix":24,"gateway":"10.10.1.1"},{"id":"R1","label":"RTR-EDGE","type":"router","x":350,"y":270,"gateways":["10.10.1.1","172.16.0.1"],"gatewayPrefix":24,"routes":[],"editable":["routes"]},{"id":"R2","label":"RTR-CORE","type":"router","x":600,"y":270,"gateways":["172.16.0.2","10.10.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-ERP","type":"server","x":850,"y":270,"ip":"10.10.2.20","prefix":24}],"links":[["PC1","R1"],["R1","R2"],["R2","SRV1"]],"goals":[{"id":"route-erp","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"R1","routes":[{"network":"10.10.2.0","prefix":24,"nextRouter":"R2"}]}]}}},
|
||||
{"title":"Route im Kreis","type":"route","points":180,"payload":{"nodes":[{"id":"PC1","label":"PC-LOG","type":"pc","x":100,"y":270,"ip":"10.20.1.10","prefix":24,"gateway":"10.20.1.1"},{"id":"R1","label":"RTR-01","type":"router","x":350,"y":270,"gateways":["10.20.1.1","172.20.0.1"],"gatewayPrefix":24,"routes":[{"network":"10.20.2.0","prefix":24,"nextRouter":"R1"}],"editable":["routes"]},{"id":"R2","label":"RTR-02","type":"router","x":600,"y":270,"gateways":["172.20.0.2","10.20.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-LOG","type":"server","x":850,"y":270,"ip":"10.20.2.50","prefix":24}],"links":[["PC1","R1"],["R1","R2"],["R2","SRV1"]],"goals":[{"id":"route-loop","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"R1","routes":[{"network":"10.20.2.0","prefix":24,"nextRouter":"R2"}]}]}}},
|
||||
{"title":"Das ferne Backup","type":"route","points":190,"payload":{"nodes":[{"id":"PC1","label":"PC-BACKUP","type":"pc","x":100,"y":270,"ip":"10.30.1.10","prefix":24,"gateway":"10.30.1.1"},{"id":"R1","label":"RTR-WEST","type":"router","x":350,"y":270,"gateways":["10.30.1.1","172.30.0.1"],"gatewayPrefix":24,"routes":[],"editable":["routes"]},{"id":"R2","label":"RTR-OST","type":"router","x":600,"y":270,"gateways":["172.30.0.2","10.30.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-BACKUP","type":"server","x":850,"y":270,"ip":"10.30.2.100","prefix":24}],"links":[["PC1","R1"],["R1","R2"],["R2","SRV1"]],"goals":[{"id":"route-backup","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"R1","routes":[{"network":"10.30.2.0","prefix":24,"nextRouter":"R2"}]}]}}},
|
||||
{"title":"Monitoring-Netz","type":"route","points":190,"payload":{"nodes":[{"id":"PC1","label":"PC-NOC","type":"pc","x":100,"y":270,"ip":"10.40.1.10","prefix":24,"gateway":"10.40.1.1"},{"id":"R1","label":"RTR-NOC","type":"router","x":350,"y":270,"gateways":["10.40.1.1","172.40.0.1"],"gatewayPrefix":24,"routes":[],"editable":["routes"]},{"id":"R2","label":"RTR-MON","type":"router","x":600,"y":270,"gateways":["172.40.0.2","10.40.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-MON","type":"server","x":850,"y":270,"ip":"10.40.2.8","prefix":24}],"links":[["PC1","R1"],["R1","R2"],["R2","SRV1"]],"goals":[{"id":"route-monitor","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"R1","routes":[{"network":"10.40.2.0","prefix":24,"nextRouter":"R2"}]}]}}},
|
||||
{"title":"Letzter Hop","type":"route","points":200,"payload":{"nodes":[{"id":"PC1","label":"PC-SOC","type":"pc","x":100,"y":270,"ip":"10.50.1.10","prefix":24,"gateway":"10.50.1.1"},{"id":"R1","label":"RTR-SOC","type":"router","x":350,"y":270,"gateways":["10.50.1.1","172.50.0.1"],"gatewayPrefix":24,"routes":[],"editable":["routes"]},{"id":"R2","label":"RTR-DC","type":"router","x":600,"y":270,"gateways":["172.50.0.2","10.50.2.1"],"gatewayPrefix":24},{"id":"SRV1","label":"SRV-SIEM","type":"server","x":850,"y":270,"ip":"10.50.2.200","prefix":24}],"links":[["PC1","R1"],["R1","R2"],["R2","SRV1"]],"goals":[{"id":"route-siem","from":"PC1","to":"SRV1","shouldReach":true}],"solution":{"nodes":[{"id":"R1","routes":[{"network":"10.50.2.0","prefix":24,"nextRouter":"R2"}]}]}}}
|
||||
]}
|
||||
]}
|
||||
@@ -6,6 +6,7 @@ body{padding-left:0!important;padding-bottom:0!important}
|
||||
.mf-theme-float{display:flex;align-items:center;gap:.55rem}
|
||||
body.mf-a11y-skin-codeescape{--mf-a11y-accent:var(--esc-accent);--mf-a11y-glow:var(--esc-glow);--mf-a11y-ink:#1c1100}
|
||||
body.mf-a11y-skin-bughunter{--mf-a11y-accent:var(--bh-accent);--mf-a11y-glow:var(--bh-glow);--mf-a11y-ink:#210c12}
|
||||
body.mf-a11y-skin-networker{--mf-a11y-accent:var(--nw-accent);--mf-a11y-glow:var(--nw-glow);--mf-a11y-ink:#061321}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-trigger{border-color:color-mix(in srgb,var(--mf-a11y-accent) 65%,var(--border));background:linear-gradient(135deg,var(--mf-a11y-accent),color-mix(in srgb,var(--mf-a11y-accent) 72%,#fff));color:var(--mf-a11y-ink);box-shadow:0 0 16px var(--mf-a11y-glow)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-panel{border-color:color-mix(in srgb,var(--mf-a11y-accent) 56%,var(--border));box-shadow:0 12px 45px rgba(0,0,0,.55),0 0 22px var(--mf-a11y-glow)}
|
||||
body[class*="mf-a11y-skin-"] .mf-a11y-head{background:color-mix(in srgb,var(--mf-a11y-accent) 28%,#07121f)}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.nw-app{--accent:var(--nw-accent);min-height:100vh;background:linear-gradient(rgba(12,30,55,.92),rgba(8,18,34,.96)),repeating-linear-gradient(0deg,transparent 0 29px,rgba(103,187,255,.08) 30px),repeating-linear-gradient(90deg,transparent 0 29px,rgba(103,187,255,.08) 30px);color:var(--text);font-family:"Courier New",monospace}.nw-top{min-height:72px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;padding:12px 24px;border-bottom:1px solid color-mix(in srgb,var(--nw-accent) 52%,var(--border));background:color-mix(in srgb,var(--panel) 94%,transparent)}.nw-back{color:var(--muted);text-decoration:none}.nw-brand{text-align:center}.nw-brand b{display:block;color:var(--nw-accent);font-size:1.25rem;letter-spacing:.2em;text-shadow:0 0 14px var(--nw-glow)}.nw-brand span,.nw-kicker{color:var(--muted);font-size:.65rem;letter-spacing:.15em}.nw-top .mf-a11y-host{justify-self:end}.nw-view{max-width:1240px;margin:auto;padding:2rem 1.2rem}.nw-loading{text-align:center;padding:20vh 0;color:var(--muted)}.nw-hero,.nw-brief,.nw-result,.nw-world,.nw-panel,.nw-board{border:1px solid color-mix(in srgb,var(--nw-accent) 48%,var(--border));background:color-mix(in srgb,var(--surface) 91%,transparent);border-radius:12px;padding:1.4rem;box-shadow:0 0 24px color-mix(in srgb,var(--nw-accent) 10%,transparent)}.nw-hero h1,.nw-brief h1,.nw-level-head h1,.nw-result h1{margin:.45rem 0;color:var(--heading)}.nw-hero p,.nw-brief p,.nw-world p{color:var(--muted);line-height:1.55}.nw-worlds{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;margin-top:1rem}.nw-world{display:flex;flex-direction:column;align-items:flex-start}.nw-world span{color:var(--nw-accent);font-size:.7rem}.nw-world h2{color:var(--heading)}.nw-world small{margin-bottom:1rem;color:var(--muted)}.nw-btn{border:1px solid var(--nw-accent);border-radius:6px;background:transparent;color:var(--nw-accent);padding:.65rem .85rem;font:700 .72rem "Courier New",monospace;letter-spacing:.07em;cursor:pointer}.nw-btn.primary,.nw-btn:hover{background:var(--nw-accent);color:#061321;box-shadow:0 0 16px var(--nw-glow)}.nw-rules,.nw-actions{display:flex;gap:.65rem;flex-wrap:wrap;margin-top:1rem}.nw-rules div{padding:.6rem;background:var(--surface-2);border-radius:6px;color:var(--muted)}.nw-rules b{display:inline-grid;place-items:center;width:1.2rem;height:1.2rem;border-radius:50%;background:var(--nw-accent);color:#061321}.nw-level-head{display:flex;justify-content:space-between;align-items:center}.nw-score{display:grid;place-items:center;width:66px;height:66px;border:1px solid var(--nw-accent);border-radius:50%;color:var(--nw-accent);font-size:1.1rem}.nw-score small{font-size:.5rem}.nw-progress{display:flex;gap:.5rem;margin:1rem 0}.nw-progress i{display:grid;place-items:center;width:26px;height:26px;border:1px solid var(--border);border-radius:50%;font-style:normal;color:var(--muted)}.nw-progress i.active{border-color:var(--nw-accent);color:var(--nw-accent)}.nw-progress i.done{background:var(--success);color:#061321}.nw-layout{display:grid;grid-template-columns:3fr 2fr;gap:1rem}.nw-board{min-height:520px;padding:.5rem;overflow:auto}.nw-board svg{min-width:700px;width:100%;height:100%}.nw-link{stroke:var(--nw-accent);stroke-width:4;stroke-dasharray:8 6;fill:none}.nw-node rect{fill:color-mix(in srgb,var(--surface) 88%,#071827);stroke:var(--nw-accent);stroke-width:2}.nw-node text{fill:var(--text);font:700 13px "Courier New",monospace}.nw-router rect{stroke:#b18cff}.nw-panel{display:grid;align-content:start;gap:.8rem}.nw-panel h2{margin:.1rem 0;color:var(--heading);font-size:1rem}.nw-panel fieldset{border:1px solid var(--border);border-radius:7px;display:grid;gap:.5rem}.nw-panel legend{color:var(--nw-accent);font-size:.72rem}.nw-panel label{display:grid;gap:.2rem;color:var(--muted);font-size:.7rem}.nw-panel input,.nw-panel textarea{width:100%;box-sizing:border-box;background:#06111f;color:var(--text);border:1px solid var(--border);border-radius:4px;padding:.45rem;font:inherit}.nw-goals div{display:flex;justify-content:space-between;gap:.5rem;padding:.5rem;border-bottom:1px solid var(--border);font-size:.72rem}.nw-goals span{color:var(--muted)}.nw-goals span.ok,.nw-feedback.ok{color:var(--success)}.nw-goals span.bad,.nw-feedback.bad{color:var(--danger)}.nw-feedback{min-height:1.3rem;font-size:.78rem;line-height:1.4}.nw-result{text-align:center;max-width:720px;margin:7vh auto}.nw-final-score{font-size:3.5rem;color:var(--nw-accent);text-shadow:0 0 20px var(--nw-glow)}.nw-result>p{color:var(--nw-accent);font-size:1.4rem}.nw-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:.7rem;margin:1rem 0}.nw-stats div{background:var(--surface-2);border-radius:6px;padding:.7rem;color:var(--muted);font-size:.75rem}.nw-stats b{display:block;color:var(--heading);font-size:1.1rem}@media(max-width:850px){.nw-layout,.nw-worlds{grid-template-columns:1fr}.nw-board{min-height:380px}.nw-top{grid-template-columns:1fr auto}.nw-brand{display:none}}
|
||||
@@ -2,7 +2,7 @@
|
||||
function load(){try{return Object.assign({},defaults,JSON.parse(localStorage.getItem(KEY)||'{}'));}catch(e){return Object.assign({},defaults);}}var state=load();
|
||||
function apply(){var h=document.documentElement;h.setAttribute('data-mf-font',String(state.font));h.setAttribute('data-mf-readable',state.readable);h.setAttribute('data-mf-contrast',state.contrast);h.setAttribute('data-mf-color',state.color?'safe':'standard');h.setAttribute('data-mf-mono',state.mono);h.setAttribute('data-mf-links',state.links);h.setAttribute('data-mf-motion',state.motion);h.setAttribute('data-mf-cursor',state.cursor);try{localStorage.setItem(KEY,JSON.stringify(state));}catch(e){}}
|
||||
apply();
|
||||
function applySkin(){var root=document.querySelector('[data-a11y-skin]'),skin=root&&root.getAttribute('data-a11y-skin');if(!skin&&document.querySelector('.esc-app'))skin='codeescape';if(!skin&&document.querySelector('.bh-app'))skin='bughunter';if(skin)document.body.classList.add('mf-a11y-skin-'+skin);}
|
||||
function applySkin(){var root=document.querySelector('[data-a11y-skin]'),skin=root&&root.getAttribute('data-a11y-skin');if(!skin&&document.querySelector('.esc-app'))skin='codeescape';if(!skin&&document.querySelector('.bh-app'))skin='bughunter';if(!skin&&document.querySelector('.nw-app'))skin='networker';if(skin)document.body.classList.add('mf-a11y-skin-'+skin);}
|
||||
function button(id,icon,label,detail){return '<button type="button" class="mf-a11y-option" data-a11y="'+id+'"><strong aria-hidden="true">'+icon+'</strong><span>'+label+'</span>'+(detail?'<small>'+detail+'</small>':'')+'</button>';}
|
||||
function init(){if(document.querySelector('.mf-a11y-trigger'))return;var trigger=document.createElement('button');trigger.type='button';trigger.className='mf-a11y-trigger';trigger.setAttribute('aria-label','Barrierefreiheit öffnen');trigger.setAttribute('aria-expanded','false');trigger.setAttribute('aria-controls','mf-a11y-panel');trigger.innerHTML='<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><circle cx="18" cy="5" r="3" fill="currentColor"/><path d="M13 11l5-2 5 4m-7-2-2 7-6 1m8-4 6 2 3 7M13 18a7 7 0 1 0 6 7"/></svg>';
|
||||
var panel=document.createElement('section');panel.id='mf-a11y-panel';panel.className='mf-a11y-panel';panel.hidden=true;panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','false');panel.setAttribute('aria-labelledby','mf-a11y-title');panel.innerHTML='<div class="mf-a11y-head"><button type="button" class="mf-a11y-close" aria-label="Barrierefreiheit schließen">×</button><h2 id="mf-a11y-title">Barrierefreiheit</h2></div><div class="mf-a11y-grid">'+button('font','T⁺','Text vergrößern','100 %')+button('contrast','◐','Hochkontrast')+button('color','◉','Farbenblind-Modus')+button('mono','◒','Monochrom')+button('readable','Aa','Lesemodus (Dyslexie)','Verdana + Abstände')+button('links','🔗','Links unterstreichen')+button('motion','Ⅱ','Animationen stoppen')+button('cursor','➤','Großer Cursor')+button('reset','↺','Alles zurücksetzen')+'</div>';
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
(function(){'use strict';const view=document.getElementById('nwView');let catalog=[],session=null,index=0,started=0,configs=[];const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const api=(url,opt)=>fetch('/api/networker'+url,{headers:{'Content-Type':'application/json'},...opt}).then(async r=>{const j=await r.json();if(!r.ok)throw new Error(j.message||'Serverfehler');return j;});const post=(url,body)=>api(url,{method:'POST',body:JSON.stringify(body)});const seconds=()=>Math.max(1,Math.round((Date.now()-started)/1000));
|
||||
function load(){api('/catalog').then(data=>{catalog=data.worlds||[];renderCatalog();}).catch(error)}
|
||||
function renderCatalog(){view.innerHTML=`<section class="nw-hero"><span class="nw-kicker">BLUEPRINT ARCHIVE</span><h1>Welche Verbindung stellst du wieder her?</h1><p>Konfiguriere Geräte, prüfe Paketwege und lies die Diagnose wie ein Netzwerktechniker.</p></section><section class="nw-worlds">${catalog.map(w=>`<article class="nw-world"><span>${esc(w.examScope)} · ${esc(w.difficulty).toUpperCase()}</span><h2>${esc(w.title)}</h2><p>${esc(w.description)}</p><small>${w.levelCount} Level</small><button class="nw-btn primary" data-world="${esc(w.slug)}">NETZPLAN ÖFFNEN</button></article>`).join('')}</section>`;view.querySelectorAll('[data-world]').forEach(b=>b.addEventListener('click',()=>begin(b.dataset.world)))}
|
||||
function begin(slug){api('/session/'+encodeURIComponent(slug)).then(data=>{session=data;index=0;started=Date.now();configs=data.levels.map(()=>null);renderIntro()}).catch(error)}
|
||||
function renderIntro(){view.innerHTML=`<section class="nw-brief"><span class="nw-kicker">${esc(session.examScope)} · ${esc(session.difficulty).toUpperCase()}</span><h1>${esc(session.title)}</h1><p>${esc(session.description)}</p><div class="nw-rules"><div><b>1</b> Werte prüfen</div><div><b>2</b> Pings simulieren</div><div><b>3</b> Diagnose lesen</div></div><button class="nw-btn primary" id="nwStart">KALIBRIERUNG STARTEN</button></section>`;document.getElementById('nwStart').addEventListener('click',renderLevel)}
|
||||
function renderLevel(){const level=session.levels[index],net=level.network;view.innerHTML=`<header class="nw-level-head"><div><span class="nw-kicker">LEVEL ${index+1} / ${session.levels.length} · ${esc(level.type).toUpperCase()}</span><h1>${esc(level.title)}</h1></div><div class="nw-score">${level.points}<small>PTS</small></div></header><div class="nw-progress">${session.levels.map((_,i)=>`<i class="${i<index?'done':i===index?'active':''}">${i+1}</i>`).join('')}</div><div class="nw-layout"><section class="nw-board">${drawNet(net)}</section><aside class="nw-panel"><h2>Konfiguration</h2>${drawInputs(net)}<section class="nw-goals"><h2>Ping-Ziele</h2>${net.goals.map(g=>`<div><b>${esc(g.from)} → ${esc(g.to)}</b><span id="goal-${esc(g.id)}">offen</span></div>`).join('')}</section><div id="nwFeedback" class="nw-feedback" aria-live="polite"></div><div class="nw-actions"><button class="nw-btn primary" id="nwCheck">PINGS TESTEN</button><button class="nw-btn" id="nwReveal">LÖSUNG ANZEIGEN</button></div></aside></div>`;document.getElementById('nwCheck').addEventListener('click',()=>check(false));document.getElementById('nwReveal').addEventListener('click',()=>check(true))}
|
||||
function drawNet(net){const nodeById=Object.fromEntries(net.nodes.map(n=>[n.id,n]));return `<svg viewBox="0 0 1000 560" role="img" aria-label="Netzwerkplan">${(net.links||[]).map(([a,b])=>{const x=nodeById[a],y=nodeById[b];return x&&y?`<path class="nw-link" d="M${x.x+65} ${x.y+35} L${y.x+65} ${y.y+35}"/>`:''}).join('')}${net.nodes.map(n=>`<g class="nw-node nw-${esc(n.type)}" transform="translate(${n.x||0} ${n.y||0})"><rect width="130" height="70" rx="8"/><text x="65" y="30" text-anchor="middle">${esc(n.label||n.id)}</text><text x="65" y="52" text-anchor="middle">${esc(n.type||'node').toUpperCase()}</text></g>`).join('')}</svg>`}
|
||||
function drawInputs(net){const rows=net.nodes.filter(n=>n.editable?.length).map(n=>`<fieldset data-node="${esc(n.id)}"><legend>${esc(n.label||n.id)}</legend>${n.editable.map(f=>f==='routes'?`<label>Statische Route <textarea data-field="routes" rows="3">${esc(JSON.stringify(n.routes||[]))}</textarea></label>`:`<label>${f==='prefix'?'Präfix /':' '+f.toUpperCase()}<input data-field="${f}" value="${esc(n[f]??'')}"></label>`).join('')}</fieldset>`).join('');return rows||'<p>In diesem Plan sind keine Werte editierbar.</p>'}
|
||||
function collect(){const nodes=[];view.querySelectorAll('[data-node]').forEach(box=>{const node={id:box.dataset.node};box.querySelectorAll('[data-field]').forEach(input=>{if(input.dataset.field==='routes'){try{node.routes=JSON.parse(input.value)}catch(e){node.routes=[]}}else node[input.dataset.field]=input.value});nodes.push(node)});return{nodes}}
|
||||
function check(reveal){const level=session.levels[index];post('/check',{levelId:level.levelId,reveal,config:collect()}).then(result=>{result.goals.forEach(g=>{const target=document.getElementById('goal-'+g.id);if(target){target.textContent=g.passed?'ERREICHT':'BLOCKIERT · '+g.message;target.className=g.passed?'ok':'bad'}});const feedback=document.getElementById('nwFeedback');if(result.resolved){if(result.revealed&&result.solution){level.network=result.solution;configs[index]=null;renderLevel();document.getElementById('nwFeedback').textContent='Auflösung geladen. Prüfe den korrigierten Plan und gehe weiter.'}else{feedback.textContent='Paketweg bestätigt.';feedback.className='nw-feedback ok'}const next=document.createElement('button');next.className='nw-btn primary';next.textContent=index+1<session.levels.length?'NÄCHSTES LEVEL':'WELT AUSWERTEN';next.addEventListener('click',()=>{index++;if(index>=session.levels.length)finish();else renderLevel()});document.querySelector('.nw-actions').replaceChildren(next)}else{feedback.textContent=`Noch nicht stimmig · Versuch ${result.attempts}/3`;feedback.className='nw-feedback bad'}}).catch(error)}
|
||||
function finish(){post('/finish',{slug:session.slug,durationSeconds:seconds(),levelIds:session.levels.map(l=>l.levelId)}).then(r=>{view.innerHTML=`<section class="nw-result"><span class="nw-kicker">NETZWERKPROTOKOLL</span><h1>${r.perfect?'Alle Paketwege fehlerfrei.':'Welt abgeschlossen.'}</h1><div class="nw-final-score">${r.score}</div><p>${'★'.repeat(r.stars)}${'☆'.repeat(3-r.stars)}</p><div class="nw-stats"><div><b>${r.solved}/${r.total}</b>Level</div><div><b>${r.highscore}</b>Highscore</div><div><b>${r.plays}</b>Welten</div></div><button class="nw-btn primary" id="nwAgain">WEITERE WELTEN</button></section>`;document.getElementById('nwAgain').addEventListener('click',load)}).catch(error)}
|
||||
function error(err){view.innerHTML=`<section class="nw-result"><h1>NETZPLAN NICHT LESBAR</h1><p>${esc(err.message||err)}</p><button class="nw-btn" id="nwReload">ERNEUT VERSUCHEN</button></section>`;document.getElementById('nwReload').addEventListener('click',load)}load()})();
|
||||
@@ -32,13 +32,13 @@
|
||||
document.head.appendChild(accessibilityCss);
|
||||
var accessibilityPlacementCss = document.createElement('link');
|
||||
accessibilityPlacementCss.rel = 'stylesheet';
|
||||
accessibilityPlacementCss.href = '/css/accessibility-placement.css?v=5';
|
||||
accessibilityPlacementCss.href = '/css/accessibility-placement.css?v=6';
|
||||
accessibilityPlacementCss.setAttribute('data-accessibility', '');
|
||||
document.head.appendChild(accessibilityPlacementCss);
|
||||
}
|
||||
if (!document.querySelector('script[data-accessibility]')) {
|
||||
var accessibilityScript = document.createElement('script');
|
||||
accessibilityScript.src = '/js/accessibility.js?v=5';
|
||||
accessibilityScript.src = '/js/accessibility.js?v=6';
|
||||
accessibilityScript.defer = true;
|
||||
accessibilityScript.setAttribute('data-accessibility', '');
|
||||
document.head.appendChild(accessibilityScript);
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
['SystemEscape','lock','Löse IT-Rätsel, sammle Zugriffsfragmente und entkomme dem System.','/codeescape'],
|
||||
['ServiceClash','sword','Der chaotische Admin-Alltag als Spiel – meistere Anfragen & halte Systeme am Laufen!','/comingsoon'],
|
||||
['BugHunter','bug','Finde Fehler im Code, denke wie ein Debugger und löse knifflige Bugs.','/bughunter'],
|
||||
['NetWorker','network','Verbinde Netzwerke, konfiguriere IPs und löse Routing-Herausforderungen.','/comingsoon'],
|
||||
['NetWorker','network','Verbinde Netzwerke, konfiguriere IPs und löse Routing-Herausforderungen.','/networker'],
|
||||
['FinalFlux','trophy','Dein Power-Modul für die Prüfung – gezielt lernen, strukturiert trainieren, sicher bestehen.','/finalflux']
|
||||
];
|
||||
document.getElementById('grid').innerHTML = GAMES.map(([t,ic,d,href])=>`
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="de" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>NetWorker · Mindflux</title><link rel="stylesheet" href="/css/theme.css"><link rel="stylesheet" href="/css/networker.css?v=1"><script src="/js/theme.js"></script></head><body><main class="nw-app"><header class="nw-top"><a href="/dashboard" class="nw-back">← Dashboard</a><div class="nw-brand"><b>NETWORKER</b><span>NETZPLAN · DIAGNOSE</span></div><span class="mf-a11y-host"></span></header><section id="nwView" class="nw-view"><div class="nw-loading">NETZWERKPLÄNE WERDEN KALIBRIERT …</div></section></main><script src="/js/networker.js?v=1"></script></body></html>
|
||||
Reference in New Issue
Block a user