feat(hud): Dreier-Konsole mit Finimeter und Tiefenmesser

Statt zweier Zahlenfelder jetzt zwei Zeigerinstrumente links und rechts vom
Luft-Ring - wie eine echte Tauchkonsole. Der Ring bleibt in der Mitte, weil die
Restzeit die Zahl ist, auf die es ankommt.

- Finimeter 0-300 bar, roter Bereich = Reserve unter reservePressure
- Tiefenmesser 0-depthDialMax, roter Bereich = Tiefengrenze des Atemgases. Der
  faengt bei Pressluft bei 45 m an und bei Trimix erst bei 100 m - der
  Gefahrenbereich haengt also an der getragenen Flasche. Gibt es real nicht,
  hilft im Spiel aber sofort beim Einschaetzen
- Skalenzahlen bewusst weggelassen: bei 96 px kollidieren sie mit allem und
  liest ohnehin niemand. Zeiger zeigt die Tendenz, die Zahl in der Uhr den Wert
- Zeiger und Wert faerben sich rot, sobald der rote Bereich erreicht ist

Beim Bauen gefunden: CSS transform-origin auf dem Zeiger wird zusaetzlich zum
rotate(winkel 50 50) im Attribut angewendet, der Drehpunkt wandert dadurch
doppelt und der Zeiger landet ausserhalb der Uhr. transform-origin ist raus.

README-Renderings auf die Konsole umgebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 09:55:45 +02:00
co-authored by Claude Opus 5
parent 64b2549cf9
commit 30c40c9f69
10 changed files with 404 additions and 201 deletions
+10 -5
View File
@@ -9,16 +9,20 @@ die bei Animationen nicht mehr verschwindet.
> Die Bilder sind maßstabsgetreue Renderings des Overlays aus denselben Werten wie > Die Bilder sind maßstabsgetreue Renderings des Overlays aus denselben Werten wie
> `nui/style.css` — keine In-Game-Aufnahmen. Erzeugt mit `docs/gen-hud-svg.mjs`. > `nui/style.css` — keine In-Game-Aufnahmen. Erzeugt mit `docs/gen-hud-svg.mjs`.
**Normal** — Luft, Tiefe, Fülldruck, Lampe an Eine Dreier-Konsole wie am echten Tauchgerät: **Finimeter** links, Restluft als
Ring in der Mitte, **Tiefenmesser** rechts.
**Normal** — Druck im grünen Bereich, flach getaucht, Lampe an
![HUD, normaler Zustand](docs/hud-normal.svg) ![HUD, normaler Zustand](docs/hud-normal.svg)
**Warnung**Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth`, **Warnung**Zeiger in der Reserve (roter Bereich ab `reservePressure`),
Druck unter `reservePressure` Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth`
![HUD, Warnstufe](docs/hud-warnung.svg) ![HUD, Warnstufe](docs/hud-warnung.svg)
**Kritisch**letzte Sekunden, der Ring pulsiert **Kritisch**unter der Tiefengrenze des Atemgases: der Zeiger steht im roten
Bereich des Tiefenmessers, die Uhr pulsiert, Tiefenrausch läuft
![HUD, kritischer Luftstand](docs/hud-kritisch.svg) ![HUD, kritischer Luftstand](docs/hud-kritisch.svg)
@@ -186,7 +190,8 @@ auf z.B. `2`.
| `hudOnlyUnderwater` | `false` | Auf `false` schon sichtbar, sobald die Ausrüstung an ist — Finimeter ablesen ohne Inventar | | `hudOnlyUnderwater` | `false` | Auf `false` schon sichtbar, sobald die Ausrüstung an ist — Finimeter ablesen ohne Inventar |
| `hudFollowGameHud` | `true` | Mit dem Spiel-HUD ausblenden | | `hudFollowGameHud` | `true` | Mit dem Spiel-HUD ausblenden |
| `hudToggleKey` | `''` | Eigene Taste, leer = unbelegt | | `hudToggleKey` | `''` | Eigene Taste, leer = unbelegt |
| `reservePressure` | `50` | Ab hier wird die Druckanzeige rot | | `reservePressure` | `50` | Roter Bereich des Finimeters, ab hier färbt sich der Zeiger |
| `depthDialMax` | `100.0` | Skalenende des Tiefenmessers. Tiefer schlägt der Zeiger an, die Zahl stimmt weiter |
**Lampe** **Lampe**
+6
View File
@@ -382,7 +382,13 @@ local function sendHudState(visible)
oxygen = oxygen, oxygen = oxygen,
capacity = capacity, capacity = capacity,
depth = currentDepth, depth = currentDepth,
-- Skalenwerte für die beiden Zeigerinstrumente. Kommen mit, damit die
-- Config an einer Stelle bleibt und das NUI nichts nachrechnen muss.
depthScale = config.depthDialMax,
depthLimit = activeTank and activeTank.maxDepth or nil,
pressure = pressure, pressure = pressure,
maxPressure = activeTank and activeTank.pressure or nil,
reservePressure = config.reservePressure,
reserve = pressure > 0 and pressure <= config.reservePressure, reserve = pressure > 0 and pressure <= config.reservePressure,
level = level, level = level,
deep = currentDepth >= config.deepWarningDepth, deep = currentDepth >= config.deepWarningDepth,
+5
View File
@@ -59,6 +59,11 @@ return {
--- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter). --- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter).
deepWarningDepth = 25.0, deepWarningDepth = 25.0,
--- Skalenende des Tiefenmessers (Meter). Echte Tiefenmesser gehen meist bis
--- 60 m; hier lieber so weit, dass auch Trimix-Tauchgänge auf der Skala
--- bleiben. Tiefer schlägt der Zeiger an, die Zahl in der Mitte stimmt weiter.
depthDialMax = 100.0,
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- Tiefenrausch -- Tiefenrausch
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
+86 -48
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Erzeugt die HUD-Renderings für die README aus denselben Werten wie nui/style.css. // Erzeugt die HUD-Renderings für die README aus denselben Werten wie nui/style.css.
// Keine In-Game-Screenshots - eine massstabsgetreue Nachbildung des Overlays. // Keine In-Game-Screenshots - eine massstabsgetreue Nachbildung der Konsole.
// //
// Nutzung: node docs/gen-hud-svg.mjs [ziel-ordner] (default: docs) // Nutzung: node docs/gen-hud-svg.mjs [ziel-ordner] (default: docs)
@@ -17,44 +17,78 @@ const C = {
text: '#e2e8f0', text: '#e2e8f0',
muted: '#94a3b8', muted: '#94a3b8',
panel: '#09121c', panel: '#09121c',
border: '#94a3b8' face: '#0b1622'
}; };
// Geometrie spiegelt das Grid aus style.css: const F = "'Segoe UI',Roboto,system-ui,sans-serif";
// 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; // Masse spiegeln das Grid aus style.css: padding 14/20, column-gap 20,
// Uhr 96, Ring 92, Beschriftung darunter, Lampe in Zeile 2.
const PAD_X = 20, PAD_Y = 14, DIAL = 96, GAUGE = 92, GAP = 20;
const ROW1 = 112, ROW_GAP = 6, LAMP_H = 23;
const PW = PAD_X * 2 + DIAL * 2 + GAUGE + GAP * 2;
const PH = PAD_Y * 2 + ROW1 + ROW_GAP + LAMP_H;
const W = 560, H = PH + 70;
const PX = (W - PW) / 2, PY = (H - PH) / 2; const PX = (W - PW) / 2, PY = (H - PH) / 2;
const GX = PX + PAD_X + COL + GAP; const LEFT_CX = PX + PAD_X + DIAL / 2;
const CX = GX + GAUGE / 2, CY = PY + PAD_Y + GAUGE / 2; const RIGHT_CX = PX + PW - PAD_X - DIAL / 2;
const MID_CX = PX + PW / 2;
const DIAL_CY = PY + PAD_Y + DIAL / 2;
const GAUGE_CY = PY + PAD_Y + ROW1 / 2;
const R = 52 * (GAUGE / 120); // r=52 im viewBox 120, auf 92px skaliert const n = (v) => Number(v.toFixed(2));
const SW = 8 * (GAUGE / 120); const rad = (d) => ((d - 90) * Math.PI) / 180;
const CIRC = 2 * Math.PI * R; const pt = (cx, cy, r, d) => [cx + r * Math.cos(rad(d)), cy + r * Math.sin(rad(d))];
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')}`; 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) { /** Zeigerinstrument. 270-Grad-Skala, unten offen - wie ein echtes Finimeter. */
const unitTspan = `<tspan font-size="13" font-weight="500" fill="${C.muted}" dx="3">${unit}</tspan>`; function dial(cx, cy, { value, max, unit, label, zoneFrom, zoneTo, needleColour, valueColour, decimals = 0 }) {
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> // 96px Uhr, Geometrie aus dem viewBox 0 0 100 100 skaliert
<text x="${x}" y="${VAL_Y}" text-anchor="${anchor}" font-family="${FONT}" font-size="26" font-weight="600" fill="${colour}">${value}${unitTspan}</text>`; const k = DIAL / 100;
const R = 42 * k, A0 = -135, SWEEP = 270;
const ang = (v) => A0 + SWEEP * Math.min(1, Math.max(0, v / (max || 1)));
let s = `<circle cx="${n(cx)}" cy="${n(cy)}" r="${n(46 * k)}" fill="${C.face}" stroke="${C.muted}" stroke-opacity="0.25" stroke-width="1.5"/>`;
if (zoneFrom !== undefined && zoneFrom < max) {
const a = ang(zoneFrom), b = ang(Math.min(zoneTo, max));
const [x1, y1] = pt(cx, cy, R - 2 * k, a);
const [x2, y2] = pt(cx, cy, R - 2 * k, b);
s += `<path d="M ${n(x1)} ${n(y1)} A ${n(R - 2 * k)} ${n(R - 2 * k)} 0 ${b - a > 180 ? 1 : 0} 1 ${n(x2)} ${n(y2)}" fill="none" stroke="${C.danger}" stroke-width="3.5" stroke-opacity="0.9"/>`;
} }
function build({ file, title, oxygen, capacity, depth, pressure, level, deep, reserve, lamp }) { for (let i = 0; i <= 10; i++) {
const a = A0 + (SWEEP * i) / 10, major = i % 2 === 0;
const [x1, y1] = pt(cx, cy, R - 6 * k, a);
const [x2, y2] = pt(cx, cy, (major ? R - 13 * k : R - 10 * k), a);
s += `<line x1="${n(x1)}" y1="${n(y1)}" x2="${n(x2)}" y2="${n(y2)}" stroke="#cbd5e1" stroke-opacity="${major ? 0.85 : 0.4}" stroke-width="${major ? 1.6 : 1}"/>`;
}
const [tipX, tipY] = pt(cx, cy, 33 * k, ang(value));
const [tailX, tailY] = pt(cx, cy, -8 * k, ang(value));
s += `<line x1="${n(tailX)}" y1="${n(tailY)}" x2="${n(tipX)}" y2="${n(tipY)}" stroke="${needleColour}" stroke-width="2.6" stroke-linecap="round"/>`;
s += `<circle cx="${n(cx)}" cy="${n(cy)}" r="${n(3.4 * k)}" fill="${needleColour}"/>`;
s += `<text x="${n(cx)}" y="${n(cy + 22)}" text-anchor="middle" font-size="16" font-weight="700" fill="${valueColour}" font-family="${F}">${value.toFixed(decimals)}<tspan font-size="10" font-weight="500" fill="${C.muted}" dx="2">${unit}</tspan></text>`;
s += `<text x="${n(cx)}" y="${n(cy + DIAL / 2 + 13)}" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="${C.muted}" font-family="${F}">${label}</text>`;
return s;
}
function build({ file, title, oxygen, capacity, level, depth, depthLimit, pressure, reservePressure, lamp, deep }) {
const ratio = Math.max(0, Math.min(1, oxygen / capacity)); const ratio = Math.max(0, Math.min(1, oxygen / capacity));
const ring = level === 'danger' ? C.danger : level === 'warn' ? C.warn : C.accent; const ringColour = level === 'danger' ? C.danger : level === 'warn' ? C.warn : C.accent;
const R = 52 * (GAUGE / 120), SW = 8 * (GAUGE / 120), CIRC = 2 * Math.PI * R;
const overLimit = depth >= depthLimit;
const depthColour = overLimit ? C.danger : deep ? C.warn : C.text;
const inReserve = pressure <= reservePressure;
const pressureColour = inReserve ? C.danger : C.text;
const lampColour = lamp ? C.accent : C.muted; 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}"> 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> <defs>
@@ -65,27 +99,30 @@ function build({ file, title, oxygen, capacity, depth, pressure, level, deep, re
</defs> </defs>
<rect width="${W}" height="${H}" fill="url(#water)"/> <rect width="${W}" height="${H}" fill="url(#water)"/>
<rect x="${n(PX)}" y="${n(PY)}" width="${PW}" height="${PH}" rx="16" fill="${C.panel}" fill-opacity="0.72" stroke="${C.muted}" stroke-opacity="0.18"/>
<rect x="${PX}" y="${PY}" width="${PW}" height="${PH}" rx="16" ${dial(LEFT_CX, DIAL_CY, {
fill="${C.panel}" fill-opacity="0.72" stroke="${C.border}" stroke-opacity="0.18"/> value: pressure, max: 300, unit: 'bar', label: 'DRUCK',
zoneFrom: 0, zoneTo: reservePressure,
needleColour: pressureColour, valueColour: pressureColour
})}
<g transform="rotate(-90 ${CX} ${CY})"> <g transform="rotate(-90 ${n(MID_CX)} ${n(GAUGE_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="${n(MID_CX)}" cy="${n(GAUGE_CY)}" r="${n(R)}" fill="none" stroke="${C.muted}" stroke-opacity="0.16" stroke-width="${n(SW)}" stroke-linecap="round"/>
<circle cx="${CX}" cy="${CY}" r="${R.toFixed(2)}" fill="none" stroke="${ring}" stroke-width="${SW.toFixed(2)}" stroke-linecap="round" <circle cx="${n(MID_CX)}" cy="${n(GAUGE_CY)}" r="${n(R)}" fill="none" stroke="${ringColour}" stroke-width="${n(SW)}" stroke-linecap="round" stroke-dasharray="${n(CIRC)}" stroke-dashoffset="${n(CIRC * (1 - ratio))}"/>
stroke-dasharray="${CIRC.toFixed(2)}" stroke-dashoffset="${(CIRC * (1 - ratio)).toFixed(2)}"/>
</g> </g>
<text x="${n(MID_CX)}" y="${n(GAUGE_CY - 1)}" text-anchor="middle" font-size="21" font-weight="600" fill="${ringColour}" font-family="${F}">${fmt(oxygen)}</text>
<text x="${n(MID_CX)}" y="${n(GAUGE_CY + 15)}" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="${C.muted}" font-family="${F}">LUFT</text>
<text x="${CX}" y="${CY - 1}" text-anchor="middle" font-family="${FONT}" font-size="21" font-weight="600" fill="${ring}">${fmt(oxygen)}</text> ${dial(RIGHT_CX, DIAL_CY, {
<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> value: depth, max: 100, unit: 'm', label: 'TIEFE', decimals: 1,
zoneFrom: depthLimit, zoneTo: 100,
needleColour: depthColour, valueColour: depthColour
})}
${readout(DEPTH_X, 'end', 'TIEFE', depth.toFixed(1), 'm', deep ? C.warn : C.text)} <rect x="${n(MID_CX - 43)}" y="${n(PY + PAD_Y + ROW1 + ROW_GAP)}" width="86" height="${LAMP_H}" rx="8" fill="${lampColour}" fill-opacity="${lamp ? 0.15 : 0.1}"/>
${readout(PRESS_X, 'start', 'DRUCK', String(Math.round(pressure)), 'bar', reserve ? C.danger : C.text)} <circle cx="${n(MID_CX - 29)}" cy="${n(PY + PAD_Y + ROW1 + ROW_GAP + LAMP_H / 2)}" r="4" fill="${lampColour}"/>
<text x="${n(MID_CX - 19)}" y="${n(PY + PAD_Y + ROW1 + ROW_GAP + LAMP_H / 2 + 3.5)}" font-size="10" font-weight="600" letter-spacing="1.2" fill="${lampColour}" font-family="${F}">LAMPE</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> </svg>
`; `;
@@ -95,17 +132,18 @@ function build({ file, title, oxygen, capacity, depth, pressure, level, deep, re
build({ build({
file: 'hud-normal.svg', title: 'Tauch-HUD: normaler Zustand', file: 'hud-normal.svg', title: 'Tauch-HUD: normaler Zustand',
oxygen: 545, capacity: 1200, depth: 12.4, pressure: 136, level: 'ok', lamp: true oxygen: 545, capacity: 1200, level: 'ok',
depth: 12.4, depthLimit: 45, pressure: 136, reservePressure: 50, lamp: true
}); });
build({ build({
file: 'hud-warnung.svg', title: 'Tauch-HUD: Warnstufe und Reservedruck', file: 'hud-warnung.svg', title: 'Tauch-HUD: Warnstufe und Reservedruck',
oxygen: 92, capacity: 900, depth: 27.5, pressure: 31, oxygen: 92, capacity: 900, level: 'warn',
level: 'warn', deep: true, reserve: true, lamp: false depth: 27.5, depthLimit: 45, deep: true, pressure: 31, reservePressure: 50, lamp: false
}); });
build({ build({
file: 'hud-kritisch.svg', title: 'Tauch-HUD: kritischer Luftstand', file: 'hud-kritisch.svg', title: 'Tauch-HUD: unter der Tiefengrenze des Atemgases',
oxygen: 8, capacity: 600, depth: 31.7, pressure: 4, oxygen: 38, capacity: 900, level: 'danger',
level: 'danger', deep: true, reserve: true, lamp: true depth: 52.4, depthLimit: 45, deep: true, pressure: 13, reservePressure: 50, lamp: true
}); });
+13 -20
View File
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 221" width="520" height="221" role="img" aria-label="Tauch-HUD: kritischer Luftstand"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 239" width="560" height="239" role="img" aria-label="Tauch-HUD: unter der Tiefengrenze des Atemgases">
<defs> <defs>
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1"> <linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
<stop offset="0%" stop-color="#0d3550"/> <stop offset="0%" stop-color="#0d3550"/>
@@ -6,28 +6,21 @@
</linearGradient> </linearGradient>
</defs> </defs>
<rect width="520" height="221" fill="url(#water)"/> <rect width="560" height="239" fill="url(#water)"/>
<rect x="98" y="35" width="364" height="169" rx="16" fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
<rect x="86" y="35" width="348" height="151" rx="16" <circle cx="166" cy="97" r="44.16" fill="#0b1622" stroke="#94a3b8" stroke-opacity="0.25" stroke-width="1.5"/><path d="M 138.85 124.15 A 38.4 38.4 0 0 1 127.6 97" fill="none" stroke="#f87171" stroke-width="3.5" stroke-opacity="0.9"/><line x1="141.56" y1="121.44" x2="146.31" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="133.13" y1="107.68" x2="136.78" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="131.87" y1="91.59" x2="138.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="138.04" y1="76.69" x2="141.15" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="150.31" y1="66.21" x2="153.36" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="166" y1="62.44" x2="166" y2="66.28" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="181.69" y1="66.21" x2="178.64" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="193.96" y1="76.69" x2="190.85" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="200.13" y1="91.59" x2="193.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="198.87" y1="107.68" x2="195.22" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="190.44" y1="121.44" x2="185.69" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="172.42" y1="92.78" x2="139.52" y2="114.39" stroke="#f87171" stroke-width="2.6" stroke-linecap="round"/><circle cx="166" cy="97" r="3.26" fill="#f87171"/><text x="166" y="119" text-anchor="middle" font-size="16" font-weight="700" fill="#f87171" font-family="'Segoe UI',Roboto,system-ui,sans-serif">13<tspan font-size="10" font-weight="500" fill="#94a3b8" dx="2">bar</tspan></text><text x="166" y="158" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">DRUCK</text>
fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
<g transform="rotate(-90 260 95)"> <g transform="rotate(-90 280 105)">
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/> <circle cx="280" cy="105" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/>
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#f87171" stroke-width="6.13" stroke-linecap="round" <circle cx="280" cy="105" r="39.87" fill="none" stroke="#f87171" stroke-width="6.13" stroke-linecap="round" stroke-dasharray="250.49" stroke-dashoffset="239.91"/>
stroke-dasharray="250.49" stroke-dashoffset="247.15"/>
</g> </g>
<text x="280" y="104" text-anchor="middle" font-size="21" font-weight="600" fill="#f87171" font-family="'Segoe UI',Roboto,system-ui,sans-serif">00:38</text>
<text x="280" y="120" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">LUFT</text>
<text x="260" y="94" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="21" font-weight="600" fill="#f87171">00:08</text> <circle cx="394" cy="97" r="44.16" fill="#0b1622" stroke="#94a3b8" stroke-opacity="0.25" stroke-width="1.5"/><path d="M 385.04 59.66 A 38.4 38.4 0 0 1 421.15 124.15" fill="none" stroke="#f87171" stroke-width="3.5" stroke-opacity="0.9"/><line x1="369.56" y1="121.44" x2="374.31" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="361.13" y1="107.68" x2="364.78" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="359.87" y1="91.59" x2="366.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="366.04" y1="76.69" x2="369.15" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="378.31" y1="66.21" x2="381.36" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="394" y1="62.44" x2="394" y2="66.28" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="409.69" y1="66.21" x2="406.64" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="421.96" y1="76.69" x2="418.85" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="428.13" y1="91.59" x2="421.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="426.87" y1="107.68" x2="423.22" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="418.44" y1="121.44" x2="413.69" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="393.13" y1="104.63" x2="397.58" y2="65.52" stroke="#f87171" stroke-width="2.6" stroke-linecap="round"/><circle cx="394" cy="97" r="3.26" fill="#f87171"/><text x="394" y="119" text-anchor="middle" font-size="16" font-weight="700" fill="#f87171" font-family="'Segoe UI',Roboto,system-ui,sans-serif">52.4<tspan font-size="10" font-weight="500" fill="#94a3b8" dx="2">m</tspan></text><text x="394" y="158" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">TIEFE</text>
<text x="260" y="110" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">LUFT</text>
<text x="192" y="84" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">TIEFE</text> <rect x="237" y="167" width="86" height="23" rx="8" fill="#38bdf8" fill-opacity="0.15"/>
<text x="192" y="110" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#fbbf24">31.7<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">m</tspan></text> <circle cx="251" cy="178.5" r="4" fill="#38bdf8"/>
<text x="328" y="84" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">DRUCK</text> <text x="261" y="182" font-size="10" font-weight="600" letter-spacing="1.2" fill="#38bdf8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">LAMPE</text>
<text x="328" y="110" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#f87171">4<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">bar</tspan></text>
<g>
<rect x="217" y="149" width="86" height="23" rx="8" fill="#38bdf8" fill-opacity="0.15"/>
<circle cx="231" cy="160.5" r="4" fill="#38bdf8"/>
<text x="241" y="164" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="10" font-weight="600" letter-spacing="1.2" fill="#38bdf8">LAMPE</text>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+13 -20
View File
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 221" width="520" height="221" role="img" aria-label="Tauch-HUD: normaler Zustand"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 239" width="560" height="239" role="img" aria-label="Tauch-HUD: normaler Zustand">
<defs> <defs>
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1"> <linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
<stop offset="0%" stop-color="#0d3550"/> <stop offset="0%" stop-color="#0d3550"/>
@@ -6,28 +6,21 @@
</linearGradient> </linearGradient>
</defs> </defs>
<rect width="520" height="221" fill="url(#water)"/> <rect width="560" height="239" fill="url(#water)"/>
<rect x="98" y="35" width="364" height="169" rx="16" fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
<rect x="86" y="35" width="348" height="151" rx="16" <circle cx="166" cy="97" r="44.16" fill="#0b1622" stroke="#94a3b8" stroke-opacity="0.25" stroke-width="1.5"/><path d="M 138.85 124.15 A 38.4 38.4 0 0 1 127.6 97" fill="none" stroke="#f87171" stroke-width="3.5" stroke-opacity="0.9"/><line x1="141.56" y1="121.44" x2="146.31" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="133.13" y1="107.68" x2="136.78" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="131.87" y1="91.59" x2="138.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="138.04" y1="76.69" x2="141.15" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="150.31" y1="66.21" x2="153.36" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="166" y1="62.44" x2="166" y2="66.28" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="181.69" y1="66.21" x2="178.64" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="193.96" y1="76.69" x2="190.85" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="200.13" y1="91.59" x2="193.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="198.87" y1="107.68" x2="195.22" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="190.44" y1="121.44" x2="185.69" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="167.68" y1="104.5" x2="159.09" y2="66.08" stroke="#e2e8f0" stroke-width="2.6" stroke-linecap="round"/><circle cx="166" cy="97" r="3.26" fill="#e2e8f0"/><text x="166" y="119" text-anchor="middle" font-size="16" font-weight="700" fill="#e2e8f0" font-family="'Segoe UI',Roboto,system-ui,sans-serif">136<tspan font-size="10" font-weight="500" fill="#94a3b8" dx="2">bar</tspan></text><text x="166" y="158" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">DRUCK</text>
fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
<g transform="rotate(-90 260 95)"> <g transform="rotate(-90 280 105)">
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/> <circle cx="280" cy="105" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/>
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#38bdf8" stroke-width="6.13" stroke-linecap="round" <circle cx="280" cy="105" r="39.87" fill="none" stroke="#38bdf8" stroke-width="6.13" stroke-linecap="round" stroke-dasharray="250.49" stroke-dashoffset="136.73"/>
stroke-dasharray="250.49" stroke-dashoffset="136.73"/>
</g> </g>
<text x="280" y="104" text-anchor="middle" font-size="21" font-weight="600" fill="#38bdf8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">09:05</text>
<text x="280" y="120" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">LUFT</text>
<text x="260" y="94" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="21" font-weight="600" fill="#38bdf8">09:05</text> <circle cx="394" cy="97" r="44.16" fill="#0b1622" stroke="#94a3b8" stroke-opacity="0.25" stroke-width="1.5"/><path d="M 385.04 59.66 A 38.4 38.4 0 0 1 421.15 124.15" fill="none" stroke="#f87171" stroke-width="3.5" stroke-opacity="0.9"/><line x1="369.56" y1="121.44" x2="374.31" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="361.13" y1="107.68" x2="364.78" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="359.87" y1="91.59" x2="366.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="366.04" y1="76.69" x2="369.15" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="378.31" y1="66.21" x2="381.36" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="394" y1="62.44" x2="394" y2="66.28" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="409.69" y1="66.21" x2="406.64" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="421.96" y1="76.69" x2="418.85" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="428.13" y1="91.59" x2="421.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="426.87" y1="107.68" x2="423.22" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="418.44" y1="121.44" x2="413.69" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="401.53" y1="95.47" x2="362.96" y2="103.33" stroke="#e2e8f0" stroke-width="2.6" stroke-linecap="round"/><circle cx="394" cy="97" r="3.26" fill="#e2e8f0"/><text x="394" y="119" text-anchor="middle" font-size="16" font-weight="700" fill="#e2e8f0" font-family="'Segoe UI',Roboto,system-ui,sans-serif">12.4<tspan font-size="10" font-weight="500" fill="#94a3b8" dx="2">m</tspan></text><text x="394" y="158" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">TIEFE</text>
<text x="260" y="110" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">LUFT</text>
<text x="192" y="84" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">TIEFE</text> <rect x="237" y="167" width="86" height="23" rx="8" fill="#38bdf8" fill-opacity="0.15"/>
<text x="192" y="110" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#e2e8f0">12.4<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">m</tspan></text> <circle cx="251" cy="178.5" r="4" fill="#38bdf8"/>
<text x="328" y="84" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">DRUCK</text> <text x="261" y="182" font-size="10" font-weight="600" letter-spacing="1.2" fill="#38bdf8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">LAMPE</text>
<text x="328" y="110" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#e2e8f0">136<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">bar</tspan></text>
<g>
<rect x="217" y="149" width="86" height="23" rx="8" fill="#38bdf8" fill-opacity="0.15"/>
<circle cx="231" cy="160.5" r="4" fill="#38bdf8"/>
<text x="241" y="164" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="10" font-weight="600" letter-spacing="1.2" fill="#38bdf8">LAMPE</text>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+13 -20
View File
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 221" width="520" height="221" role="img" aria-label="Tauch-HUD: Warnstufe und Reservedruck"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 239" width="560" height="239" role="img" aria-label="Tauch-HUD: Warnstufe und Reservedruck">
<defs> <defs>
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1"> <linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
<stop offset="0%" stop-color="#0d3550"/> <stop offset="0%" stop-color="#0d3550"/>
@@ -6,28 +6,21 @@
</linearGradient> </linearGradient>
</defs> </defs>
<rect width="520" height="221" fill="url(#water)"/> <rect width="560" height="239" fill="url(#water)"/>
<rect x="98" y="35" width="364" height="169" rx="16" fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
<rect x="86" y="35" width="348" height="151" rx="16" <circle cx="166" cy="97" r="44.16" fill="#0b1622" stroke="#94a3b8" stroke-opacity="0.25" stroke-width="1.5"/><path d="M 138.85 124.15 A 38.4 38.4 0 0 1 127.6 97" fill="none" stroke="#f87171" stroke-width="3.5" stroke-opacity="0.9"/><line x1="141.56" y1="121.44" x2="146.31" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="133.13" y1="107.68" x2="136.78" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="131.87" y1="91.59" x2="138.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="138.04" y1="76.69" x2="141.15" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="150.31" y1="66.21" x2="153.36" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="166" y1="62.44" x2="166" y2="66.28" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="181.69" y1="66.21" x2="178.64" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="193.96" y1="76.69" x2="190.85" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="200.13" y1="91.59" x2="193.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="198.87" y1="107.68" x2="195.22" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="190.44" y1="121.44" x2="185.69" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="173.34" y1="94.74" x2="135.72" y2="106.32" stroke="#f87171" stroke-width="2.6" stroke-linecap="round"/><circle cx="166" cy="97" r="3.26" fill="#f87171"/><text x="166" y="119" text-anchor="middle" font-size="16" font-weight="700" fill="#f87171" font-family="'Segoe UI',Roboto,system-ui,sans-serif">31<tspan font-size="10" font-weight="500" fill="#94a3b8" dx="2">bar</tspan></text><text x="166" y="158" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">DRUCK</text>
fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
<g transform="rotate(-90 260 95)"> <g transform="rotate(-90 280 105)">
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/> <circle cx="280" cy="105" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/>
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#fbbf24" stroke-width="6.13" stroke-linecap="round" <circle cx="280" cy="105" r="39.87" fill="none" stroke="#fbbf24" stroke-width="6.13" stroke-linecap="round" stroke-dasharray="250.49" stroke-dashoffset="224.88"/>
stroke-dasharray="250.49" stroke-dashoffset="224.88"/>
</g> </g>
<text x="280" y="104" text-anchor="middle" font-size="21" font-weight="600" fill="#fbbf24" font-family="'Segoe UI',Roboto,system-ui,sans-serif">01:32</text>
<text x="280" y="120" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">LUFT</text>
<text x="260" y="94" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="21" font-weight="600" fill="#fbbf24">01:32</text> <circle cx="394" cy="97" r="44.16" fill="#0b1622" stroke="#94a3b8" stroke-opacity="0.25" stroke-width="1.5"/><path d="M 385.04 59.66 A 38.4 38.4 0 0 1 421.15 124.15" fill="none" stroke="#f87171" stroke-width="3.5" stroke-opacity="0.9"/><line x1="369.56" y1="121.44" x2="374.31" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="361.13" y1="107.68" x2="364.78" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="359.87" y1="91.59" x2="366.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="366.04" y1="76.69" x2="369.15" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="378.31" y1="66.21" x2="381.36" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="394" y1="62.44" x2="394" y2="66.28" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="409.69" y1="66.21" x2="406.64" y2="72.19" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="421.96" y1="76.69" x2="418.85" y2="78.94" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="428.13" y1="91.59" x2="421.5" y2="92.64" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="426.87" y1="107.68" x2="423.22" y2="106.49" stroke="#cbd5e1" stroke-opacity="0.4" stroke-width="1"/><line x1="418.44" y1="121.44" x2="413.69" y2="116.69" stroke="#cbd5e1" stroke-opacity="0.85" stroke-width="1.6"/><line x1="400.7" y1="100.75" x2="366.36" y2="81.52" stroke="#fbbf24" stroke-width="2.6" stroke-linecap="round"/><circle cx="394" cy="97" r="3.26" fill="#fbbf24"/><text x="394" y="119" text-anchor="middle" font-size="16" font-weight="700" fill="#fbbf24" font-family="'Segoe UI',Roboto,system-ui,sans-serif">27.5<tspan font-size="10" font-weight="500" fill="#94a3b8" dx="2">m</tspan></text><text x="394" y="158" text-anchor="middle" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">TIEFE</text>
<text x="260" y="110" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">LUFT</text>
<text x="192" y="84" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">TIEFE</text> <rect x="237" y="167" width="86" height="23" rx="8" fill="#94a3b8" fill-opacity="0.1"/>
<text x="192" y="110" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#fbbf24">27.5<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">m</tspan></text> <circle cx="251" cy="178.5" r="4" fill="#94a3b8"/>
<text x="328" y="84" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">DRUCK</text> <text x="261" y="182" font-size="10" font-weight="600" letter-spacing="1.2" fill="#94a3b8" font-family="'Segoe UI',Roboto,system-ui,sans-serif">LAMPE</text>
<text x="328" y="110" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#f87171">31<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">bar</tspan></text>
<g>
<rect x="217" y="149" width="86" height="23" rx="8" fill="#94a3b8" fill-opacity="0.1"/>
<circle cx="231" cy="160.5" r="4" fill="#94a3b8"/>
<text x="241" y="164" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="10" font-weight="600" letter-spacing="1.2" fill="#94a3b8">LAMPE</text>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+31 -6
View File
@@ -6,11 +6,24 @@
</head> </head>
<body> <body>
<div id="hud" class="hidden"> <div id="hud" class="hidden">
<div class="readout readout--right"> <!-- Finimeter: Restdruck der Flasche, roter Bereich = Reserve -->
<span id="depth-label" class="caption">TIEFE</span> <div class="dial" id="pressure">
<div class="value"><span id="depth-value">0.0</span><span class="unit">m</span></div> <div class="dial-face">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle class="face" cx="50" cy="50" r="46"></circle>
<path class="zone" id="pressure-zone"></path>
<g class="ticks" id="pressure-ticks"></g>
<g class="needle" id="pressure-needle">
<line x1="50" y1="58" x2="50" y2="17"></line>
<circle cx="50" cy="50" r="3.4"></circle>
</g>
</svg>
<div class="dial-value"><span id="pressure-value">0</span><span class="unit">bar</span></div>
</div>
<span id="pressure-label" class="caption">DRUCK</span>
</div> </div>
<!-- Restluft als Ring, die Zahl auf die es ankommt -->
<div class="gauge"> <div class="gauge">
<svg viewBox="0 0 120 120" aria-hidden="true"> <svg viewBox="0 0 120 120" aria-hidden="true">
<circle class="gauge-track" cx="60" cy="60" r="52"></circle> <circle class="gauge-track" cx="60" cy="60" r="52"></circle>
@@ -22,9 +35,21 @@
</div> </div>
</div> </div>
<div class="readout" id="pressure"> <!-- Tiefenmesser: roter Bereich = Tiefengrenze des Atemgases -->
<span id="pressure-label" class="caption">DRUCK</span> <div class="dial" id="depth">
<div class="value"><span id="pressure-value">0</span><span class="unit">bar</span></div> <div class="dial-face">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle class="face" cx="50" cy="50" r="46"></circle>
<path class="zone" id="depth-zone"></path>
<g class="ticks" id="depth-ticks"></g>
<g class="needle" id="depth-needle">
<line x1="50" y1="58" x2="50" y2="17"></line>
<circle cx="50" cy="50" r="3.4"></circle>
</g>
</svg>
<div class="dial-value"><span id="depth-value">0.0</span><span class="unit">m</span></div>
</div>
<span id="depth-label" class="caption">TIEFE</span>
</div> </div>
<div id="lamp" class="lamp"> <div id="lamp" class="lamp">
+101 -10
View File
@@ -1,17 +1,36 @@
// Das HUD ist reine Anzeige - es sendet nichts zurueck an den Client. // Das HUD ist reine Anzeige - es sendet nichts zurueck an den Client.
// Der Lua-Client schickt den kompletten Zustand, damit die Locale-Texte an einer // Der Lua-Client schickt den kompletten Zustand samt Skalenendwerten, damit die
// Stelle bleiben und hier nichts uebersetzt werden muss. // Locale-Texte und die Config an einer Stelle bleiben.
const RING = 327; // 2 * PI * 52, muss zu --ring in style.css passen const RING = 327; // 2 * PI * 52, muss zu --ring in style.css passen
// Zeigerinstrument: 270-Grad-Skala, unten offen - wie ein echtes Finimeter.
const DIAL_START = -135;
const DIAL_SWEEP = 270;
const DIAL_R = 42;
const hud = document.getElementById('hud'); const hud = document.getElementById('hud');
const gaugeFill = document.getElementById('gauge-fill'); const gaugeFill = document.getElementById('gauge-fill');
const airValue = document.getElementById('air-value'); const airValue = document.getElementById('air-value');
const depthValue = document.getElementById('depth-value');
const pressure = document.getElementById('pressure');
const pressureValue = document.getElementById('pressure-value');
const lamp = document.getElementById('lamp'); const lamp = document.getElementById('lamp');
const dials = {
pressure: {
root: document.getElementById('pressure'),
needle: document.getElementById('pressure-needle'),
zone: document.getElementById('pressure-zone'),
ticks: document.getElementById('pressure-ticks'),
value: document.getElementById('pressure-value')
},
depth: {
root: document.getElementById('depth'),
needle: document.getElementById('depth-needle'),
zone: document.getElementById('depth-zone'),
ticks: document.getElementById('depth-ticks'),
value: document.getElementById('depth-value')
}
};
const labels = { const labels = {
air: document.getElementById('air-label'), air: document.getElementById('air-label'),
depth: document.getElementById('depth-label'), depth: document.getElementById('depth-label'),
@@ -19,6 +38,67 @@ const labels = {
lamp: document.getElementById('lamp-label') lamp: document.getElementById('lamp-label')
}; };
const NS = 'http://www.w3.org/2000/svg';
const toRad = (deg) => ((deg - 90) * Math.PI) / 180;
function pointOn(radius, deg) {
const r = toRad(deg);
return [50 + radius * Math.cos(r), 50 + radius * Math.sin(r)];
}
/** Winkel für einen Messwert auf der Skala. */
function angleFor(value, max) {
const ratio = Math.min(1, Math.max(0, value / (max || 1)));
return DIAL_START + DIAL_SWEEP * ratio;
}
/** Striche einmalig zeichnen - die ändern sich nie. */
function buildTicks(group) {
for (let i = 0; i <= 10; i++) {
const deg = DIAL_START + (DIAL_SWEEP * i) / 10;
const major = i % 2 === 0;
const [x1, y1] = pointOn(DIAL_R - 6, deg);
const [x2, y2] = pointOn(major ? DIAL_R - 13 : DIAL_R - 10, deg);
const line = document.createElementNS(NS, 'line');
line.setAttribute('x1', x1.toFixed(2));
line.setAttribute('y1', y1.toFixed(2));
line.setAttribute('x2', x2.toFixed(2));
line.setAttribute('y2', y2.toFixed(2));
if (major) line.setAttribute('class', 'major');
group.appendChild(line);
}
}
buildTicks(dials.pressure.ticks);
buildTicks(dials.depth.ticks);
/** Roter Bereich. Beim Finimeter die Reserve, beim Tiefenmesser die Grenze des Atemgases. */
function setZone(dial, from, to, max) {
if (!(max > 0) || from === undefined || from >= max) {
dial.zone.removeAttribute('d');
return;
}
const a = angleFor(from, max);
const b = angleFor(Math.min(to, max), max);
const [x1, y1] = pointOn(DIAL_R - 2, a);
const [x2, y2] = pointOn(DIAL_R - 2, b);
const large = b - a > 180 ? 1 : 0;
dial.zone.setAttribute(
'd',
`M ${x1.toFixed(2)} ${y1.toFixed(2)} A ${DIAL_R - 2} ${DIAL_R - 2} 0 ${large} 1 ${x2.toFixed(2)} ${y2.toFixed(2)}`
);
}
function setNeedle(dial, value, max) {
dial.needle.setAttribute('transform', `rotate(${angleFor(value, max).toFixed(2)} 50 50)`);
}
function formatTime(seconds) { function formatTime(seconds) {
const total = Math.max(0, Math.floor(seconds)); const total = Math.max(0, Math.floor(seconds));
const mm = String(Math.floor(total / 60)).padStart(2, '0'); const mm = String(Math.floor(total / 60)).padStart(2, '0');
@@ -36,17 +116,26 @@ function render(state) {
gaugeFill.style.strokeDashoffset = String(RING * (1 - ratio)); gaugeFill.style.strokeDashoffset = String(RING * (1 - ratio));
airValue.textContent = formatTime(state.oxygen); airValue.textContent = formatTime(state.oxygen);
depthValue.textContent = state.depth.toFixed(1);
pressureValue.textContent = String(Math.round(state.pressure ?? 0));
pressure.classList.toggle('reserve', !!state.reserve);
// Warnstufen kommen als Restsekunden vom Client, damit sie zu // Warnstufen kommen als Restsekunden vom Client, damit sie zu
// config.warnAtSeconds passen statt hier nochmal definiert zu werden. // config.warnAtSeconds passen statt hier nochmal definiert zu werden.
hud.classList.toggle('warn', state.level === 'warn'); hud.classList.toggle('warn', state.level === 'warn');
hud.classList.toggle('danger', state.level === 'danger'); hud.classList.toggle('danger', state.level === 'danger');
hud.classList.toggle('deep', !!state.deep);
hud.classList.toggle('overlimit', !!state.overLimit); hud.classList.toggle('overlimit', !!state.overLimit);
const maxPressure = state.maxPressure || 300;
dials.pressure.value.textContent = String(Math.round(state.pressure ?? 0));
setNeedle(dials.pressure, state.pressure ?? 0, maxPressure);
setZone(dials.pressure, 0, state.reservePressure ?? 0, maxPressure);
dials.pressure.root.classList.toggle('alert', !!state.reserve);
const depthScale = state.depthScale || 100;
dials.depth.value.textContent = state.depth.toFixed(1);
setNeedle(dials.depth, state.depth, depthScale);
setZone(dials.depth, state.depthLimit, depthScale, depthScale);
dials.depth.root.classList.toggle('alert', !!state.overLimit);
dials.depth.root.classList.toggle('deep', !state.overLimit && !!state.deep);
lamp.classList.toggle('hidden', !state.hasLamp); lamp.classList.toggle('hidden', !state.hasLamp);
lamp.classList.toggle('on', !!state.lamp); lamp.classList.toggle('on', !!state.lamp);
} }
@@ -70,7 +159,9 @@ window.addEventListener('message', (event) => {
// Damit sich das HUD im Browser ohne FiveM anschauen laesst. // Damit sich das HUD im Browser ohne FiveM anschauen laesst.
if (typeof GetParentResourceName !== 'function') { if (typeof GetParentResourceName !== 'function') {
render({ render({
visible: true, oxygen: 372, capacity: 600, depth: 12.4, pressure: 186, visible: true, oxygen: 372, capacity: 600,
depth: 27.5, depthScale: 100, depthLimit: 45, deep: true,
pressure: 186, maxPressure: 300, reservePressure: 50,
level: 'ok', hasLamp: true, lamp: true level: 'ok', hasLamp: true, lamp: true
}); });
} }
+126 -72
View File
@@ -8,7 +8,8 @@
--text: #e2e8f0; --text: #e2e8f0;
--muted: #94a3b8; --muted: #94a3b8;
--panel: rgba(9, 18, 28, 0.72); --panel: rgba(9, 18, 28, 0.72);
--ring: 327; /* 2 * PI * 52, Umfang des Gauge-Kreises */ --dial-face: #0b1622;
--ring: 327; /* 2 * PI * 52, Umfang des Luft-Rings */
} }
* { * {
@@ -24,9 +25,9 @@ body {
user-select: none; user-select: none;
} }
/* Grid statt Flex: so sitzen Tiefe, Ring und Druck garantiert auf einer /* Dreier-Konsole: Finimeter, Luft, Tiefenmesser nebeneinander, Lampe darunter.
gemeinsamen Mittelachse, und die Lampen-Leiste hängt in einer zweiten Zeile Grid statt Flex, damit die drei Instrumente auf einer Mittelachse sitzen und
mittig unter dem Ring - ohne Ausgleichs-Margins. */ die Lampen-Leiste ohne Ausgleichs-Margins in Zeile 2 haengt. */
#hud { #hud {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -37,9 +38,9 @@ body {
grid-template-rows: auto auto; grid-template-rows: auto auto;
align-items: center; align-items: center;
justify-items: center; justify-items: center;
column-gap: 22px; column-gap: 20px;
row-gap: 8px; row-gap: 6px;
padding: 14px 22px; padding: 14px 20px;
border-radius: 16px; border-radius: 16px;
background: var(--panel); background: var(--panel);
border: 1px solid rgba(148, 163, 184, 0.18); border: 1px solid rgba(148, 163, 184, 0.18);
@@ -54,7 +55,119 @@ body {
pointer-events: none; pointer-events: none;
} }
/* --- Luft-Gauge --- */ .caption {
font-size: 9px;
font-weight: 600;
letter-spacing: 1.4px;
color: var(--muted);
text-transform: uppercase;
}
/* --- Zeigerinstrumente --- */
.dial {
grid-row: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
#pressure { grid-column: 1; }
#depth { grid-column: 3; }
.dial-face {
position: relative;
width: 96px;
height: 96px;
}
.dial-face svg {
width: 100%;
height: 100%;
}
.dial .face {
fill: var(--dial-face);
stroke: rgba(148, 163, 184, 0.25);
stroke-width: 1.5;
}
/* Skalenzahlen gibt es bewusst nicht: bei dieser Groesse liest die niemand.
Der Zeiger zeigt die Tendenz, die Zahl in der Mitte den genauen Wert. */
.dial .ticks line {
stroke: #cbd5e1;
stroke-opacity: 0.4;
stroke-width: 1;
}
.dial .ticks line.major {
stroke-opacity: 0.85;
stroke-width: 1.6;
}
.dial .zone {
fill: none;
stroke: var(--danger);
stroke-width: 3.5;
stroke-opacity: 0.9;
}
.dial .needle line {
stroke: var(--text);
stroke-width: 2.6;
stroke-linecap: round;
transition: stroke 260ms ease;
}
.dial .needle circle {
fill: var(--text);
transition: fill 260ms ease;
}
/* Kein transform-origin hier: das rotate(winkel 50 50) im transform-Attribut
bringt den Drehpunkt schon mit. Beides zusammen verschiebt den Zeiger aus der
Uhr heraus - CSS rechnet die Origin nochmal obendrauf. */
.dial .needle {
transition: transform 260ms ease-out;
}
.dial-value {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
/* In den offenen unteren Teil der Skala, damit die Zahl nicht am
Zeiger-Drehpunkt klebt - wie das Sichtfenster eines echten Instruments. */
padding-top: 36px;
font-size: 16px;
font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1;
transition: color 260ms ease;
}
.unit {
font-size: 10px;
font-weight: 500;
color: var(--muted);
}
/* Zeiger und Wert faerben sich, sobald der rote Bereich erreicht ist. */
#pressure.alert .needle line,
#depth.alert .needle line { stroke: var(--danger); }
#pressure.alert .needle circle,
#depth.alert .needle circle { fill: var(--danger); }
#pressure.alert .dial-value,
#depth.alert .dial-value { color: var(--danger); }
#depth.deep .dial-value { color: var(--warn); }
#depth.deep .needle line { stroke: var(--warn); }
#depth.deep .needle circle { fill: var(--warn); }
/* --- Luft-Ring --- */
.gauge { .gauge {
position: relative; position: relative;
@@ -105,56 +218,7 @@ body {
line-height: 1; line-height: 1;
} }
.caption { /* --- Lampe --- */
font-size: 9px;
font-weight: 600;
letter-spacing: 1.4px;
color: var(--muted);
text-transform: uppercase;
}
/* --- Tiefe + Lampe --- */
.readout {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 84px;
grid-row: 1;
}
/* Tiefe spiegelt den Druck: Werte zeigen jeweils zum Ring hin. */
.readout--right {
align-items: flex-end;
grid-column: 1;
justify-self: end;
}
.readout--right .value {
justify-content: flex-end;
}
#pressure {
grid-column: 3;
justify-self: start;
}
.value {
display: flex;
align-items: baseline;
gap: 3px;
font-size: 26px;
font-weight: 600;
font-variant-numeric: tabular-nums;
line-height: 1;
transition: color 260ms ease;
}
.unit {
font-size: 13px;
font-weight: 500;
color: var(--muted);
}
.lamp { .lamp {
grid-column: 2; grid-column: 2;
@@ -179,16 +243,14 @@ body {
fill: currentColor; fill: currentColor;
} }
.lamp.hidden { .lamp.hidden { display: none; }
display: none;
}
.lamp.on { .lamp.on {
background: var(--accent-dim); background: var(--accent-dim);
color: var(--accent); color: var(--accent);
} }
/* --- Warnzustaende --- */ /* --- Warnzustaende der Restluft --- */
#hud.warn .gauge-fill { stroke: var(--warn); } #hud.warn .gauge-fill { stroke: var(--warn); }
#hud.warn #air-value { color: var(--warn); } #hud.warn #air-value { color: var(--warn); }
@@ -197,16 +259,8 @@ body {
#hud.danger #air-value { color: var(--danger); } #hud.danger #air-value { color: var(--danger); }
#hud.danger .gauge { animation: pulse 1s ease-in-out infinite; } #hud.danger .gauge { animation: pulse 1s ease-in-out infinite; }
#hud.deep #depth-value { color: var(--warn); } /* Tiefengrenze des Atemgases ueberschritten - Tiefenrausch laeuft. */
#hud.overlimit #depth .dial-face { animation: pulse 1s ease-in-out infinite; }
/* Tiefengrenze des Atemgases überschritten - Tiefenrausch läuft. */
#hud.overlimit #depth-value {
color: var(--danger);
animation: pulse 1s ease-in-out infinite;
}
/* Reservedruck: in der Tauchpraxis die Schwelle zum Aufstieg. */
#pressure.reserve .value { color: var(--danger); }
@keyframes pulse { @keyframes pulse {
0%, 100% { opacity: 1; } 0%, 100% { opacity: 1; }