feat(drone): add personal drone with first-person camera and control panel

Implements a flyable quadcopter with WASD/arrow controls, altitude hold,
and landing. Features a scissored camera view in a command panel showing
real-time feed, telemetry, and control hints. Includes localized strings
for English, Spanish, and Portuguese.
This commit is contained in:
2026-11-01 00:10:37 -03:00
parent 580c830ffb
commit 68cb4eb9ad
7 changed files with 306 additions and 17 deletions
+28
View File
@@ -118,6 +118,7 @@
<button type="button" id="btn-customize" class="btn" style="padding:.25rem .45rem; font-size:.75rem;">{{ __('city.act_customize') }}</button>
<button type="button" id="btn-ride-bus" class="btn" style="padding:.25rem .45rem; font-size:.75rem;">{{ __('city.act_ride_bus') }}</button>
<button type="button" id="btn-ride-train" class="btn" style="padding:.25rem .45rem; font-size:.75rem;">{{ __('city.act_ride_train') }}</button>
<button type="button" id="btn-drone" class="btn" style="padding:.25rem .45rem; font-size:.75rem;">🚁 {{ __('city.drone') }}</button>
</div>
{{-- Marketplace --}}
@@ -237,6 +238,31 @@
padding:.35rem .6rem; font-size:.75rem; color:var(--muted);">{{ __('city.hint') }}</span>
</div>
{{-- Drone command panel: header/hints opaque, #drone-video is a
transparent window — the renderer draws the drone camera
into that exact region (scissored viewport) of the canvas. --}}
<div id="drone-panel"
style="display:none; position:absolute; left:50%; top:.9rem; transform:translateX(-50%); z-index:16; width:330px;
border:1px solid rgba(148,163,184,.55); border-radius:.6rem; overflow:hidden;
box-shadow:0 12px 30px rgba(0,0,0,.5); background:transparent;">
<div id="drone-header"
style="display:flex; align-items:center; justify-content:space-between; gap:.4rem;
padding:.4rem .6rem; background:rgba(2,8,20,.88);">
<span style="font-weight:700; font-size:.8rem;">🚁 {{ __('city.drone') }}</span>
<span id="drone-tele" style="font-size:.72rem; color:#93c5fd;">↑ 0 m</span>
<button type="button" id="drone-close"
style="border:0; background:rgba(148,163,184,.18); color:#e2e8f0; border-radius:.35rem; cursor:pointer; padding:.1rem .45rem;">✕</button>
</div>
<div id="drone-video"
style="height:200px; background:transparent;
border-top:1px solid rgba(148,163,184,.35);
border-bottom:1px solid rgba(148,163,184,.35);"></div>
<div id="drone-hints"
style="padding:.4rem .6rem; font-size:.7rem; color:#cbd5e1; background:rgba(2,8,20,.88);">
{{ __('city.drone_hint') }}
</div>
</div>
{{-- Chat panel (bottom-right, transparent — opens on bump) --}}
<div id="chat-panel"
style="position:absolute; right:.6rem; bottom:3.2rem; z-index:15; width:280px; display:none;
@@ -376,6 +402,8 @@
signCityhall: @json(__('city.sign_cityhall')),
signUniversity: @json(__('city.sign_university')),
signFirestation: @json(__('city.sign_firestation')),
drone: @json(__('city.drone')),
droneHint: @json(__('city.drone_hint')),
exerciseStopHint: @json(__('city.exercise_stopHint')),
exerciseStart: @json(__('city.exerciseStart')),
exerciseStop: @json(__('city.exerciseStop')),