133 lines
8.1 KiB
Svelte
133 lines
8.1 KiB
Svelte
<script lang="ts">
|
|
import ItemIcon from '$lib/components/ItemIcon.svelte';
|
|
|
|
export type MatrixCell = {
|
|
id: string;
|
|
label: string;
|
|
name: string;
|
|
meta: string;
|
|
summary: string;
|
|
iconUrl?: string;
|
|
issue?: string;
|
|
};
|
|
|
|
let {
|
|
active,
|
|
doctrine,
|
|
abilities,
|
|
weapons,
|
|
gear,
|
|
protocol,
|
|
configured,
|
|
onselect
|
|
}: {
|
|
active: string;
|
|
doctrine: MatrixCell;
|
|
abilities: MatrixCell[];
|
|
weapons: MatrixCell[];
|
|
gear: MatrixCell[];
|
|
protocol: MatrixCell;
|
|
configured: boolean;
|
|
onselect: (id: string) => void;
|
|
} = $props();
|
|
|
|
const selected = (cell: MatrixCell) =>
|
|
active === cell.id
|
|
|| (cell.id.startsWith('ability:') && active.startsWith('chip:')
|
|
&& Math.floor(Number(active.split(':')[1]) / 3) === Number(cell.id.split(':')[1]));
|
|
|
|
const issueLabel = (issue: string) => {
|
|
if (/not configured|required|select|choose/i.test(issue)) return 'REQUIRED';
|
|
if (/incomplete/i.test(issue)) return 'INCOMPLETE';
|
|
return 'CHECK';
|
|
};
|
|
</script>
|
|
|
|
<section class="loadout-matrix" aria-label="Loadout matrix">
|
|
<header>
|
|
<span>LOADOUT</span>
|
|
<b class:ready={configured}>{configured ? 'COMPLETE' : 'INCOMPLETE'}</b>
|
|
</header>
|
|
|
|
<div class="matrix-section doctrine-section">
|
|
<small class="section-label">SPECIALIZATION FOCUS</small>
|
|
<button
|
|
type="button"
|
|
class="doctrine-cell"
|
|
class:active={selected(doctrine)}
|
|
class:needs-attention={Boolean(doctrine.issue)}
|
|
aria-pressed={selected(doctrine)}
|
|
onclick={() => onselect(doctrine.id)}
|
|
>
|
|
<ItemIcon src={doctrine.iconUrl} alt="" className="matrix-icon" />
|
|
<span><small>{doctrine.label}</small><strong>{doctrine.name}</strong><em class:visible={selected(doctrine)}>{doctrine.summary}</em>{#if doctrine.issue}<mark title={doctrine.issue} aria-hidden="true">{issueLabel(doctrine.issue)}</mark><span class="sr-only">{doctrine.issue}</span>{/if}</span>
|
|
<i aria-hidden="true"></i>
|
|
</button>
|
|
<div class="supply-line"><span>ABILITIES</span></div>
|
|
<div class="ability-cells">
|
|
{#each abilities as cell}
|
|
<button type="button" class:active={selected(cell)} class:needs-attention={Boolean(cell.issue)} aria-pressed={selected(cell)} onclick={() => onselect(cell.id)}>
|
|
<ItemIcon src={cell.iconUrl} alt="" className="matrix-icon" />
|
|
<span><small>{cell.label}</small><strong>{cell.name}</strong><em class:visible={selected(cell)}>{cell.summary}</em>{#if cell.issue}<mark title={cell.issue} aria-hidden="true">{issueLabel(cell.issue)}</mark><span class="sr-only">{cell.issue}</span>{/if}</span>
|
|
<i aria-hidden="true"></i>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="matrix-section">
|
|
<small class="section-label">WEAPONS</small>
|
|
<div class="equipment-cells">
|
|
{#each weapons as cell}
|
|
<button type="button" class:active={selected(cell)} class:needs-attention={Boolean(cell.issue)} aria-pressed={selected(cell)} onclick={() => onselect(cell.id)}>
|
|
<ItemIcon src={cell.iconUrl} alt="" className="matrix-icon" />
|
|
<span><small>{cell.label}</small><strong>{cell.name}</strong><em class:visible={selected(cell)}>{cell.summary}</em>{#if cell.issue}<mark title={cell.issue} aria-hidden="true">{issueLabel(cell.issue)}</mark><span class="sr-only">{cell.issue}</span>{/if}</span>
|
|
<i aria-hidden="true"></i>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="matrix-section">
|
|
<small class="section-label">GEAR</small>
|
|
<div class="equipment-cells gear-cells">
|
|
{#each gear as cell}
|
|
<button type="button" class:active={selected(cell)} class:needs-attention={Boolean(cell.issue)} aria-pressed={selected(cell)} onclick={() => onselect(cell.id)}>
|
|
<ItemIcon src={cell.iconUrl} alt="" className="matrix-icon" />
|
|
<span><small>{cell.label}</small><strong>{cell.name}</strong><em class:visible={selected(cell)}>{cell.summary}</em>{#if cell.issue}<mark title={cell.issue} aria-hidden="true">{issueLabel(cell.issue)}</mark><span class="sr-only">{cell.issue}</span>{/if}</span>
|
|
<i aria-hidden="true"></i>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="matrix-section protocol-section">
|
|
<small class="section-label">OS PROTOCOL</small>
|
|
<button type="button" class:active={selected(protocol)} class:needs-attention={Boolean(protocol.issue)} aria-pressed={selected(protocol)} onclick={() => onselect(protocol.id)}>
|
|
<ItemIcon src={protocol.iconUrl} alt="" className="matrix-icon" />
|
|
<span><small>{protocol.label}</small><strong>{protocol.name}</strong><em class:visible={selected(protocol)}>{protocol.summary}</em>{#if protocol.issue}<mark title={protocol.issue} aria-hidden="true">{issueLabel(protocol.issue)}</mark><span class="sr-only">{protocol.issue}</span>{/if}</span>
|
|
<i aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
.loadout-matrix{min-width:0;border:1px solid var(--shd-border);background:var(--shd-surface)}
|
|
.loadout-matrix>header{height:auto;min-height:40px;position:static;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 10px;border-bottom:1px solid var(--shd-border);background:#0c0f10;color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.08em}
|
|
.loadout-matrix>header b{color:var(--shd-warning);font-size:9px;text-align:right}.loadout-matrix>header b.ready{color:var(--shd-success)}
|
|
.matrix-section{padding:5px;border-bottom:1px solid var(--shd-border)}.matrix-section:last-child{border-bottom:0}
|
|
.section-label{display:block;padding:2px 3px 5px;color:var(--shd-text-dim);font-size:9px;font-weight:900;letter-spacing:.09em}
|
|
.doctrine-cell,.equipment-cells button,.ability-cells button,.protocol-section>button{min-width:0;display:grid;grid-template-columns:34px minmax(0,1fr) 3px;align-items:center;gap:7px;padding:7px;border:1px solid var(--shd-border-soft);background:var(--shd-builder-canvas);color:var(--shd-text);text-align:left}
|
|
.doctrine-cell{width:100%;min-height:76px;grid-template-columns:42px minmax(0,1fr) 3px}.doctrine-cell :global(.matrix-icon){width:40px;height:40px}
|
|
:global(.matrix-icon){width:30px;height:30px;object-fit:contain}
|
|
button>span{min-width:0;display:grid;gap:2px}button small,button em{overflow:hidden;color:var(--shd-text-dim);font-size:9px;font-style:normal;letter-spacing:.04em;text-overflow:ellipsis;white-space:nowrap}button em{display:none}button em.visible{display:block}button strong{overflow:hidden;font-size:11px;text-overflow:ellipsis;white-space:nowrap}button>i{align-self:stretch;background:var(--quality-high-end)}
|
|
button:hover,button:focus-visible{border-color:var(--shd-border-strong);background:var(--shd-surface-active);outline:none}button.active{border-color:var(--shd-builder-selection-border);background:var(--shd-builder-selection);box-shadow:inset 2px 0 var(--shd-orange)}
|
|
button.needs-attention{border-bottom-color:color-mix(in srgb,var(--shd-danger) 68%,var(--shd-border-soft))}button.needs-attention>i{background:var(--shd-danger)}button mark{width:fit-content;max-width:100%;overflow:hidden;margin-top:2px;padding:2px 4px;border:1px solid color-mix(in srgb,var(--shd-danger) 58%,transparent);background:#1d1212;color:var(--shd-danger);font-size:8px;font-weight:900;letter-spacing:.07em;line-height:1.2;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}
|
|
.supply-line{height:22px;display:grid;place-items:center;background:linear-gradient(90deg,transparent 49.7%,var(--shd-orange) 49.7% 50.3%,transparent 50.3%)}.supply-line span{padding:0 6px;background:var(--shd-surface);color:var(--shd-orange);font-size:8px;font-weight:900;letter-spacing:.08em}
|
|
.ability-cells{display:grid;grid-template-columns:1fr;gap:3px}.ability-cells button{min-height:48px;grid-template-columns:25px minmax(0,1fr) 3px;padding:6px}.ability-cells :global(.matrix-icon){width:24px;height:24px}
|
|
.equipment-cells{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:3px}.equipment-cells button{min-height:69px}.protocol-section>button{width:100%;min-height:68px}
|
|
button:focus-visible{outline:2px solid var(--shd-focus-ring);outline-offset:2px}
|
|
@media(max-width:800px) and (min-width:651px){.ability-cells{grid-template-columns:repeat(3,minmax(0,1fr))}.ability-cells button{min-height:68px}}
|
|
@media(max-width:520px){.ability-cells{grid-template-columns:1fr}.equipment-cells{grid-template-columns:1fr 1fr}.gear-cells{grid-template-columns:1fr}}
|
|
</style>
|