Styling fixes and Olden Era mmodule
This commit is contained in:
@@ -4,30 +4,26 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>OPHI-118 / onboard</title>
|
||||
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}">
|
||||
<link rel="stylesheet" href="{{ asset('css/hud.css') }}">
|
||||
<style>
|
||||
/* Onboard wizard is the only page that's not an OBS browser_source — it
|
||||
renders in a real desktop browser at arbitrary viewport sizes. So we
|
||||
break from the canvas-pixel design language and use a centered
|
||||
terminal-card layout with breathing room. */
|
||||
body.overlay-body {
|
||||
padding: 24px;
|
||||
body.page-shell {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 24px;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
.wizard {
|
||||
width: min(960px, 100%);
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 360px;
|
||||
grid-template-columns: 1fr 480px;
|
||||
gap: 22px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
@media (max-width: 1100px) {
|
||||
.wizard { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
@@ -362,12 +358,65 @@
|
||||
#toast.ok { border-color: var(--term-fg-bright); }
|
||||
#toast pre { white-space: pre-wrap; margin: 6px 0 0; color: var(--term-fg-dim); }
|
||||
|
||||
/* Header strip mirrors the on-stream HUD chrome. */
|
||||
.hud {
|
||||
font-size: 16px;
|
||||
padding: 0 4px;
|
||||
/* Olden Era extra module — appears only when the picked game is Olden Era. */
|
||||
#olden-module[hidden] { display: none !important; }
|
||||
#olden-module {
|
||||
margin-top: 8px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--term-edge);
|
||||
}
|
||||
#olden-module::before {
|
||||
content: '┤ HEROES OF MIGHT & MAGIC: OLDEN ERA ├';
|
||||
display: block;
|
||||
color: var(--term-fg-bright);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.28em;
|
||||
margin-bottom: 14px;
|
||||
text-shadow: 0 0 6px var(--term-glow);
|
||||
}
|
||||
.olden-pick {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.olden-pick select {
|
||||
flex: 1 1 auto;
|
||||
background: rgba(0, 30, 0, 0.45);
|
||||
border: 1px solid var(--term-edge);
|
||||
border-radius: 3px;
|
||||
color: var(--term-fg-bright);
|
||||
font: inherit;
|
||||
font-size: 15px;
|
||||
padding: 8px 12px;
|
||||
outline: 0;
|
||||
text-shadow: 0 0 6px var(--term-glow);
|
||||
}
|
||||
.olden-pick select:focus {
|
||||
border-color: var(--term-fg-bright);
|
||||
box-shadow: 0 0 0 1px var(--term-fg-bright), 0 0 18px rgba(95, 220, 98, 0.25);
|
||||
}
|
||||
.olden-preview {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
object-fit: contain;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid var(--term-edge);
|
||||
border-radius: 3px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.olden-preview.olden-portrait { object-fit: cover; }
|
||||
.olden-preview:not([src]),
|
||||
.olden-preview[src=""] { opacity: 0.25; }
|
||||
.toggle.olden-color button.olden-red.on {
|
||||
background: rgba(230, 58, 46, 0.20);
|
||||
color: #ff6a5e;
|
||||
text-shadow: 0 0 6px rgba(230, 58, 46, 0.55);
|
||||
}
|
||||
.toggle.olden-color button.olden-blue.on {
|
||||
background: rgba(58, 166, 255, 0.20);
|
||||
color: #6ec5ff;
|
||||
text-shadow: 0 0 6px rgba(58, 166, 255, 0.55);
|
||||
}
|
||||
.hud .group { gap: 18px; }
|
||||
|
||||
/* Footer note. */
|
||||
.foot-note {
|
||||
@@ -378,22 +427,13 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="overlay-body">
|
||||
<body class="page-shell">
|
||||
|
||||
<header class="hud hud--onair">
|
||||
<div class="group">
|
||||
<span><span class="led"></span>ONBOARD</span>
|
||||
<span>OPHI-118 // PROJECT MANIFEST</span>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="dim">RIG</span>
|
||||
<span id="rig-name">{{ $telemetry['rig'] ?? '—' }}</span>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="dim">UTC</span>
|
||||
<span id="clock">--:--:--</span>
|
||||
</div>
|
||||
</header>
|
||||
@include('partials.topbar', [
|
||||
'crumb' => 'ONBOARD',
|
||||
'context' => '<span class="dim">RIG</span><span id="rig-name">'
|
||||
. e($telemetry['rig'] ?? '—') . '</span>',
|
||||
])
|
||||
|
||||
<main class="wizard">
|
||||
<section class="panel" id="form-panel">
|
||||
@@ -498,6 +538,65 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="olden-module" hidden>
|
||||
<div class="form-row">
|
||||
<label for="ol-my-faction">My Faction</label>
|
||||
<div class="field olden-pick">
|
||||
<select id="ol-my-faction" name="olden[my][faction]"></select>
|
||||
<img class="olden-preview" id="ol-my-faction-img" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="ol-my-hero">My Main Hero</label>
|
||||
<div class="field olden-pick">
|
||||
<select id="ol-my-hero" name="olden[my][hero]"></select>
|
||||
<img class="olden-preview olden-portrait" id="ol-my-hero-img" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="ol-enemy-faction">Enemy Faction</label>
|
||||
<div class="field olden-pick">
|
||||
<select id="ol-enemy-faction" name="olden[enemy][faction]"></select>
|
||||
<img class="olden-preview" id="ol-enemy-faction-img" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="ol-enemy-hero">Enemy Main Hero</label>
|
||||
<div class="field olden-pick">
|
||||
<select id="ol-enemy-hero" name="olden[enemy][hero]"></select>
|
||||
<img class="olden-preview olden-portrait" id="ol-enemy-hero-img" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>My Color</label>
|
||||
<div class="field">
|
||||
<div class="toggle olden-color" data-toggle="oldenColor" data-value="red">
|
||||
<button type="button" data-val="red" class="olden-red on">RED · LEFT</button>
|
||||
<button type="button" data-val="blue" class="olden-blue">BLUE · RIGHT</button>
|
||||
</div>
|
||||
<input type="hidden" id="ol-color" name="oldenColor" value="red">
|
||||
<div class="hint">Red → your nick on the matchup's left side, in red. Blue → right side, in blue.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="ol-map">Map / Template</label>
|
||||
<div class="field">
|
||||
<div class="prompt-line">
|
||||
<span class="caret">▶</span>
|
||||
<input
|
||||
type="text" id="ol-map" name="olden[map]"
|
||||
autocomplete="off" spellcheck="false"
|
||||
placeholder="e.g. Crimson Tide">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn" id="submit-btn">
|
||||
<span class="spinner" id="spinner"></span>WRITE MANIFEST
|
||||
@@ -563,7 +662,7 @@
|
||||
group.querySelectorAll('button').forEach(b => {
|
||||
b.classList.toggle('on', b.dataset.val === val);
|
||||
});
|
||||
hidden.value = val === 'true' ? '1' : '0';
|
||||
hidden.value = val === 'true' ? '1' : val === 'false' ? '0' : val;
|
||||
renderPreview();
|
||||
});
|
||||
});
|
||||
@@ -593,6 +692,7 @@
|
||||
|
||||
function setGameId(id) {
|
||||
gameInput.dataset.gameId = id || '';
|
||||
syncOldenVisibility();
|
||||
}
|
||||
function clearGameId() {
|
||||
if (gameInput.dataset.gameId) setGameId('');
|
||||
@@ -725,6 +825,84 @@
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ─── olden era module ──────────────────────────────────────────────────
|
||||
const OLDEN_GAME_ID = @json(\App\Support\OldenEra::GAME_ID);
|
||||
const OLDEN_CATALOG = @json($oldenEra['catalog']);
|
||||
const OLDEN_SAVED = @json($oldenEra['manifest']);
|
||||
const oldenModule = $('#olden-module');
|
||||
const olColorInput = $('#ol-color');
|
||||
const olColorGroup = document.querySelector('.toggle.olden-color');
|
||||
const olMap = $('#ol-map');
|
||||
|
||||
function syncOldenVisibility() {
|
||||
if (!oldenModule) return;
|
||||
oldenModule.hidden = gameInput.dataset.gameId !== OLDEN_GAME_ID;
|
||||
}
|
||||
function fillFactionSelect(sel) {
|
||||
sel.innerHTML = Object.keys(OLDEN_CATALOG)
|
||||
.map(f => `<option value="${escapeHtml(f)}">${escapeHtml(f)}</option>`).join('');
|
||||
}
|
||||
function fillHeroSelect(side, faction) {
|
||||
const sel = $(`#ol-${side}-hero`);
|
||||
const heroes = (OLDEN_CATALOG[faction] || {}).heroes || [];
|
||||
sel.innerHTML = heroes
|
||||
.map(h => `<option value="${escapeHtml(h.name)}">${escapeHtml(h.name)}</option>`).join('');
|
||||
}
|
||||
function updateFactionImg(side) {
|
||||
const f = $(`#ol-${side}-faction`).value;
|
||||
$(`#ol-${side}-faction-img`).src = (OLDEN_CATALOG[f] || {}).logo || '';
|
||||
}
|
||||
function updateHeroImg(side) {
|
||||
const f = $(`#ol-${side}-faction`).value;
|
||||
const h = $(`#ol-${side}-hero`).value;
|
||||
const hero = ((OLDEN_CATALOG[f] || {}).heroes || []).find(x => x.name === h);
|
||||
$(`#ol-${side}-hero-img`).src = hero ? hero.portrait : '';
|
||||
}
|
||||
function setColor(val) {
|
||||
if (val !== 'red' && val !== 'blue') val = 'red';
|
||||
olColorInput.value = val;
|
||||
olColorGroup.dataset.value = val;
|
||||
olColorGroup.querySelectorAll('button').forEach(b => {
|
||||
b.classList.toggle('on', b.dataset.val === val);
|
||||
});
|
||||
}
|
||||
|
||||
['my', 'enemy'].forEach(side => {
|
||||
const factionSel = $(`#ol-${side}-faction`);
|
||||
const heroSel = $(`#ol-${side}-hero`);
|
||||
fillFactionSelect(factionSel);
|
||||
factionSel.addEventListener('change', () => {
|
||||
fillHeroSelect(side, factionSel.value);
|
||||
updateFactionImg(side);
|
||||
updateHeroImg(side);
|
||||
});
|
||||
heroSel.addEventListener('change', () => updateHeroImg(side));
|
||||
});
|
||||
|
||||
// Pre-populate from saved olden-era-manifest.json (if any).
|
||||
(function preFillOlden() {
|
||||
const saved = OLDEN_SAVED || {};
|
||||
const factions = Object.keys(OLDEN_CATALOG);
|
||||
if (!factions.length) return;
|
||||
const pick = (want, side) => {
|
||||
const f = factions.includes(want?.faction) ? want.faction : factions[0];
|
||||
$(`#ol-${side}-faction`).value = f;
|
||||
fillHeroSelect(side, f);
|
||||
const heroes = (OLDEN_CATALOG[f] || {}).heroes || [];
|
||||
const wantedHero = want?.hero && heroes.find(h => h.name === want.hero) ? want.hero
|
||||
: (heroes[0] && heroes[0].name) || '';
|
||||
$(`#ol-${side}-hero`).value = wantedHero;
|
||||
updateFactionImg(side);
|
||||
updateHeroImg(side);
|
||||
};
|
||||
pick(saved.my, 'my');
|
||||
pick(saved.enemy, 'enemy');
|
||||
setColor(saved.color || 'red');
|
||||
olMap.value = saved.map || '';
|
||||
})();
|
||||
|
||||
syncOldenVisibility();
|
||||
|
||||
// ─── submit ────────────────────────────────────────────────────────────
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -740,6 +918,14 @@
|
||||
microphone: micHidden.value === '1',
|
||||
pushTwitch: pushInput.checked,
|
||||
};
|
||||
if (!oldenModule.hidden) {
|
||||
payload.olden = {
|
||||
color: olColorInput.value || 'red',
|
||||
my: { faction: $('#ol-my-faction').value, hero: $('#ol-my-hero').value },
|
||||
enemy: { faction: $('#ol-enemy-faction').value, hero: $('#ol-enemy-hero').value },
|
||||
map: olMap.value.trim() || null,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const r = await fetch('/onboard', {
|
||||
method: 'POST',
|
||||
@@ -756,6 +942,7 @@
|
||||
return;
|
||||
}
|
||||
const lines = [`✓ wrote ${body.path}`];
|
||||
if (body.oldenPath) lines.push(`✓ wrote ${body.oldenPath}`);
|
||||
if (body.twitch) lines.push(`twitch: ${body.twitch}`);
|
||||
lines.push('→ refresh the Project Loading browser source in OBS.');
|
||||
toast.className = 'on ok';
|
||||
|
||||
Reference in New Issue
Block a user