docs: vollstaendige README mit HUD-Renderings, Luft-Ring mittig
Lint / Lint Resource (push) Has been cancelled

HUD-Layout symmetrisch: Tiefe links, Luft-Ring mittig, Druck rechts, Lampe
zentriert darunter. Umgestellt auf Grid, damit die drei Werte garantiert auf
einer Mittelachse sitzen und die Lampen-Leiste ohne Ausgleichs-Margins in einer
zweiten Zeile haengt.

README:
- Drei HUD-Renderings (normal, Warnung, kritisch) als SVG. Keine In-Game-Bilder -
  hier laeuft kein GTA -, sondern massstabsgetreu aus denselben Werten wie
  style.css erzeugt, per docs/gen-hud-svg.mjs reproduzierbar
- Vollstaendige Config-Referenz fuer client.lua und shared.lua
- Item-Tabelle mit Laufzeit, Fuelldruck, Gewicht, dazu die Metadata-Keys und
  warum kein decay gesetzt werden darf
- Installation, Bedienung, Exports, Balancing-Hinweis
- Bekannte Unsicherheiten offen benannt

Item-Snippet mit klarem Copy-Paste-Header. Bewusst keine fertige items.lua zum
Ersetzen: die wuerde die restlichen Items des Servers mitloeschen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 00:13:06 +02:00
co-authored by Claude Opus 5
parent 5a59fc513f
commit ce0f693266
8 changed files with 445 additions and 77 deletions
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env node
// Erzeugt die HUD-Renderings für die README aus denselben Werten wie nui/style.css.
// Keine In-Game-Screenshots - eine massstabsgetreue Nachbildung des Overlays.
//
// Nutzung: node docs/gen-hud-svg.mjs [ziel-ordner] (default: docs)
import { writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
const OUT = process.argv[2] ?? 'docs';
mkdirSync(OUT, { recursive: true });
const C = {
accent: '#38bdf8',
warn: '#fbbf24',
danger: '#f87171',
text: '#e2e8f0',
muted: '#94a3b8',
panel: '#09121c',
border: '#94a3b8'
};
// Geometrie spiegelt das Grid aus style.css:
// padding 14/22, Spalten 84 | 92 | 84 mit column-gap 22, row-gap 8, Lampe 23 hoch.
const PAD_X = 22, PAD_Y = 14, COL = 84, GAUGE = 92, GAP = 22, ROW_GAP = 8, LAMP_H = 23;
const PW = PAD_X * 2 + COL * 2 + GAUGE + GAP * 2;
const PH = PAD_Y * 2 + GAUGE + ROW_GAP + LAMP_H;
const W = 520, H = PH + 70;
const PX = (W - PW) / 2, PY = (H - PH) / 2;
const GX = PX + PAD_X + COL + GAP;
const CX = GX + GAUGE / 2, CY = PY + PAD_Y + GAUGE / 2;
const R = 52 * (GAUGE / 120); // r=52 im viewBox 120, auf 92px skaliert
const SW = 8 * (GAUGE / 120);
const CIRC = 2 * Math.PI * R;
const DEPTH_X = PX + PAD_X + COL; // rechtsbündig, zeigt zum Ring
const PRESS_X = GX + GAUGE + GAP; // linksbündig, zeigt zum Ring
const CAP_Y = PY + PAD_Y + 35;
const VAL_Y = PY + PAD_Y + 61;
const FONT = "'Segoe UI',Roboto,system-ui,sans-serif";
const fmt = (s) => `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
function readout(x, anchor, label, value, unit, colour) {
const unitTspan = `<tspan font-size="13" font-weight="500" fill="${C.muted}" dx="3">${unit}</tspan>`;
return `<text x="${x}" y="${CAP_Y}" text-anchor="${anchor}" font-family="${FONT}" font-size="9" font-weight="600" letter-spacing="1.4" fill="${C.muted}">${label}</text>
<text x="${x}" y="${VAL_Y}" text-anchor="${anchor}" font-family="${FONT}" font-size="26" font-weight="600" fill="${colour}">${value}${unitTspan}</text>`;
}
function build({ file, title, oxygen, capacity, depth, pressure, level, deep, reserve, lamp }) {
const ratio = Math.max(0, Math.min(1, oxygen / capacity));
const ring = level === 'danger' ? C.danger : level === 'warn' ? C.warn : C.accent;
const lampColour = lamp ? C.accent : C.muted;
const lampX = CX - 43;
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="${title}">
<defs>
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
<stop offset="0%" stop-color="#0d3550"/>
<stop offset="100%" stop-color="#071a26"/>
</linearGradient>
</defs>
<rect width="${W}" height="${H}" fill="url(#water)"/>
<rect x="${PX}" y="${PY}" width="${PW}" height="${PH}" rx="16"
fill="${C.panel}" fill-opacity="0.72" stroke="${C.border}" stroke-opacity="0.18"/>
<g transform="rotate(-90 ${CX} ${CY})">
<circle cx="${CX}" cy="${CY}" r="${R.toFixed(2)}" fill="none" stroke="${C.muted}" stroke-opacity="0.16" stroke-width="${SW.toFixed(2)}" stroke-linecap="round"/>
<circle cx="${CX}" cy="${CY}" r="${R.toFixed(2)}" fill="none" stroke="${ring}" stroke-width="${SW.toFixed(2)}" stroke-linecap="round"
stroke-dasharray="${CIRC.toFixed(2)}" stroke-dashoffset="${(CIRC * (1 - ratio)).toFixed(2)}"/>
</g>
<text x="${CX}" y="${CY - 1}" text-anchor="middle" font-family="${FONT}" font-size="21" font-weight="600" fill="${ring}">${fmt(oxygen)}</text>
<text x="${CX}" y="${CY + 15}" text-anchor="middle" font-family="${FONT}" font-size="9" font-weight="600" letter-spacing="1.4" fill="${C.muted}">LUFT</text>
${readout(DEPTH_X, 'end', 'TIEFE', depth.toFixed(1), 'm', deep ? C.warn : C.text)}
${readout(PRESS_X, 'start', 'DRUCK', String(Math.round(pressure)), 'bar', reserve ? C.danger : C.text)}
<g>
<rect x="${lampX}" y="${PY + PAD_Y + GAUGE + ROW_GAP}" width="86" height="${LAMP_H}" rx="8" fill="${lampColour}" fill-opacity="${lamp ? 0.15 : 0.1}"/>
<circle cx="${lampX + 14}" cy="${PY + PAD_Y + GAUGE + ROW_GAP + LAMP_H / 2}" r="4" fill="${lampColour}"/>
<text x="${lampX + 24}" y="${PY + PAD_Y + GAUGE + ROW_GAP + LAMP_H / 2 + 3.5}" font-family="${FONT}" font-size="10" font-weight="600" letter-spacing="1.2" fill="${lampColour}">LAMPE</text>
</g>
</svg>
`;
writeFileSync(join(OUT, file), svg);
console.log(file);
}
build({
file: 'hud-normal.svg', title: 'Tauch-HUD: normaler Zustand',
oxygen: 545, capacity: 1200, depth: 12.4, pressure: 136, level: 'ok', lamp: true
});
build({
file: 'hud-warnung.svg', title: 'Tauch-HUD: Warnstufe und Reservedruck',
oxygen: 92, capacity: 900, depth: 27.5, pressure: 31,
level: 'warn', deep: true, reserve: true, lamp: false
});
build({
file: 'hud-kritisch.svg', title: 'Tauch-HUD: kritischer Luftstand',
oxygen: 8, capacity: 600, depth: 31.7, pressure: 4,
level: 'danger', deep: true, reserve: true, lamp: true
});