diff --git a/src/dock-ui.js b/src/dock-ui.js index bf410af..1755407 100644 --- a/src/dock-ui.js +++ b/src/dock-ui.js @@ -24,6 +24,7 @@ overlay.innerHTML = ` CARGO 0/0 SALVAGE 0 MISSION NONE + WANTED 0 @@ -46,6 +47,13 @@ overlay.innerHTML = ` SHIPYARD Upgrade the Sparrow and manage recovered salvage. + + + HULL SERVICE + Hull already at full strength. + + REPAIR + @@ -166,6 +174,32 @@ style.textContent = ` } .dock-service strong { display:block; margin-bottom:8px; color:#fff1bc; font-size:22px; } .dock-service span { color:#aac0c6; font:600 12px/1.45 system-ui,sans-serif; } + .dock-repair-strip { + grid-column: 1 / -1; + display:flex; + align-items:center; + justify-content:space-between; + gap:12px; + padding:12px 14px; + border:1px solid rgba(194, 142, 61, .46); + border-radius:10px; + background:rgba(4, 28, 35, .9); + } + .dock-repair-strip > div { display:grid; gap:3px; } + .dock-repair-strip strong { color:#fff0bd; font-size:14px; } + .dock-repair-strip span { color:#9fb5bb; font:600 10px/1.3 system-ui,sans-serif; } + .dock-repair-strip button { + min-width:110px; + min-height:42px; + padding:7px 12px; + border:1px solid #9a6a2b; + border-radius:8px; + background:#103b3d; + color:#f5dda0; + font:700 10px/1 system-ui,sans-serif; + touch-action:manipulation; + } + .dock-repair-strip button:disabled { opacity:.36; } .dock-panel { display: grid; gap: 10px; } .dock-section-head { display:flex; align-items:center; justify-content:space-between; gap:10px; } .dock-section-head > div { display:grid; gap:2px; } @@ -309,7 +343,13 @@ const renderMarket = (state) => { strong.textContent = item.name; const tag = document.createElement('small'); tag.className = 'dock-tag'; - tag.textContent = item.exported ? 'LOCAL EXPORT' : item.imported ? 'HIGH DEMAND' : 'MARKET'; + tag.textContent = item.shortage > 0.5 + ? `SHORTAGE +${Math.round(item.shortage * 8)}%` + : item.exported + ? 'LOCAL EXPORT' + : item.imported + ? 'HIGH DEMAND' + : 'MARKET'; name.append(strong, tag); const buy = document.createElement('span'); buy.className = 'dock-price'; @@ -317,13 +357,20 @@ const renderMarket = (state) => { const sell = document.createElement('span'); sell.className = 'dock-price'; sell.textContent = `SELL ¢${item.sellPrice} • HOLD ${item.held || 0}`; + const hint = document.createElement('p'); + const opportunity = item.opportunity; + hint.textContent = opportunity + ? opportunity.margin > 0 + ? `BEST KNOWN: ${opportunity.portName} • SELL ¢${opportunity.sellPrice} • +¢${opportunity.margin}/UNIT` + : `BEST KNOWN: ${opportunity.portName} • no positive margin` + : 'No known destination price.'; const actions = document.createElement('div'); actions.className = 'dock-card-actions'; actions.append( actionButton('BUY 1', 'buy-trade-good', { goodId: item.goodId }, !item.canBuy), actionButton('SELL 1', 'sell-trade-good', { goodId: item.goodId }, !item.canSell), ); - card.append(name, buy, sell, actions); + card.append(name, buy, sell, hint, actions); fields['market-list'].append(card); } }; @@ -338,6 +385,19 @@ const render = (state) => { fields.cargo.textContent = `${dock.cargoUsed || 0}/${dock.cargoCapacity || 0}`; fields.salvage.textContent = state.progression?.inventoryCount || 0; fields.mission.textContent = dock.activeMission ? dock.activeMission.type.toUpperCase() : 'NONE'; + fields.wanted.textContent = dock.wanted || 0; + + const repairButton = overlay.querySelector('[data-action="repair-hull"]'); + const repair = dock.repair; + if (repair) { + fields['repair-status'].textContent = repair.missing > 0 + ? `Repair ${repair.missing} hull for ¢${repair.cost}.` + : 'Hull already at full strength.'; + repairButton.disabled = repair.missing <= 0 + || (state.progression?.credits || 0) < repair.cost; + repairButton.textContent = repair.missing > 0 ? `REPAIR ¢${repair.cost}` : 'FULL HULL'; + } + fields.message.textContent = String(state.status || '').replace(/\n/g, ' '); renderMission(state); renderMarket(state); @@ -360,6 +420,8 @@ for (const button of overlay.querySelectorAll('[data-dock-tab]')) { for (const button of overlay.querySelectorAll('[data-action="open-shipyard"]')) { button.addEventListener('click', () => dispatchAction('open-shipyard')); } +overlay.querySelector('[data-action="repair-hull"]') + .addEventListener('click', () => dispatchAction('repair-hull')); overlay.querySelector('.dock-leave').addEventListener('click', () => dispatchAction('close-dock')); overlay.querySelector('.dock-backdrop').addEventListener('click', () => dispatchAction('close-dock')); diff --git a/src/mobile-hud.js b/src/mobile-hud.js index 1793f41..aca24f0 100644 --- a/src/mobile-hud.js +++ b/src/mobile-hud.js @@ -326,6 +326,42 @@ if (!isDesktopPointer.matches) { display: block; } + .steam-objective { + display: none; + position: absolute; + left: 50%; + top: 20.5%; + transform: translateX(-50%); + align-items: center; + gap: 7px; + max-width: 78%; + padding: 5px 9px; + border: 1px solid rgba(201, 146, 60, .58); + border-radius: 999px; + background: rgba(5, 22, 28, .82); + color: #f7dfa1; + font: 700 clamp(9px, 2.45vw, 12px)/1.1 system-ui, sans-serif; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + box-shadow: 0 2px 10px rgba(0,0,0,.28); + } + + .steam-objective.visible { display: flex; } + + .steam-objective-arrow { + display: inline-block; + color: #ffe08c; + font-size: 18px; + line-height: 1; + transform-origin: center; + } + + .steam-objective-text { + overflow: hidden; + text-overflow: ellipsis; + } + #mobile-controls.controller-active #drag-stick { left: 1%; bottom: max(8px, env(safe-area-inset-bottom)); @@ -697,6 +733,14 @@ if (!isDesktopPointer.matches) { status.dataset.steam = 'status'; hud.appendChild(status); + const objective = document.createElement('div'); + objective.className = 'steam-objective'; + objective.innerHTML = ` + ↑ + + `; + hud.appendChild(objective); + const fields = Object.fromEntries( [...hud.querySelectorAll('[data-steam]')].map((el) => [el.dataset.steam, el]), ); @@ -800,6 +844,21 @@ if (!isDesktopPointer.matches) { fields['port-reload'].style.transform = `scaleX(${clamp01(state.reload?.port ?? 1)})`; fields['starboard-reload'].style.transform = `scaleX(${clamp01(state.reload?.starboard ?? 1)})`; + const mission = state.objective || null; + objective.classList.toggle('visible', Boolean(mission)); + if (mission?.type === 'delivery') { + fields['objective-arrow'].hidden = false; + fields['objective-arrow'].style.transform = `rotate(${mission.relativeBearing || 0}deg)`; + fields['objective-text'].textContent = + `${mission.label} • ${Math.round(mission.distance || 0)}m`; + } else if (mission?.type === 'bounty') { + fields['objective-arrow'].hidden = true; + fields['objective-text'].textContent = mission.progressText || 'PIRATE BOUNTY'; + } else { + fields['objective-arrow'].hidden = true; + fields['objective-text'].textContent = ''; + } + const progression = state.progression || {}; if (buyButton) { buyButton.hidden = !progression.docked; diff --git a/src/shipyard-ui.js b/src/shipyard-ui.js index ef3f187..1818664 100644 --- a/src/shipyard-ui.js +++ b/src/shipyard-ui.js @@ -79,12 +79,14 @@ overlay.innerHTML = `