Map-Bilder und Seitenwahl mit Emblemen
Map-Bilder aus ghostcap-gaming/cs2-map-images lokal als WebP gespeichert (npm run images lädt und verkleinert sie). Bilder in Veto-Kacheln, Pool-Vorschau, Serie und Seitenwahl; CT/T-Embleme aus client/public/sides mit Ersatzsymbol, falls die Dateien fehlen.
This commit is contained in:
1 parent
e304556346
commit
7be0d85ab5
21 files changed
+701
-27
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
/**
|
||||
* Map-Bild aus der Konfiguration (map.image). Lädt das Bild nicht, bleibt das
|
||||
* gezeichnete Muster in Map-Farbe darunter sichtbar.
|
||||
*/
|
||||
export default function MapImage({ map, className = '' }) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
return (
|
||||
<span className={`map-art ${className}`} style={{ '--map': map?.color ?? '#556' }} aria-hidden="true">
|
||||
{map?.image && !failed && (
|
||||
// Kein loading="lazy": in Kacheln mit clip-path zeichnet Chromium das Bild nach dem Laden sonst nicht neu.
|
||||
<img src={map.image} alt="" onError={() => setFailed(true)} />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import MapImage from './MapImage.jsx';
|
||||
import SideIcon from './SideIcon.jsx';
|
||||
|
||||
const STATUS = { pending: 'Ausstehend', next: 'Wird eingerichtet', live: 'Aktuell', done: 'Gespielt' };
|
||||
|
||||
export default function SeriesList({ state }) {
|
||||
@@ -12,13 +15,19 @@ export default function SeriesList({ state }) {
|
||||
const t = s.ctTeam ? teams[s.ctTeam === 'A' ? 'B' : 'A'].name : null;
|
||||
return (
|
||||
<li key={s.number} className={`series-item is-${s.status}`} style={{ '--map': map?.color ?? '#556' }}>
|
||||
<MapImage map={map} className="series-art" />
|
||||
<span className="series-no">Map {s.number}</span>
|
||||
<strong className="series-map">{s.mapName}</strong>
|
||||
<span className="series-origin">
|
||||
{s.pickedBy ? `Pick von ${teams[s.pickedBy].name}` : 'Entscheidungsmap'}
|
||||
</span>
|
||||
<span className="series-sides">
|
||||
{ct ? <><span className="side-ct">CT: {ct}</span> <span className="side-t">T: {t}</span></> : 'Seiten im Spiel festlegen'}
|
||||
{ct ? (
|
||||
<>
|
||||
<span className="side-ct"><SideIcon side="CT" size={18} /> {ct}</span>
|
||||
<span className="side-t"><SideIcon side="T" size={18} /> {t}</span>
|
||||
</>
|
||||
) : 'Seiten im Spiel festlegen'}
|
||||
</span>
|
||||
<span className="series-status">{STATUS[s.status]}</span>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
// Emblem-Dateien in client/public/sides/. Fehlen sie, wird ein schlichtes Ersatzsymbol gezeichnet.
|
||||
const IMAGES = { CT: '/sides/ct.png', T: '/sides/t.png' };
|
||||
|
||||
export default function SideIcon({ side, size = 28 }) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
if (!failed) {
|
||||
return (
|
||||
<img className={`side-icon side-icon-${side.toLowerCase()}`} src={IMAGES[side]} width={size} height={size}
|
||||
alt="" aria-hidden="true" onError={() => setFailed(true)} />
|
||||
);
|
||||
}
|
||||
return <FallbackIcon side={side} size={size} />;
|
||||
}
|
||||
|
||||
function FallbackIcon({ side, size }) {
|
||||
if (side === 'CT') {
|
||||
return (
|
||||
<svg className="side-icon side-icon-ct" viewBox="0 0 32 32" width={size} height={size} aria-hidden="true">
|
||||
<path d="M16 2 4 6.5v8.2C4 22.3 9.1 28 16 30c6.9-2 12-7.7 12-15.3V6.5L16 2z" fill="currentColor" opacity=".22" />
|
||||
<path d="M16 2 4 6.5v8.2C4 22.3 9.1 28 16 30c6.9-2 12-7.7 12-15.3V6.5L16 2z" fill="none" stroke="currentColor" strokeWidth="2" strokeLinejoin="round" />
|
||||
<path d="m16 9 2.1 4.3 4.7.7-3.4 3.3.8 4.7-4.2-2.2-4.2 2.2.8-4.7-3.4-3.3 4.7-.7z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<svg className="side-icon side-icon-t" viewBox="0 0 32 32" width={size} height={size} aria-hidden="true">
|
||||
<rect x="5" y="11" width="22" height="15" rx="2.5" fill="currentColor" opacity=".22" />
|
||||
<rect x="5" y="11" width="22" height="15" rx="2.5" fill="none" stroke="currentColor" strokeWidth="2" />
|
||||
<rect x="9" y="15" width="9" height="5" rx="1" fill="currentColor" />
|
||||
<circle cx="22.5" cy="17.5" r="1.8" fill="currentColor" />
|
||||
<path d="M11 11V8.5a2.5 2.5 0 0 1 2.5-2.5h2c1.4 0 2.5-1.1 2.5-2.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { clock } from '../lib/labels.js';
|
||||
import MapImage from './MapImage.jsx';
|
||||
import SideIcon from './SideIcon.jsx';
|
||||
|
||||
const STEP_SHORT = { ban: 'Ban', pick: 'Pick', side: 'Seite', decider: 'Decider' };
|
||||
|
||||
@@ -57,7 +59,7 @@ export default function VetoBoard({ state, viewer, act, busy, compact = false })
|
||||
aria-pressed={isSel}
|
||||
aria-label={`${meta.name}: ${statusText(m, teams)}`}
|
||||
>
|
||||
<span className="map-art" aria-hidden="true" />
|
||||
<MapImage map={meta} />
|
||||
<span className="map-name">{meta.name}</span>
|
||||
<span className="map-status">{statusText(m, teams)}</span>
|
||||
</button>
|
||||
@@ -77,10 +79,21 @@ export default function VetoBoard({ state, viewer, act, busy, compact = false })
|
||||
|
||||
{yourTurn && current.type === 'side' && (
|
||||
<div className="side-choice">
|
||||
<p>Auf welcher Seite startet {teams[viewer.team].name} auf {sideMap?.name}?</p>
|
||||
<div className="row">
|
||||
<button className="btn btn-ct btn-lg" disabled={!!busy} onClick={() => submitSide('CT')}>Counter-Terrorists</button>
|
||||
<button className="btn btn-t btn-lg" disabled={!!busy} onClick={() => submitSide('T')}>Terrorists</button>
|
||||
<MapImage map={sideMap} className="side-choice-art" />
|
||||
<div className="side-choice-body">
|
||||
<p>Auf welcher Seite startet <strong>{teams[viewer.team].name}</strong> auf <strong>{sideMap?.name}</strong>?</p>
|
||||
<div className="side-options">
|
||||
<button className="side-option side-option-ct" disabled={!!busy} onClick={() => submitSide('CT')}>
|
||||
<SideIcon side="CT" size={44} />
|
||||
<span className="side-option-name">Counter-Terrorists</span>
|
||||
<span className="side-option-sub">Start auf CT, Gegner beginnt als T</span>
|
||||
</button>
|
||||
<button className="side-option side-option-t" disabled={!!busy} onClick={() => submitSide('T')}>
|
||||
<SideIcon side="T" size={44} />
|
||||
<span className="side-option-name">Terrorists</span>
|
||||
<span className="side-option-sub">Start auf T, Gegner beginnt als CT</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user