diff --git a/.env.example b/.env.example index 33c9229a51e83d580c9dc6bc6469144c4ff954d4..20f7a29766fbd017997b6d35a41d2ea8c01d4ed9 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,7 @@ MISTRAL_API_KEY=your_mistral_api_key_here SECRET_KEY=change-me-in-production + +# Optionnel : génération sprites via Vertex AI Imagen (ex. project GCP temporaire) +# Si défini, le script generate_sprites utilise Imagen au lieu de Mistral. À retirer en prod. +# GCP_PROJECT_ID=mtgbinder +# GCP_LOCATION=us-central1 diff --git a/.gitattributes b/.gitattributes index f3edcc234e3317fc05680a4ed352f7d9eeb09c94..c47c4ecf994c771304174ba403813e7187094544 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ *.png filter=lfs diff=lfs merge=lfs -text *.PNG filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index b4f48318f901ef363a4db483092557e26b5151f5..455901275324088e2afcd86dae72c64fc67534ae 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ __pycache__/ **/*.pyc **/__pycache__/ frontend/.svelte-kit/generated/ -frontend/.svelte-kit/output/ -frontend/build/ \ No newline at end of file +frontend/.svelte-kit/output/ \ No newline at end of file diff --git a/TODO.md b/TODO.md index ab7c6578dbd5909f955dc8c1006c800ea57d84e1..0dde33103007d29ef6b6100b3a968191953b1fc5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,7 @@ -AJouter detection safename -Ajouter observing des matches existants si trop de matches en cours. -Refaire la landing page pour etre plus précis engageant design plus cool etc... expliquer dans la peau d'un général -Ajouter PVs unités \ No newline at end of file ++ Refaire la landing page pour etre plus précis engageant design plus cool etc... expliquer dans la peau d'un général ++ AJouter detection safename ++ Ajouter observing des matches existants si trop de matches en cours. +Ajouter PVs unités +utiliser poisiotns de départ et placement des minéraux +pathfinding +shoot animation \ No newline at end of file diff --git a/backend/config.py b/backend/config.py index 4725d52e5a416726bf43c11fb6cc11ebfa8e77ea..5fe43d758fea19626bf2ec34d2629a840c4d1cb7 100644 --- a/backend/config.py +++ b/backend/config.py @@ -17,6 +17,11 @@ VOXTRAL_REALTIME_MODEL: str = "voxtral-mini-transcribe-realtime-2602" # streami # Mistral LLM model for command parsing MISTRAL_CHAT_MODEL: str = "mistral-large-latest" +# GCP Vertex AI Imagen (optionnel, pour génération sprites si Mistral restreint) +# Ex: project mtgbinder — à retirer en prod +GCP_PROJECT_ID: str = os.getenv("GCP_PROJECT_ID", "") +GCP_LOCATION: str = os.getenv("GCP_LOCATION", "us-central1") + # Game constants TICK_INTERVAL: float = 0.25 # seconds per game tick TICKS_PER_SECOND: int = 4 diff --git a/backend/game/bot.py b/backend/game/bot.py index 17b308feb2ca31ccfd9259e0ac39328108ca687a..7eb743022386553a662ab6d4819f48620101d080 100644 --- a/backend/game/bot.py +++ b/backend/game/bot.py @@ -31,7 +31,7 @@ BOT_TICK_INTERVAL = 8 def _cmd(*actions: GameAction) -> ParsedCommand: - return ParsedCommand(actions=list(actions), feedback="") + return ParsedCommand(actions=list(actions), feedback_template="") class BotPlayer: diff --git a/backend/game/buildings.py b/backend/game/buildings.py index 678ff37f6eb863ef3b2afedb1fe1df57d28f1007..53a9e2e8aed57c91d58e25c3a5befe0c0018ebf9 100644 --- a/backend/game/buildings.py +++ b/backend/game/buildings.py @@ -33,6 +33,17 @@ class BuildingDef(BaseModel): width: int = 2 height: int = 2 supply_provided: int = 0 + # Collision box is shrunk by this amount on each side vs. the visual box, + # so units can squeeze between buildings that have a small visual gap. + collision_shrink: float = 0.4 + + def col_hw(self) -> float: + """Half collision width.""" + return max(0.5, self.width / 2 - self.collision_shrink) + + def col_hh(self) -> float: + """Half collision height.""" + return max(0.5, self.height / 2 - self.collision_shrink) BUILDING_DEFS: dict[BuildingType, BuildingDef] = { @@ -54,7 +65,7 @@ BUILDING_DEFS: dict[BuildingType, BuildingDef] = { ), BuildingType.REFINERY: BuildingDef( max_hp=500, mineral_cost=100, gas_cost=0, build_time_ticks=40, - width=2, height=2, + width=2, height=2, collision_shrink=0.2, ), BuildingType.FACTORY: BuildingDef( max_hp=1250, mineral_cost=200, gas_cost=100, build_time_ticks=80, @@ -81,8 +92,8 @@ class Building(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())[:8]) building_type: BuildingType owner: str - x: int - y: int + x: float # center x + y: float # center y hp: float max_hp: int status: BuildingStatus = BuildingStatus.CONSTRUCTING @@ -93,20 +104,20 @@ class Building(BaseModel): rally_y: Optional[float] = None @classmethod - def create(cls, bt: BuildingType, owner: str, x: int, y: int) -> "Building": + def create(cls, bt: BuildingType, owner: str, x: float, y: float) -> "Building": defn = BUILDING_DEFS[bt] return cls( building_type=bt, owner=owner, x=x, y=y, - hp=float(defn.max_hp), + hp=float(defn.max_hp) * 0.15, max_hp=defn.max_hp, construction_ticks_remaining=defn.build_time_ticks, - construction_max_ticks=defn.build_time_ticks, + construction_max_ticks=defn.build_time_ticks, ) def spawn_point(self) -> tuple[float, float]: - """Position where units appear when produced.""" + """Position where units appear when produced (below bottom edge, horizontally centered).""" defn = BUILDING_DEFS[self.building_type] - return (float(self.x) + defn.width / 2, float(self.y) + defn.height + 1) + return (self.x, self.y + defn.height / 2 + 1.0) diff --git a/backend/game/commands.py b/backend/game/commands.py index 9c9385006bfdaad982814db80db590fc750373be..c238b478a39d9f99e9c8a12da8c6b9eb4f780807 100644 --- a/backend/game/commands.py +++ b/backend/game/commands.py @@ -6,9 +6,9 @@ given to Mistral in voice/command_parser.py. """ from enum import Enum -from typing import Optional +from typing import Any, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Field class ActionType(str, Enum): @@ -68,19 +68,20 @@ class GameAction(BaseModel): class ParsedCommand(BaseModel): - """Full Mistral response: one or more actions + French feedback text.""" + """Full Mistral response: one or more actions + feedback template to fill with game data.""" actions: list[GameAction] - feedback: str + feedback_template: str # sentence in command language with placeholders {n}, {zone}, {building}, etc. + language: str = "fr" # ISO code of the user's command language (fr, en, ...) class ActionResult(BaseModel): action_type: str success: bool - message: str + data: dict[str, Any] = Field(default_factory=dict) # template variables: n, zone, building, count, summary, ... sound_events: list[dict] = [] # e.g. [{"kind": "move_ack", "unit_type": "marine"}] unit_ids: list[str] = [] # for query_units: list of unit IDs matching the filter class CommandResult(BaseModel): results: list[ActionResult] - feedback_override: Optional[str] = None # replaces Mistral feedback on hard error + feedback_override: Optional[str] = None # error key for API to generate message (e.g. "game_not_in_progress") diff --git a/backend/game/engine.py b/backend/game/engine.py index d75d2541e8ed777409cc4376f15f8e3dc638287d..1043553b84109463312c7f10c0e7a9defd915f16 100644 --- a/backend/game/engine.py +++ b/backend/game/engine.py @@ -22,7 +22,7 @@ from .bot import BOT_TICK_INTERVAL, BotPlayer from .buildings import Building, BuildingDef, BuildingStatus, BuildingType, BUILDING_DEFS from .commands import ActionResult, ActionType, CommandResult, GameAction, ParsedCommand from .map import MAP_HEIGHT, MAP_WIDTH, ResourceType -from .pathfinding import find_path, is_walkable, snap_to_walkable +from .pathfinding import find_path, is_walkable, nearest_walkable_navpoint, snap_to_walkable from .state import GamePhase, GameState, PlayerState from .tech_tree import can_build, can_train, get_producer, missing_for_build, missing_for_train from .units import Unit, UnitDef, UnitStatus, UnitType, UNIT_DEFS @@ -47,6 +47,7 @@ class GameEngine: self._task: Optional[asyncio.Task] = None # type: ignore[type-arg] self.bot: Optional[BotPlayer] = None self._sound_events: list[dict] = [] # fire/death per tick, sent in game_update + self._cmd_lang: str = "fr" # ------------------------------------------------------------------ # Lifecycle @@ -68,9 +69,10 @@ class GameEngine: # ------------------------------------------------------------------ def apply_command(self, player_id: str, parsed: ParsedCommand) -> CommandResult: + self._cmd_lang = getattr(parsed, "language", "fr") or "fr" player = self.state.players.get(player_id) if not player or self.state.phase != GamePhase.PLAYING: - return CommandResult(results=[], feedback_override="La partie n'est pas en cours.") + return CommandResult(results=[], feedback_override="game_not_in_progress") results: list[ActionResult] = [] last_query_unit_ids: Optional[list[str]] = None @@ -137,20 +139,44 @@ class GameEngine: for building in player.buildings.values(): if building.status != BuildingStatus.CONSTRUCTING: continue + # Only progress if the assigned SCV has arrived (status == BUILDING) + scv = next( + (u for u in player.units.values() + if u.building_target_id == building.id and u.status == UnitStatus.BUILDING), + None, + ) + if not scv: + continue building.construction_ticks_remaining -= 1 + # HP grows linearly from 15% to 100% over build time + hp_gain = building.max_hp * 0.85 / max(1, building.construction_max_ticks) + building.hp = min(building.hp + hp_gain, float(building.max_hp)) if building.construction_ticks_remaining <= 0: building.status = BuildingStatus.ACTIVE building.construction_ticks_remaining = 0 - # Mark the assigned SCV as idle - for unit in player.units.values(): - if unit.building_target_id == building.id: - unit.status = UnitStatus.IDLE - unit.building_target_id = None - unit.target_x = unit.target_y = None + building.hp = float(building.max_hp) + scv.building_target_id = None + scv.target_x = scv.target_y = None + # Auto-return SCV to nearest mineral patch + cc = player.command_center() + cx = float(cc.x) + 2.0 if cc else scv.x + cy = float(cc.y) + 1.5 if cc else scv.y + patch = self.state.game_map.nearest_mineral(cx, cy) + if patch: + scv.status = UnitStatus.MINING_MINERALS + scv.assigned_resource_id = patch.id + scv.harvest_carry = False + scv.harvest_amount = 0 + patch.assigned_scv_ids.append(scv.id) + self._set_unit_destination(scv, float(patch.x), float(patch.y), is_flying=False) + else: + scv.status = UnitStatus.IDLE def _tick_production(self, player: PlayerState) -> None: """Tick building production queues and spawn units.""" for building in player.buildings.values(): + if building.status == BuildingStatus.CONSTRUCTING: + continue if not building.production_queue: building.status = BuildingStatus.ACTIVE continue @@ -166,13 +192,19 @@ class GameEngine: self._spawn_unit(player, building, UnitType(item.unit_type)) def _spawn_unit(self, player: PlayerState, building: Building, ut: UnitType) -> None: - sx, sy = building.spawn_point() + raw_sx, raw_sy = building.spawn_point() + is_flying = UNIT_DEFS[ut].is_flying + if is_flying: + sx, sy = raw_sx, raw_sy + else: + blocked = self._building_blocked_rects() + sx, sy = nearest_walkable_navpoint(raw_sx, raw_sy, blocked_rects=blocked) tx = building.rally_x if building.rally_x is not None else sx ty = building.rally_y if building.rally_y is not None else sy unit = Unit.create(ut, player.player_id, sx, sy) if tx != sx or ty != sy: unit.status = UnitStatus.MOVING - self._set_unit_destination(unit, tx, ty, is_flying=UNIT_DEFS[ut].is_flying) + self._set_unit_destination(unit, tx, ty, is_flying=is_flying) player.units[unit.id] = unit def _tick_mining(self, player: PlayerState) -> None: @@ -193,36 +225,47 @@ class GameEngine: unit.harvest_carry = False return - cc_x = float(cc.x) + 2.0 - cc_y = float(cc.y) + 1.5 - arrive_dist = 1.5 + mineral_arrive = 1.2 # SCV stops right next to the patch + cc_edge_arrive = 1.0 # SCV triggers deposit when within 1 tile of any CC edge for unit in player.units.values(): if unit.status == UnitStatus.MINING_MINERALS: resource = self.state.game_map.get_resource(unit.assigned_resource_id or "") if not resource or resource.is_depleted: + # Remove from old patch assignment + if resource and unit.id in resource.assigned_scv_ids: + resource.assigned_scv_ids.remove(unit.id) unit.assigned_resource_id = None - unit.status = UnitStatus.IDLE - unit.target_x = unit.target_y = None - unit.path_waypoints = [] - unit.harvest_carry = False + # Auto-reassign to next available mineral instead of going idle + next_patch = self.state.game_map.nearest_mineral(cc.x, cc.y) + if next_patch: + unit.assigned_resource_id = next_patch.id + next_patch.assigned_scv_ids.append(unit.id) + self._set_unit_destination(unit, float(next_patch.x), float(next_patch.y), is_flying=False) + else: + unit.status = UnitStatus.IDLE + unit.target_x = unit.target_y = None + unit.path_waypoints = [] + unit.harvest_carry = False continue rx, ry = float(resource.x), float(resource.y) if not unit.harvest_carry: - if unit.target_x != rx or unit.target_y != ry: + if unit.target_x is None: self._set_unit_destination(unit, rx, ry, is_flying=False) - if unit.dist_to(rx, ry) <= arrive_dist: + if unit.dist_to(rx, ry) <= mineral_arrive: gathered = min(MINERAL_PER_HARVEST, resource.amount) resource.amount -= gathered unit.harvest_carry = True unit.harvest_amount = gathered - self._set_unit_destination(unit, cc_x, cc_y, is_flying=False) + tx, ty = self._nearest_building_entry(unit, cc) + self._set_unit_destination(unit, tx, ty, is_flying=False) else: - if unit.target_x != cc_x or unit.target_y != cc_y: - self._set_unit_destination(unit, cc_x, cc_y, is_flying=False) - if unit.dist_to(cc_x, cc_y) <= arrive_dist: + if unit.target_x is None: + tx, ty = self._nearest_building_entry(unit, cc) + self._set_unit_destination(unit, tx, ty, is_flying=False) + if self._dist_unit_to_building(unit, cc) <= cc_edge_arrive: player.minerals += unit.harvest_amount unit.harvest_carry = False unit.harvest_amount = 0 @@ -241,16 +284,18 @@ class GameEngine: rx, ry = float(resource.x), float(resource.y) if not unit.harvest_carry: - if unit.target_x != rx or unit.target_y != ry: + if unit.target_x is None: self._set_unit_destination(unit, rx, ry, is_flying=False) - if unit.dist_to(rx, ry) <= arrive_dist: + if unit.dist_to(rx, ry) <= mineral_arrive: unit.harvest_carry = True unit.harvest_amount = GAS_PER_HARVEST - self._set_unit_destination(unit, cc_x, cc_y, is_flying=False) + tx, ty = self._nearest_building_entry(unit, cc) + self._set_unit_destination(unit, tx, ty, is_flying=False) else: - if unit.target_x != cc_x or unit.target_y != cc_y: - self._set_unit_destination(unit, cc_x, cc_y, is_flying=False) - if unit.dist_to(cc_x, cc_y) <= arrive_dist: + if unit.target_x is None: + tx, ty = self._nearest_building_entry(unit, cc) + self._set_unit_destination(unit, tx, ty, is_flying=False) + if self._dist_unit_to_building(unit, cc) <= cc_edge_arrive: player.gas += unit.harvest_amount unit.harvest_carry = False unit.harvest_amount = 0 @@ -280,6 +325,10 @@ class GameEngine: # Building SCVs stay put; mining SCVs move but skip combat if unit.status == UnitStatus.BUILDING: continue + if unit.status == UnitStatus.MOVING_TO_BUILD: + if unit.target_x is not None and unit.target_y is not None: + self._move_toward(unit, defn, unit.target_x, unit.target_y) + continue if unit.status in (UnitStatus.MINING_MINERALS, UnitStatus.MINING_GAS): if unit.target_x is not None and unit.target_y is not None: self._move_toward(unit, defn, unit.target_x, unit.target_y) @@ -325,6 +374,22 @@ class GameEngine: if unit.attack_target_id or unit.attack_target_building_id: self._combat_attack(unit, defn, all_units, player, enemy, sieged=False) + def _building_blocked_rects(self) -> list[tuple[float, float, float, float]]: + """Collision footprints (x, y, w, h) used for pathfinding and unit collision. + + Uses the shrunk collision box so units can pass between buildings + that have a small visual gap. + """ + rects: list[tuple[float, float, float, float]] = [] + for player in self.state.players.values(): + for b in player.buildings.values(): + if b.status == BuildingStatus.DESTROYED: + continue + defn = BUILDING_DEFS[b.building_type] + chw, chh = defn.col_hw(), defn.col_hh() + rects.append((b.x - chw, b.y - chh, chw * 2, chh * 2)) + return rects + def _set_unit_destination( self, unit: Unit, tx: float, ty: float, *, is_flying: bool ) -> None: @@ -334,40 +399,157 @@ class GameEngine: unit.path_waypoints = [] if is_flying: return - path = find_path(unit.x, unit.y, tx, ty) + blocked = self._building_blocked_rects() + sx, sy = unit.x, unit.y + path = find_path(sx, sy, tx, ty, blocked_rects=blocked) if path is None: - snapped = snap_to_walkable(tx, ty) - unit.target_x, unit.target_y = snapped[0], snapped[1] - return + # Start is inside a building footprint — snap start outside first + snapped_start = snap_to_walkable(sx, sy, blocked_rects=blocked) + if snapped_start != (sx, sy): + path = find_path(snapped_start[0], snapped_start[1], tx, ty, blocked_rects=blocked) + if path is None: + snapped_dst = snap_to_walkable(tx, ty, blocked_rects=blocked) + unit.target_x, unit.target_y = snapped_dst[0], snapped_dst[1] + return if not path: return unit.target_x, unit.target_y = path[0][0], path[0][1] unit.path_waypoints = [[p[0], p[1]] for p in path[1:]] + def _would_overlap( + self, unit: Unit, new_x: float, new_y: float, *, exclude_unit_id: Optional[str] = None + ) -> bool: + """True if (new_x, new_y) would overlap another unit or a building. + + Mining SCVs use soft collision (they can pass through each other, like in SC). + For other units, if two already overlap we allow moves that increase separation. + """ + _mining = (UnitStatus.MINING_MINERALS, UnitStatus.MINING_GAS) + unit_is_miner = unit.status in _mining + for player in self.state.players.values(): + for u in player.units.values(): + if u.id == unit.id or (exclude_unit_id and u.id == exclude_unit_id): + continue + # Miners pass through other miners (soft collision, matching SC behaviour) + if unit_is_miner and u.status in _mining: + continue + min_dist = 2 * UNIT_RADIUS + new_dist = math.hypot(new_x - u.x, new_y - u.y) + if new_dist >= min_dist: + continue + # Already overlapping: allow the move only if it increases separation + cur_dist = math.hypot(unit.x - u.x, unit.y - u.y) + if cur_dist < min_dist and new_dist >= cur_dist: + continue # moving away from an already-overlapping unit — allow + return True + for rx, ry, w, h in self._building_blocked_rects(): + px = max(rx, min(rx + w, new_x)) + py = max(ry, min(ry + h, new_y)) + if math.hypot(new_x - px, new_y - py) < UNIT_RADIUS: + return True + return False + def _move_toward(self, unit: Unit, defn: UnitDef, tx: float, ty: float) -> None: - dx = tx - unit.x - dy = ty - unit.y - dist = math.sqrt(dx * dx + dy * dy) + """Move unit up to one full step toward target, consuming intermediate waypoints smoothly.""" step = defn.move_speed * TICK_INTERVAL - if dist <= step: - unit.x = tx - unit.y = ty - if unit.path_waypoints: - next_wp = unit.path_waypoints.pop(0) - unit.target_x, unit.target_y = next_wp[0], next_wp[1] - return - if unit.status == UnitStatus.MOVING: - unit.status = UnitStatus.IDLE - unit.target_x = unit.target_y = None - elif unit.status == UnitStatus.ATTACKING: - unit.status = UnitStatus.IDLE - unit.target_x = unit.target_y = None - elif unit.status == UnitStatus.PATROLLING: - unit.target_x, unit.patrol_x = unit.patrol_x, unit.target_x - unit.target_y, unit.patrol_y = unit.patrol_y, unit.target_y + remaining = step + cur_tx, cur_ty = tx, ty + moved = False + arrived = False + + while remaining > 1e-6: + dx = cur_tx - unit.x + dy = cur_ty - unit.y + dist = math.sqrt(dx * dx + dy * dy) + + if dist < 1e-6: + # Already on this waypoint — advance immediately + if unit.path_waypoints: + nw = unit.path_waypoints.pop(0) + cur_tx = nw[0]; cur_ty = nw[1] + unit.target_x = cur_tx; unit.target_y = cur_ty + else: + arrived = True + break + + if dist <= remaining: + # Can reach this waypoint within remaining budget — snap and continue + unit.x = cur_tx + unit.y = cur_ty + remaining -= dist + moved = True + if unit.path_waypoints: + nw = unit.path_waypoints.pop(0) + cur_tx = nw[0]; cur_ty = nw[1] + unit.target_x = cur_tx; unit.target_y = cur_ty + else: + arrived = True + break + else: + # Partial move — try full remaining, then half, then slide + nx = dx / dist + ny = dy / dist + new_x = unit.x + nx * remaining + new_y = unit.y + ny * remaining + + if not self._would_overlap(unit, new_x, new_y): + unit.x = new_x + unit.y = new_y + moved = True + else: + hx = unit.x + nx * remaining * 0.5 + hy = unit.y + ny * remaining * 0.5 + if not self._would_overlap(unit, hx, hy): + unit.x = hx + unit.y = hy + moved = True + elif abs(nx) >= abs(ny): + if abs(nx) > 0.05 and not self._would_overlap(unit, unit.x + nx * remaining, unit.y): + unit.x += nx * remaining + moved = True + elif abs(ny) > 0.05 and not self._would_overlap(unit, unit.x, unit.y + ny * remaining): + unit.y += ny * remaining + moved = True + else: + if abs(ny) > 0.05 and not self._would_overlap(unit, unit.x, unit.y + ny * remaining): + unit.y += ny * remaining + moved = True + elif abs(nx) > 0.05 and not self._would_overlap(unit, unit.x + nx * remaining, unit.y): + unit.x += nx * remaining + moved = True + break + + unit.target_x = cur_tx + unit.target_y = cur_ty + + if moved or arrived: + unit.stuck_ticks = 0 else: - unit.x += (dx / dist) * step - unit.y += (dy / dist) * step + unit.stuck_ticks += 1 + if unit.stuck_ticks >= 4: + unit.stuck_ticks = 0 + if unit.path_waypoints: + nw = unit.path_waypoints.pop(0) + unit.target_x, unit.target_y = nw[0], nw[1] + else: + arrived = True + else: + return + + if not arrived: + return + if unit.status == UnitStatus.MOVING: + unit.status = UnitStatus.IDLE + unit.target_x = unit.target_y = None + elif unit.status == UnitStatus.ATTACKING: + unit.status = UnitStatus.IDLE + unit.target_x = unit.target_y = None + elif unit.status == UnitStatus.MOVING_TO_BUILD: + unit.status = UnitStatus.BUILDING + unit.target_x = unit.target_y = None + elif unit.status == UnitStatus.PATROLLING: + unit.target_x, unit.patrol_x = unit.patrol_x, unit.target_x + unit.target_y, unit.patrol_y = unit.patrol_y, unit.target_y def _combat_attack( self, @@ -517,18 +699,38 @@ class GameEngine: # ------------------------------------------------------------------ def _building_center(self, b: Building) -> tuple[float, float]: - defn = BUILDING_DEFS[b.building_type] - return (float(b.x) + defn.width / 2, float(b.y) + defn.height / 2) + return (b.x, b.y) def _dist_unit_to_building(self, unit: Unit, building: Building) -> float: - """Distance from unit center to nearest point on building footprint (carré le plus proche).""" + """Distance from unit center to nearest point on the building collision box.""" defn = BUILDING_DEFS[building.building_type] - x0, x1 = float(building.x), float(building.x) + defn.width - y0, y1 = float(building.y), float(building.y) + defn.height + chw, chh = defn.col_hw(), defn.col_hh() + x0, x1 = building.x - chw, building.x + chw + y0, y1 = building.y - chh, building.y + chh px = max(x0, min(x1, unit.x)) py = max(y0, min(y1, unit.y)) return math.sqrt((unit.x - px) ** 2 + (unit.y - py) ** 2) + def _nearest_building_entry(self, unit: Unit, building: Building) -> tuple[float, float]: + """Return a point just outside the nearest visual edge of a building, on the unit's side. + + SCVs use this to approach from whichever direction they're coming from, + so they can deposit/interact from any side instead of always queuing at one point. + Uses the visual box (not the collision box) so the SCV visually touches the building. + """ + defn = BUILDING_DEFS[building.building_type] + hw = defn.width / 2 + hh = defn.height / 2 + px = max(building.x - hw, min(building.x + hw, unit.x)) + py = max(building.y - hh, min(building.y + hh, unit.y)) + dx = unit.x - px + dy = unit.y - py + d = math.hypot(dx, dy) + margin = UNIT_RADIUS + 0.3 + if d > 0: + return (px + dx / d * margin, py + dy / d * margin) + return (building.x, building.y + hh + margin) + def _nearest_enemy_in_range( self, unit: Unit, @@ -557,17 +759,18 @@ class GameEngine: return best[1] if best else None def _resolve_zone(self, player_id: str, zone: str) -> tuple[float, float]: + import re as _re player = self.state.players[player_id] enemy = self.state.enemy_of(player_id) cc = player.command_center() - base_x = float(cc.x) + 2 if cc else float(MAP_WIDTH) / 2 - base_y = float(cc.y) + 2 if cc else float(MAP_HEIGHT) / 2 + base_x = cc.x if cc else float(MAP_WIDTH) / 2 + base_y = cc.y if cc else float(MAP_HEIGHT) / 2 if zone == "my_base": return (base_x, base_y) if zone == "enemy_base" and enemy: ecc = enemy.command_center() - return (float(ecc.x) + 2, float(ecc.y) + 2) if ecc else (MAP_WIDTH - 5, MAP_HEIGHT - 5) + return (ecc.x, ecc.y) if ecc else (MAP_WIDTH - 5, MAP_HEIGHT - 5) if zone == "center": return (MAP_WIDTH / 2, MAP_HEIGHT / 2) if zone == "top_left": @@ -587,6 +790,29 @@ class GameEngine: avg_x = sum(u.x for u in military) / len(military) avg_y = sum(u.y for u in military) / len(military) return (avg_x, avg_y) + + m = _re.match(r'^mineral_(\d+)$', zone) + if m: + idx = int(m.group(1)) - 1 + minerals = [ + r for r in self.state.game_map.resources + if r.resource_type == ResourceType.MINERAL and not r.is_depleted + ] + minerals.sort(key=lambda r: (r.x - base_x) ** 2 + (r.y - base_y) ** 2) + if 0 <= idx < len(minerals): + return (float(minerals[idx].x), float(minerals[idx].y)) + + m = _re.match(r'^geyser_(\d+)$', zone) + if m: + idx = int(m.group(1)) - 1 + geysers = [ + r for r in self.state.game_map.resources + if r.resource_type == ResourceType.GEYSER + ] + geysers.sort(key=lambda r: (r.x - base_x) ** 2 + (r.y - base_y) ** 2) + if 0 <= idx < len(geysers): + return (float(geysers[idx].x), float(geysers[idx].y)) + # Fallback: enemy base if enemy: ecc = enemy.command_center() @@ -635,38 +861,71 @@ class GameEngine: pass return [u.id for u in units] + # Vision radii (in cells) — must match frontend constants + _UNIT_VISION: dict[UnitType, float] = { + UnitType.SCV: 6, UnitType.MARINE: 6, UnitType.MEDIC: 6, + UnitType.GOLIATH: 8, UnitType.TANK: 8, UnitType.WRAITH: 9, + } + _BUILDING_VISION: dict[BuildingType, float] = { + BuildingType.COMMAND_CENTER: 10, BuildingType.SUPPLY_DEPOT: 7, + BuildingType.BARRACKS: 7, BuildingType.ENGINEERING_BAY: 7, + BuildingType.REFINERY: 7, BuildingType.FACTORY: 7, + BuildingType.ARMORY: 7, BuildingType.STARPORT: 7, + } + _SCV_BUILD_RANGE: float = 6.0 + + def _is_visible(self, player: PlayerState, x: float, y: float) -> bool: + """Return True if tile (x, y) is within vision of any own unit or building.""" + for u in player.units.values(): + r = self._UNIT_VISION.get(u.unit_type, 6.0) + if (u.x - x) ** 2 + (u.y - y) ** 2 <= r * r: + return True + for b in player.buildings.values(): + if b.status == BuildingStatus.DESTROYED: + continue + r = self._BUILDING_VISION.get(b.building_type, 7.0) + if (b.x - x) ** 2 + (b.y - y) ** 2 <= r * r: + return True + return False + def _find_build_position( - self, player: PlayerState, bt: BuildingType - ) -> Optional[tuple[int, int]]: - cc = player.command_center() - if not cc: - return None - origin_x, origin_y = cc.x, cc.y + self, player: PlayerState, bt: BuildingType, near_scv: Unit + ) -> Optional[tuple[float, float]]: + """Return CENTER coordinates for a new building, or None if no valid spot found.""" defn = BUILDING_DEFS[bt] + cx, cy = near_scv.x, near_scv.y - for radius in range(3, 18): + for radius in range(1, int(self._SCV_BUILD_RANGE) + 2): for dx in range(-radius, radius + 1): for dy in range(-radius, radius + 1): - x, y = origin_x + dx, origin_y + dy - if self._can_place(x, y, defn): - return (x, y) + tl_x, tl_y = int(cx) + dx, int(cy) + dy + tile_cx = tl_x + defn.width / 2.0 + tile_cy = tl_y + defn.height / 2.0 + if (tile_cx - cx) ** 2 + (tile_cy - cy) ** 2 > self._SCV_BUILD_RANGE ** 2: + continue + if not self._can_place(tl_x, tl_y, defn): + continue + if not self._is_visible(player, tile_cx, tile_cy): + continue + return (tile_cx, tile_cy) return None - def _can_place(self, x: int, y: int, defn: BuildingDef) -> bool: - if x < 0 or y < 0 or x + defn.width > MAP_WIDTH or y + defn.height > MAP_HEIGHT: + def _can_place(self, tl_x: int, tl_y: int, defn: BuildingDef) -> bool: + """Check if a building with given top-left corner can be placed (no overlap, in bounds).""" + if tl_x < 0 or tl_y < 0 or tl_x + defn.width > MAP_WIDTH or tl_y + defn.height > MAP_HEIGHT: return False - # Check overlap with all buildings + # Check overlap with existing buildings (stored as center coords) for player in self.state.players.values(): for b in player.buildings.values(): if b.status == BuildingStatus.DESTROYED: continue bd = BUILDING_DEFS[b.building_type] - if x < b.x + bd.width and x + defn.width > b.x \ - and y < b.y + bd.height and y + defn.height > b.y: + if tl_x < b.x + bd.width / 2 and tl_x + defn.width > b.x - bd.width / 2 \ + and tl_y < b.y + bd.height / 2 and tl_y + defn.height > b.y - bd.height / 2: return False # Check overlap with resources for res in self.state.game_map.resources: - if x <= res.x < x + defn.width and y <= res.y < y + defn.height: + if tl_x <= res.x < tl_x + defn.width and tl_y <= res.y < tl_y + defn.height: return False return True @@ -705,30 +964,37 @@ class GameEngine: return self._cmd_query_units(player, action) if t == ActionType.ASSIGN_TO_GROUP: return self._cmd_assign_to_group(player, action) - return ActionResult(action_type=t, success=False, message="Action inconnue.") + return ActionResult( + action_type=t, success=False, data={"error": "unknown_action"} + ) except Exception as exc: log.exception("Error applying action %s", action.type) - return ActionResult(action_type=str(action.type), success=False, message=str(exc)) + return ActionResult( + action_type=str(action.type), success=False, data={"error": "exception", "detail": str(exc)} + ) def _cmd_build(self, player: PlayerState, action: GameAction) -> ActionResult: raw = action.building_type if not raw: - return ActionResult(action_type="build", success=False, message="Type de bâtiment manquant.") + return ActionResult(action_type="build", success=False, data={"error": "build_missing_type"}) try: bt = BuildingType(raw) except ValueError: - return ActionResult(action_type="build", success=False, message=f"Bâtiment inconnu: {raw}.") + return ActionResult(action_type="build", success=False, data={"error": "build_unknown", "raw": raw}) if not can_build(bt, player): missing = missing_for_build(bt, player) names = ", ".join(m.value for m in missing) - return ActionResult(action_type="build", success=False, - message=f"Prérequis manquants: {names}.") + return ActionResult( + action_type="build", success=False, data={"error": "build_missing_prereq", "names": names} + ) defn = BUILDING_DEFS[bt] if player.minerals < defn.mineral_cost or player.gas < defn.gas_cost: - return ActionResult(action_type="build", success=False, - message=f"Ressources insuffisantes ({defn.mineral_cost}m/{defn.gas_cost}g requis).") + return ActionResult( + action_type="build", success=False, + data={"error": "build_insufficient_resources", "mineral": defn.mineral_cost, "gas": defn.gas_cost}, + ) # Clamp count to what resources and SCVs allow count = max(1, min(action.count, 5)) @@ -745,27 +1011,31 @@ class GameEngine: ) count = min(count, len(available_scvs)) if count == 0: - return ActionResult(action_type="build", success=False, message="Aucun SCV disponible.") + return ActionResult(action_type="build", success=False, data={"error": "build_no_scv"}) cc = player.command_center() cx, cy = (float(cc.x), float(cc.y)) if cc else (0.0, 0.0) built = 0 for i in range(count): - # Find position for this building + scv = available_scvs[i] + + # Find center position for this building if bt == BuildingType.REFINERY: geyser = self.state.game_map.nearest_geyser_without_refinery(cx, cy) if not geyser: break - pos: tuple[int, int] = (geyser.x, geyser.y) + # Refinery (2×2) centered on geyser tile (+1 from integer geyser coord) + pos_cx: float = geyser.x + 1.0 + pos_cy: float = geyser.y + 1.0 + if not self._is_visible(player, pos_cx, pos_cy): + break geyser.has_refinery = True else: - pos_opt = self._find_build_position(player, bt) + pos_opt = self._find_build_position(player, bt, scv) if not pos_opt: break - pos = pos_opt - - scv = available_scvs[i] + pos_cx, pos_cy = pos_opt # Unassign from resource if mining if scv.assigned_resource_id: res = self.state.game_map.get_resource(scv.assigned_resource_id) @@ -778,62 +1048,80 @@ class GameEngine: player.minerals -= defn.mineral_cost player.gas -= defn.gas_cost - building = Building.create(bt, player.player_id, pos[0], pos[1]) + building = Building.create(bt, player.player_id, pos_cx, pos_cy) player.buildings[building.id] = building - scv.status = UnitStatus.BUILDING + scv.status = UnitStatus.MOVING_TO_BUILD scv.building_target_id = building.id - scv.target_x = float(pos[0]) - scv.target_y = float(pos[1]) + # Navigate to the nearest point just outside the building edge + bw = float(defn.width) + bh = float(defn.height) + edge_x = max(pos_cx - bw / 2, min(pos_cx + bw / 2, scv.x)) + edge_y = max(pos_cy - bh / 2, min(pos_cy + bh / 2, scv.y)) + dx = scv.x - edge_x + dy = scv.y - edge_y + edge_dist = math.hypot(dx, dy) + approach_margin = UNIT_RADIUS + 0.6 + if edge_dist > 0: + dest_x = edge_x + dx / edge_dist * approach_margin + dest_y = edge_y + dy / edge_dist * approach_margin + else: + dest_x = pos_cx + bw / 2 + approach_margin + dest_y = pos_cy + self._set_unit_destination(scv, dest_x, dest_y, is_flying=False) built += 1 if built == 0: - return ActionResult(action_type="build", success=False, - message="Impossible de trouver un emplacement.") - return ActionResult(action_type="build", success=True, - message=f"{built} {bt.value}(s) en construction.") + return ActionResult(action_type="build", success=False, data={"error": "build_no_placement"}) + return ActionResult( + action_type="build", success=True, + data={"built": built, "building": bt.value}, + ) def _cmd_train(self, player: PlayerState, action: GameAction) -> ActionResult: raw = action.unit_type if not raw: - return ActionResult(action_type="train", success=False, message="Type d'unité manquant.") + return ActionResult(action_type="train", success=False, data={"error": "train_missing_type"}) try: ut = UnitType(raw) except ValueError: - return ActionResult(action_type="train", success=False, message=f"Unité inconnue: {raw}.") + return ActionResult(action_type="train", success=False, data={"error": "train_unknown", "raw": raw}) if not can_train(ut, player): missing = missing_for_train(ut, player) names = ", ".join(m.value for m in missing) - return ActionResult(action_type="train", success=False, - message=f"Prérequis manquants: {names}.") + return ActionResult( + action_type="train", success=False, data={"error": "train_missing_prereq", "names": names} + ) defn = UNIT_DEFS[ut] producer_type = get_producer(ut) producers = player.active_buildings_of(producer_type) if not producers: - return ActionResult(action_type="train", success=False, - message=f"Aucun {producer_type.value} actif.") + return ActionResult( + action_type="train", success=False, + data={"error": "train_no_producer", "producer": producer_type.value}, + ) count = max(1, min(action.count, 20)) num_producers = len(producers) - # Resources are checked for the full requested count total_minerals = defn.mineral_cost * count total_gas = defn.gas_cost * count if player.minerals < total_minerals or player.gas < total_gas: - return ActionResult(action_type="train", success=False, - message="Ressources insuffisantes.") + return ActionResult( + action_type="train", success=False, data={"error": "train_insufficient_resources"} + ) - # Supply check: account for actual units + already-queued units + new batch queued_supply = sum( UNIT_DEFS[UnitType(item.unit_type)].supply_cost for b in player.buildings.values() for item in b.production_queue ) if player.supply_used + queued_supply + defn.supply_cost * count > player.supply_max: - return ActionResult(action_type="train", success=False, - message="Supply insuffisant.") + return ActionResult( + action_type="train", success=False, data={"error": "train_insufficient_supply"} + ) from .buildings import ProductionItem # local import to avoid cycle for i in range(count): @@ -849,13 +1137,15 @@ class GameEngine: player.minerals -= total_minerals player.gas -= total_gas - return ActionResult(action_type="train", success=True, - message=f"{count} {ut.value}(s) en production.") + return ActionResult( + action_type="train", success=True, + data={"count": count, "unit": ut.value}, + ) def _cmd_move(self, player: PlayerState, action: GameAction) -> ActionResult: units = self._resolve_selector(player, action.unit_selector or "all_military") if not units: - return ActionResult(action_type="move", success=False, message="Aucune unité sélectionnée.") + return ActionResult(action_type="move", success=False, data={"error": "no_units_selected"}) tx, ty = self._resolve_zone(player.player_id, action.target_zone or "center") for unit in units: if unit.is_sieged: @@ -865,14 +1155,16 @@ class GameEngine: unit.attack_target_building_id = None self._set_unit_destination(unit, tx, ty, is_flying=UNIT_DEFS[unit.unit_type].is_flying) move_ack = [{"kind": "move_ack", "unit_type": units[0].unit_type.value}] - return ActionResult(action_type="move", success=True, - message=f"{len(units)} unité(s) en mouvement vers {action.target_zone}.", - sound_events=move_ack) + zone = action.target_zone or "center" + return ActionResult( + action_type="move", success=True, + data={"n": len(units), "zone": zone}, sound_events=move_ack, + ) def _cmd_attack(self, player: PlayerState, action: GameAction) -> ActionResult: units = self._resolve_selector(player, action.unit_selector or "all_military") if not units: - return ActionResult(action_type="attack", success=False, message="Aucune unité sélectionnée.") + return ActionResult(action_type="attack", success=False, data={"error": "no_units_selected"}) tx, ty = self._resolve_zone(player.player_id, action.target_zone or "enemy_base") for unit in units: if unit.is_sieged: @@ -882,34 +1174,40 @@ class GameEngine: unit.attack_target_building_id = None self._set_unit_destination(unit, tx, ty, is_flying=UNIT_DEFS[unit.unit_type].is_flying) move_ack = [{"kind": "move_ack", "unit_type": units[0].unit_type.value}] - return ActionResult(action_type="attack", success=True, - message=f"{len(units)} unité(s) envoyées à l'attaque vers {action.target_zone}.", - sound_events=move_ack) + zone = action.target_zone or "enemy_base" + return ActionResult( + action_type="attack", success=True, + data={"n": len(units), "zone": zone}, sound_events=move_ack, + ) def _cmd_siege(self, player: PlayerState, action: GameAction, siege: bool) -> ActionResult: tanks = self._resolve_selector(player, action.unit_selector or "all_tanks") tanks = [u for u in tanks if u.unit_type == UnitType.TANK] if not tanks: - return ActionResult(action_type="siege", success=False, message="Aucun tank disponible.") + return ActionResult(action_type="siege", success=False, data={"error": "siege_no_tanks"}) for tank in tanks: tank.is_sieged = siege tank.status = UnitStatus.SIEGED if siege else UnitStatus.IDLE if siege: tank.target_x = tank.target_y = None - mode = "siège" if siege else "mobile" - return ActionResult(action_type="siege", success=True, - message=f"{len(tanks)} tank(s) en mode {mode}.") + mode = "siege" if siege else "mobile" + return ActionResult( + action_type="siege", success=True, + data={"n": len(tanks), "mode": mode}, + ) def _cmd_cloak(self, player: PlayerState, action: GameAction, cloak: bool) -> ActionResult: wraiths = self._resolve_selector(player, action.unit_selector or "all_wraiths") wraiths = [u for u in wraiths if u.unit_type == UnitType.WRAITH] if not wraiths: - return ActionResult(action_type="cloak", success=False, message="Aucun wraith disponible.") + return ActionResult(action_type="cloak", success=False, data={"error": "cloak_no_wraiths"}) for wraith in wraiths: wraith.is_cloaked = cloak - state = "activé" if cloak else "désactivé" - return ActionResult(action_type="cloak", success=True, - message=f"Camouflage {state} sur {len(wraiths)} wraith(s).") + state = "on" if cloak else "off" + return ActionResult( + action_type="cloak", success=True, + data={"n": len(wraiths), "state": state}, + ) def _cmd_gather(self, player: PlayerState, action: GameAction) -> ActionResult: resource_type = (action.resource_type or "minerals").lower() @@ -919,7 +1217,7 @@ class GameEngine: scvs = self._resolve_selector(player, action.unit_selector or "idle_scv") scvs = [u for u in scvs if u.unit_type == UnitType.SCV] if not scvs: - return ActionResult(action_type="gather", success=False, message="Aucun SCV disponible.") + return ActionResult(action_type="gather", success=False, data={"error": "gather_no_scv"}) assigned = 0 if resource_type == "gas": @@ -940,8 +1238,23 @@ class GameEngine: geyser.assigned_scv_ids.append(scv.id) assigned += 1 else: + # Distribute SCVs across patches evenly: pick the patch with fewest assigned SCVs + # (avoids funnelling all 5 SCVs to the same nearest patch) + mineral_patches = [ + r for r in self.state.game_map.resources + if r.resource_type.value == "mineral" and not r.is_depleted and r.has_capacity + ] + if not mineral_patches: + return ActionResult(action_type="gather", success=False, data={"error": "gather_no_resource"}) + for scv in scvs: - patch = self.state.game_map.nearest_mineral(cx, cy) + if not mineral_patches: + break + # Choose patch with fewest assigned SCVs (tie-break: nearest to CC) + patch = min( + mineral_patches, + key=lambda r: (len(r.assigned_scv_ids), (r.x - cx) ** 2 + (r.y - cy) ** 2) + ) if not patch: break if scv.assigned_resource_id: @@ -955,12 +1268,17 @@ class GameEngine: self._set_unit_destination(scv, float(patch.x), float(patch.y), is_flying=False) patch.assigned_scv_ids.append(scv.id) assigned += 1 + # Remove full patches from candidates + mineral_patches = [r for r in mineral_patches if r.has_capacity] if assigned == 0: - return ActionResult(action_type="gather", success=False, - message="Aucune ressource disponible ou aucun SCV libre.") - return ActionResult(action_type="gather", success=True, - message=f"{assigned} SCV(s) envoyés collecter {resource_type}.") + return ActionResult( + action_type="gather", success=False, data={"error": "gather_no_resource"} + ) + return ActionResult( + action_type="gather", success=True, + data={"n": assigned, "resource": resource_type}, + ) def _cmd_stop(self, player: PlayerState, action: GameAction) -> ActionResult: units = self._resolve_selector(player, action.unit_selector or "all_military") @@ -970,13 +1288,12 @@ class GameEngine: unit.path_waypoints = [] unit.attack_target_id = None unit.attack_target_building_id = None - return ActionResult(action_type="stop", success=True, - message=f"{len(units)} unité(s) stoppées.") + return ActionResult(action_type="stop", success=True, data={"n": len(units)}) def _cmd_patrol(self, player: PlayerState, action: GameAction) -> ActionResult: units = self._resolve_selector(player, action.unit_selector or "all_military") if not units: - return ActionResult(action_type="patrol", success=False, message="Aucune unité sélectionnée.") + return ActionResult(action_type="patrol", success=False, data={"error": "no_units_selected"}) tx, ty = self._resolve_zone(player.player_id, action.target_zone or "center") for unit in units: if unit.is_sieged: @@ -986,12 +1303,17 @@ class GameEngine: unit.status = UnitStatus.PATROLLING self._set_unit_destination(unit, tx, ty, is_flying=UNIT_DEFS[unit.unit_type].is_flying) move_ack = [{"kind": "move_ack", "unit_type": units[0].unit_type.value}] - return ActionResult(action_type="patrol", success=True, - message=f"{len(units)} unité(s) en patrouille vers {action.target_zone}.", - sound_events=move_ack) + zone = action.target_zone or "center" + return ActionResult( + action_type="patrol", success=True, + data={"n": len(units), "zone": zone}, sound_events=move_ack, + ) def _cmd_query(self, player: PlayerState, action: GameAction) -> ActionResult: - return ActionResult(action_type="query", success=True, message=player.summary()) + return ActionResult( + action_type="query", success=True, + data={"summary": player.summary(self._cmd_lang)}, + ) def _cmd_query_units(self, player: PlayerState, action: GameAction) -> ActionResult: """Query units by zone and/or type; return their IDs in result.unit_ids.""" @@ -1001,7 +1323,7 @@ class GameEngine: return ActionResult( action_type="query_units", success=True, - message=f"{len(ids)} unité(s) trouvée(s).", + data={"n": len(ids)}, unit_ids=ids, ) @@ -1012,7 +1334,7 @@ class GameEngine: return ActionResult( action_type="assign_to_group", success=False, - message="Groupe invalide (utilise 1, 2 ou 3).", + data={"error": "group_invalid"}, ) ids = list(action.unit_ids) if action.unit_ids else [] valid_ids = [uid for uid in ids if uid in player.units] @@ -1020,7 +1342,7 @@ class GameEngine: return ActionResult( action_type="assign_to_group", success=True, - message=f"Groupe {gi} : {len(valid_ids)} unité(s) assignée(s).", + data={"gi": gi, "n": len(valid_ids)}, ) # ------------------------------------------------------------------ diff --git a/backend/game/map.py b/backend/game/map.py index a212c33be9d1ac1830fabaceb7552af0ac74522d..70fa563d60673791e327ac2eaf65695716350332 100644 --- a/backend/game/map.py +++ b/backend/game/map.py @@ -7,25 +7,25 @@ from enum import Enum from pydantic import BaseModel, Field -MAP_WIDTH = 40 -MAP_HEIGHT = 40 +MAP_WIDTH = 80 +MAP_HEIGHT = 80 # Starting positions (top-left corner of Command Center footprint) — fallback if no game_positions.json -PLAYER1_START: tuple[int, int] = (4, 5) -PLAYER2_START: tuple[int, int] = (32, 32) +PLAYER1_START: tuple[int, int] = (8, 10) +PLAYER2_START: tuple[int, int] = (64, 64) # Absolute resource positions (fallback) _P1_MINERALS: list[tuple[int, int]] = [ - (2, 2), (3, 2), (4, 2), (5, 2), (6, 2), - (2, 3), (6, 3), (3, 9), + (4, 4), (6, 4), (8, 4), (10, 4), (12, 4), + (4, 6), (12, 6), (6, 18), ] -_P1_GEYSERS: list[tuple[int, int]] = [(2, 9), (7, 9)] +_P1_GEYSERS: list[tuple[int, int]] = [(4, 18), (14, 18)] _P2_MINERALS: list[tuple[int, int]] = [ - (33, 37), (34, 37), (35, 37), (36, 37), (37, 37), - (33, 36), (37, 36), (34, 30), + (66, 74), (68, 74), (70, 74), (72, 74), (74, 74), + (66, 72), (74, 72), (68, 60), ] -_P2_GEYSERS: list[tuple[int, int]] = [(32, 30), (37, 30)] +_P2_GEYSERS: list[tuple[int, int]] = [(64, 60), (74, 60)] def _game_positions_path() -> Path | None: @@ -33,32 +33,266 @@ def _game_positions_path() -> Path | None: return p if p.exists() else None -def get_start_positions() -> tuple[tuple[int, int], tuple[int, int]]: - """Return (player1_start, player2_start) in game grid. With 3 positions in file, 2 are chosen at random.""" +def _to_game_coords(x: float, y: float) -> tuple[int, int]: + gx = max(0, min(MAP_WIDTH - 1, int(round(x * MAP_WIDTH / 100.0)))) + gy = max(0, min(MAP_HEIGHT - 1, int(round(y * MAP_HEIGHT / 100.0)))) + return (gx, gy) + + +def _resources_from_start_entry(entry: dict) -> list["Resource"]: + """Build list of Resource from a starting_position entry that has nested minerals/geysers.""" + resources: list[Resource] = [] + for m in entry.get("minerals") or []: + x, y = int(m.get("x", 0)), int(m.get("y", 0)) + if 0 <= x < MAP_WIDTH and 0 <= y < MAP_HEIGHT: + resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) + for g in entry.get("geysers") or []: + x, y = int(g.get("x", 0)), int(g.get("y", 0)) + if 0 <= x < MAP_WIDTH and 0 <= y < MAP_HEIGHT: + resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) + return resources + + +# 8 minerals in a ring at ~6 tiles from center, 2 geysers at ~8 tiles. +# All distances are deterministic so every base gets the same layout. +_RESOURCE_OFFSETS_MINERAL = [ + (6, 0), (5, 3), (0, 6), (-5, 3), (-6, 0), (-5, -3), (0, -6), (5, -3), +] +_RESOURCE_OFFSETS_GEYSER = [(7, 4), (-7, 4)] + + +def _generate_resources_at(gx: int, gy: int) -> list["Resource"]: + """Generate 8 mineral patches and 2 geysers at fixed distances around a game coordinate. + + Every base always gets the same symmetric layout so distances are consistent. + """ + resources: list[Resource] = [] + seen: set[tuple[int, int]] = set() + for dx, dy in _RESOURCE_OFFSETS_MINERAL: + x = max(0, min(MAP_WIDTH - 1, gx + dx)) + y = max(0, min(MAP_HEIGHT - 1, gy + dy)) + if (x, y) not in seen: + seen.add((x, y)) + resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) + for dx, dy in _RESOURCE_OFFSETS_GEYSER: + x = max(0, min(MAP_WIDTH - 1, gx + dx)) + y = max(0, min(MAP_HEIGHT - 1, gy + dy)) + if (x, y) not in seen: + seen.add((x, y)) + resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) + return resources + + +def get_start_positions() -> tuple[tuple[float, float], tuple[float, float]]: + """Return (player1_start, player2_start) in game coords. With 3 positions in file, 2 are chosen at random.""" + start1, _, start2, _ = get_start_data() + return start1, start2 + + +def get_all_map_resources() -> list["Resource"]: + """Return resources for ALL starting positions AND expansion positions. + + Resources are ALWAYS generated via _generate_resources_at() using fixed offsets so that + every base — regardless of origin — has minerals and geysers at a consistent distance + from its Command Center. Embedded minerals in game_positions.json are intentionally + ignored in favour of this deterministic layout. + """ + path = _game_positions_path() + if path: + try: + with open(path, encoding="utf-8") as f: + data = json.load(f) + all_entries = ( + list(data.get("starting_positions") or []) + + list(data.get("expansion_positions") or []) + ) + if all_entries: + resources: list[Resource] = [] + for entry in all_entries: + gx, gy = _to_game_coords(float(entry.get("x", 0)), float(entry.get("y", 0))) + resources.extend(_generate_resources_at(gx, gy)) + return resources + except (OSError, json.JSONDecodeError, KeyError): + pass + # Fallback: derive starts + expansions from nav_points + compiled_path = Path(__file__).resolve().parent.parent / "static" / "compiled_map.json" + if compiled_path.exists(): + try: + with open(compiled_path, encoding="utf-8") as f: + nav_data = json.load(f) + pts = nav_data.get("nav_points") or [] + if len(pts) >= 4: + coords = [(float(p[0]), float(p[1])) for p in pts] + starts, expansions, _ = _fallback_all_resources(coords) + resources = [] + for pos in starts + expansions: + gx, gy = int(round(pos[0])), int(round(pos[1])) + resources.extend(_generate_resources_at(gx, gy)) + return resources + except (OSError, json.JSONDecodeError, KeyError, ValueError): + pass + _, r1, _, r2 = _fallback_from_nav() + return r1 + r2 + + +def get_start_data() -> tuple[ + tuple[float, float], list[Resource], tuple[float, float], list[Resource] +]: + """Return (start1, resources1, start2, resources2) for the 2 chosen bases. With 3 positions, 2 are chosen at random; resources are only those for the chosen bases.""" import random path = _game_positions_path() if not path: - return PLAYER1_START, PLAYER2_START + return _fallback_from_nav() try: with open(path, encoding="utf-8") as f: data = json.load(f) starts = data.get("starting_positions") or [] if len(starts) >= 3: - def to_game(p: dict) -> tuple[int, int]: - x = p.get("x", 0) * MAP_WIDTH / 100.0 - y = p.get("y", 0) * MAP_HEIGHT / 100.0 - return (max(0, min(MAP_WIDTH - 1, int(round(x)))), max(0, min(MAP_HEIGHT - 1, int(round(y))))) chosen = random.sample(starts, 2) - return to_game(chosen[0]), to_game(chosen[1]) + s1, s2 = chosen[0], chosen[1] + start1 = _to_game_coords(s1.get("x", 0), s1.get("y", 0)) + start2 = _to_game_coords(s2.get("x", 0), s2.get("y", 0)) + res1 = _resources_from_start_entry(s1) if "minerals" in s1 else [] + res2 = _resources_from_start_entry(s2) if "minerals" in s2 else [] + return (start1, res1, start2, res2) if len(starts) >= 2: - def to_game(p: dict) -> tuple[int, int]: - x = p.get("x", 0) * MAP_WIDTH / 100.0 - y = p.get("y", 0) * MAP_HEIGHT / 100.0 - return (max(0, min(MAP_WIDTH - 1, int(round(x)))), max(0, min(MAP_HEIGHT - 1, int(round(y))))) - return to_game(starts[0]), to_game(starts[1]) + s1, s2 = starts[0], starts[1] + start1 = _to_game_coords(s1.get("x", 0), s1.get("y", 0)) + start2 = _to_game_coords(s2.get("x", 0), s2.get("y", 0)) + res1 = _resources_from_start_entry(s1) if "minerals" in s1 else [] + res2 = _resources_from_start_entry(s2) if "minerals" in s2 else [] + return (start1, res1, start2, res2) except (OSError, json.JSONDecodeError, KeyError): pass - return PLAYER1_START, PLAYER2_START + return _fallback_from_nav() + + +def _pick_nav_corners(coords: list[tuple[float, float]]) -> tuple[tuple[float, float], tuple[float, float]]: + """Pick the 2 most separated nav_points (opposing corners).""" + pa1 = min(coords, key=lambda p: p[0] + p[1]) + pa2 = max(coords, key=lambda p: p[0] + p[1]) + pb1 = min(coords, key=lambda p: p[0] - p[1]) + pb2 = max(coords, key=lambda p: p[0] - p[1]) + d_a = (pa2[0] - pa1[0]) ** 2 + (pa2[1] - pa1[1]) ** 2 + d_b = (pb2[0] - pb1[0]) ** 2 + (pb2[1] - pb1[1]) ** 2 + return (pa1, pa2) if d_a >= d_b else (pb1, pb2) + + +def _pick_expansion_nav_positions( + coords: list[tuple[float, float]], + start_positions: list[tuple[float, float]], + count: int = 3, + min_dist_from_start: float = 12.0, +) -> list[tuple[float, float]]: + """Pick expansion positions: closest nav_point to each pair midpoint, at least min_dist from any start.""" + def dist2(a: tuple[float, float], b: tuple[float, float]) -> float: + return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + + candidates: list[tuple[float, float]] = [] + n = len(start_positions) + for i in range(n): + for j in range(i + 1, n): + mx = (start_positions[i][0] + start_positions[j][0]) / 2 + my = (start_positions[i][1] + start_positions[j][1]) / 2 + # Find closest nav_point to midpoint that is far enough from all starts + nearby = [ + p for p in coords + if all(dist2(p, s) >= min_dist_from_start ** 2 for s in start_positions) + ] + if nearby: + best = min(nearby, key=lambda p: dist2(p, (mx, my))) + # Avoid duplicates + if all(dist2(best, c) > 4.0 for c in candidates): + candidates.append(best) + + return candidates[:count] + + +def _fallback_all_resources( + coords: list[tuple[float, float]], +) -> tuple[list[tuple[float, float]], list[tuple[float, float]], list[list[Resource]]]: + """Pick start and expansion positions from nav_points and generate resources at each. + + Returns (start_positions, expansion_positions, all_resource_lists). + Resources are generated via _generate_resources_at for consistent fixed-distance placement. + """ + s1_f, s2_f = _pick_nav_corners(coords) + def min_dist2(p: tuple[float, float]) -> float: + return min((p[0]-s1_f[0])**2+(p[1]-s1_f[1])**2, (p[0]-s2_f[0])**2+(p[1]-s2_f[1])**2) + s3_f = max(coords, key=min_dist2) + + starts = [s1_f, s2_f, s3_f] + expansions = _pick_expansion_nav_positions(coords, starts, count=3) + + all_resources: list[list[Resource]] = [] + for pos in starts + expansions: + gx, gy = int(round(pos[0])), int(round(pos[1])) + all_resources.append(_generate_resources_at(gx, gy)) + + return starts, expansions, all_resources + + +def _fallback_from_nav() -> tuple[ + tuple[int, int], list[Resource], tuple[int, int], list[Resource] +]: + """Pick 2 well-separated start positions from compiled nav_points and generate minerals near each.""" + compiled_path = Path(__file__).resolve().parent.parent / "static" / "compiled_map.json" + if compiled_path.exists(): + try: + with open(compiled_path, encoding="utf-8") as f: + data = json.load(f) + pts = data.get("nav_points") or [] + if len(pts) >= 4: + coords = [(float(p[0]), float(p[1])) for p in pts] + s1_f, s2_f = _pick_nav_corners(coords) + res1 = _nav_minerals_around(s1_f, coords) + res2 = _nav_minerals_around(s2_f, coords) + return (s1_f, res1, s2_f, res2) + except (OSError, json.JSONDecodeError, KeyError, ValueError): + pass + # Last resort: use original hardcoded values (may be outside walkable area on this map) + resources: list[Resource] = [] + for x, y in _P1_MINERALS: + resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) + for x, y in _P1_GEYSERS: + resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) + r2: list[Resource] = [] + for x, y in _P2_MINERALS: + r2.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) + for x, y in _P2_GEYSERS: + r2.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) + return ( + (float(PLAYER1_START[0]), float(PLAYER1_START[1])), + resources, + (float(PLAYER2_START[0]), float(PLAYER2_START[1])), + r2, + ) + + +def _nav_minerals_around( + center: tuple[float, float], + all_nav: list[tuple[float, float]], + mineral_count: int = 7, + geyser_count: int = 1, + mineral_radius: float = 6.0, + geyser_radius: float = 8.0, +) -> list[Resource]: + """Generate minerals and geysers at nav_points near a start position.""" + cx, cy = center + nearby = sorted( + [p for p in all_nav if p != center and (p[0]-cx)**2 + (p[1]-cy)**2 <= mineral_radius**2], + key=lambda p: (p[0]-cx)**2 + (p[1]-cy)**2, + ) + resources: list[Resource] = [] + for p in nearby[:mineral_count]: + resources.append(Resource(resource_type=ResourceType.MINERAL, x=int(round(p[0])), y=int(round(p[1])))) + geyser_nearby = sorted( + [p for p in all_nav if p != center and (p[0]-cx)**2 + (p[1]-cy)**2 <= geyser_radius**2], + key=lambda p: -((p[0]-cx)**2 + (p[1]-cy)**2), + ) + for p in geyser_nearby[:geyser_count]: + resources.append(Resource(resource_type=ResourceType.GEYSER, x=int(round(p[0])), y=int(round(p[1])))) + return resources # Named map zones resolved to (x, y) center coordinates # Zone values depend on player — resolved at engine level using player start positions @@ -99,35 +333,32 @@ class GameMap(BaseModel): resources: list[Resource] = Field(default_factory=list) @classmethod - def create_default(cls) -> "GameMap": + def create_default(cls, resources: list[Resource] | None = None) -> "GameMap": + """Create map with given resources, or load from file (legacy flat minerals/geysers), or use hardcoded fallback.""" + if resources is not None: + return cls(resources=resources) path = _game_positions_path() if path: try: with open(path, encoding="utf-8") as f: data = json.load(f) - resources = [] + # Legacy format: top-level minerals/geysers + flat_resources = [] for m in data.get("minerals") or []: x, y = int(m.get("x", 0)), int(m.get("y", 0)) if 0 <= x < MAP_WIDTH and 0 <= y < MAP_HEIGHT: - resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) + flat_resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) for g in data.get("geysers") or []: x, y = int(g.get("x", 0)), int(g.get("y", 0)) if 0 <= x < MAP_WIDTH and 0 <= y < MAP_HEIGHT: - resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) - if resources: - return cls(resources=resources) + flat_resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) + if flat_resources: + return cls(resources=flat_resources) except (OSError, json.JSONDecodeError, KeyError): pass - resources = [] - for x, y in _P1_MINERALS: - resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) - for x, y in _P1_GEYSERS: - resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) - for x, y in _P2_MINERALS: - resources.append(Resource(resource_type=ResourceType.MINERAL, x=x, y=y)) - for x, y in _P2_GEYSERS: - resources.append(Resource(resource_type=ResourceType.GEYSER, x=x, y=y)) - return cls(resources=resources) + # Use nav-based fallback which derives positions from the actual compiled map + _, r1, _, r2 = _fallback_from_nav() + return cls(resources=r1 + r2) def get_resource(self, resource_id: str) -> Resource | None: return next((r for r in self.resources if r.id == resource_id), None) diff --git a/backend/game/map_compiler.py b/backend/game/map_compiler.py new file mode 100644 index 0000000000000000000000000000000000000000..3b25d06417bb13e9eb3b60e2f041592d3aa20c63 --- /dev/null +++ b/backend/game/map_compiler.py @@ -0,0 +1,157 @@ +""" +Compile walkable polygons into a single figure: one exterior boundary + holes (internal non-walkable zones). +Produces compiled_map.json used by pathfinding for walkable test and optional nav points. +""" + +from __future__ import annotations + +import json +from pathlib import Path + + +def _signed_area(polygon: list[tuple[float, float]]) -> float: + """Signed area (positive = CCW).""" + n = len(polygon) + if n < 3: + return 0.0 + area = 0.0 + for i in range(n): + j = (i + 1) % n + area += polygon[i][0] * polygon[j][1] + area -= polygon[j][0] * polygon[i][1] + return area / 2.0 + + +def _centroid(polygon: list[tuple[float, float]]) -> tuple[float, float]: + n = len(polygon) + if n == 0: + return (0.0, 0.0) + cx = sum(p[0] for p in polygon) / n + cy = sum(p[1] for p in polygon) / n + return (cx, cy) + + +def _point_in_polygon(x: float, y: float, polygon: list[tuple[float, float]]) -> bool: + n = len(polygon) + inside = False + j = n - 1 + for i in range(n): + xi, yi = polygon[i] + xj, yj = polygon[j] + if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi): + inside = not inside + j = i + return inside + + +def compile_walkable( + polygons: list[list[tuple[float, float]]], +) -> tuple[list[tuple[float, float]], list[list[tuple[float, float]]]]: + """ + From a list of polygons (each list of (x,y) in 0-100 space), + return (exterior, holes). + Exterior = polygon with largest absolute area. + Holes = polygons whose centroid is inside the exterior. + """ + if not polygons: + return ([], []) + + # Filter to valid polygons + valid = [p for p in polygons if len(p) >= 3] + if not valid: + return ([], []) + + # Exterior = largest area + by_area = [(abs(_signed_area(p)), p) for p in valid] + by_area.sort(key=lambda x: -x[0]) + exterior = by_area[0][1] + others = [p for _, p in by_area[1:]] + + holes: list[list[tuple[float, float]]] = [] + for poly in others: + cx, cy = _centroid(poly) + if _point_in_polygon(cx, cy, exterior): + holes.append(poly) + + return (exterior, holes) + + +def build_nav_points( + polygons: list[list[tuple[float, float]]], + scale_x: float, + scale_y: float, + step: float = 2.0, +) -> list[list[float]]: + """ + Generate navigation points (in game coordinates) for ALL walkable polygons. + Used for pathfinding graph: units can path between these points. + """ + if not polygons: + return [] + + all_xs = [p[0] for poly in polygons for p in poly] + all_ys = [p[1] for poly in polygons for p in poly] + min_x, max_x = min(all_xs), max(all_xs) + min_y, max_y = min(all_ys), max(all_ys) + + def is_walkable_100(x: float, y: float) -> bool: + return any(_point_in_polygon(x, y, poly) for poly in polygons) + + points: list[list[float]] = [] + x = min_x + while x <= max_x: + y = min_y + while y <= max_y: + if is_walkable_100(x, y): + points.append([x * scale_x / 100.0, y * scale_y / 100.0]) + y += step + x += step + return points + + +def run_compiler( + walkable_path: Path, + output_path: Path, + map_width: float = 80.0, + map_height: float = 80.0, + nav_step: float = 2.0, +) -> None: + """Load walkable.json, compile to exterior + holes, write compiled_map.json.""" + scale_x = map_width / 100.0 + scale_y = map_height / 100.0 + + if not walkable_path.exists(): + raise FileNotFoundError(f"Walkable file not found: {walkable_path}") + + with open(walkable_path, encoding="utf-8") as f: + data = json.load(f) + + raw = data.get("polygons", []) + if not raw and data.get("polygon"): + raw = [data["polygon"]] + + polygons: list[list[tuple[float, float]]] = [] + for poly in raw: + if len(poly) < 3: + continue + polygons.append([(float(p[0]), float(p[1])) for p in poly]) + + exterior, holes = compile_walkable(polygons) + + nav_points_game = build_nav_points(polygons, map_width, map_height, step=nav_step) + + out = { + "exterior": [[round(x, 4), round(y, 4)] for x, y in exterior], + "holes": [[[round(x, 4), round(y, 4)] for x, y in h] for h in holes], + "nav_points": [[round(x, 4), round(y, 4)] for x, y in nav_points_game], + } + with open(output_path, "w", encoding="utf-8") as f: + json.dump(out, f, indent=2) + + +if __name__ == "__main__": + static_dir = Path(__file__).resolve().parent.parent / "static" + run_compiler( + static_dir / "walkable.json", + static_dir / "compiled_map.json", + ) diff --git a/backend/game/pathfinding.py b/backend/game/pathfinding.py index 546daf901481ffce5fa19e15fb93935dc3e4de8c..31ed6fd8f9fc27f54193a42daf5c3e4ba0e581d9 100644 --- a/backend/game/pathfinding.py +++ b/backend/game/pathfinding.py @@ -1,28 +1,150 @@ """ -Pathfinding constrained to walkable polygons (walkable.json). -Coordinates: walkable.json uses 0-100 space; game uses 0-MAP_WIDTH, 0-MAP_HEIGHT. +Pathfinding constrained to walkable area: one exterior polygon + holes (compiled_map.json), +or fallback to walkable.json polygons. Supports dynamic obstacles (building footprints). +Coordinates: sources use 0-100; game uses 0-MAP_WIDTH, 0-MAP_HEIGHT. """ from __future__ import annotations import heapq import json +import math from pathlib import Path from typing import Optional from .map import MAP_HEIGHT, MAP_WIDTH -# Walkable data is in 0-100; game is 0-40 -WALKABLE_SCALE = 0.01 # 100 -> 1.0, then we scale by MAP_WIDTH/MAP_HEIGHT -# So game_x = raw_x * (MAP_WIDTH / 100) = raw_x * 0.4 SCALE_X = MAP_WIDTH / 100.0 SCALE_Y = MAP_HEIGHT / 100.0 _GRID_STEP = 0.5 _GRID_W = int(MAP_WIDTH / _GRID_STEP) + 1 _GRID_H = int(MAP_HEIGHT / _GRID_STEP) + 1 + +# Compiled map: (exterior_scaled, holes_scaled) in game coords, or None +_compiled_cache: Optional[tuple[list[tuple[float, float]], list[list[tuple[float, float]]]]] = None +# Legacy: list of polygons in game coords (when no compiled map) _polygons_cache: Optional[list[list[tuple[float, float]]]] = None +# Nav graph: (points_list, adjacency_dict). points_list[i] = (x, y) in game coords; adjacency[i] = [(j, cost), ...] +_nav_graph_cache: Optional[tuple[list[tuple[float, float]], dict[int, list[tuple[int, float]]]]] = None +# step=2.0 → cardinal dist=2.0, diagonal dist≈2.83; use 2.9 to include diagonals +_NAV_CONNECT_RADIUS = 2.9 +# Clearance margin around buildings when filtering nav points (should match UNIT_RADIUS in engine) +_NAV_BUILDING_MARGIN = 0.6 + + +def _load_compiled() -> Optional[tuple[list[tuple[float, float]], list[list[tuple[float, float]]]]]: + global _compiled_cache + if _compiled_cache is not None: + return _compiled_cache + path = Path(__file__).resolve().parent.parent / "static" / "compiled_map.json" + if not path.exists(): + return None + try: + with open(path, encoding="utf-8") as f: + data = json.load(f) + except (OSError, json.JSONDecodeError): + return None + exterior_raw = data.get("exterior", []) + holes_raw = data.get("holes", []) + if len(exterior_raw) < 3: + return None + exterior = [(float(p[0]) * SCALE_X, float(p[1]) * SCALE_Y) for p in exterior_raw] + holes = [ + [(float(p[0]) * SCALE_X, float(p[1]) * SCALE_Y) for p in h] + for h in holes_raw if len(h) >= 3 + ] + _compiled_cache = (exterior, holes) + return _compiled_cache + + +def _load_nav_graph() -> Optional[tuple[list[tuple[float, float]], dict[int, list[tuple[int, float]]]]]: + """Load nav_points from compiled_map.json (game coords) and build adjacency graph. Returns (points, adj) or None.""" + global _nav_graph_cache + if _nav_graph_cache is not None: + return _nav_graph_cache + path = Path(__file__).resolve().parent.parent / "static" / "compiled_map.json" + if not path.exists(): + return None + try: + with open(path, encoding="utf-8") as f: + data = json.load(f) + except (OSError, json.JSONDecodeError): + return None + raw = data.get("nav_points", []) + if len(raw) < 2: + return None + points: list[tuple[float, float]] = [(float(p[0]), float(p[1])) for p in raw] + adj: dict[int, list[tuple[int, float]]] = {i: [] for i in range(len(points))} + for i in range(len(points)): + xi, yi = points[i] + for j in range(i + 1, len(points)): + xj, yj = points[j] + d = ((xj - xi) ** 2 + (yj - yi) ** 2) ** 0.5 + if d <= _NAV_CONNECT_RADIUS and d > 0: + adj[i].append((j, d)) + adj[j].append((i, d)) + _nav_graph_cache = (points, adj) + return _nav_graph_cache + + +def _nav_point_blocked( + x: float, + y: float, + blocked_rects: list[tuple[float, float, float, float]], +) -> bool: + """True if nav point (x, y) is inside or too close to any building rect.""" + for rx, ry, w, h in blocked_rects: + cx = max(rx, min(rx + w, x)) + cy = max(ry, min(ry + h, y)) + if math.hypot(x - cx, y - cy) < _NAV_BUILDING_MARGIN: + return True + return False + + +def _nav_valid_set( + points: list[tuple[float, float]], + blocked_rects: Optional[list[tuple[float, float, float, float]]], +) -> Optional[set[int]]: + """Return set of nav point indices not blocked by buildings, or None if no filtering needed.""" + if not blocked_rects: + return None # all valid + return { + i for i, (px, py) in enumerate(points) + if not _nav_point_blocked(px, py, blocked_rects) + } + + +def _nearest_nav_index( + x: float, + y: float, + points: list[tuple[float, float]], + valid: Optional[set[int]] = None, +) -> int: + """Return index of nav point closest to (x, y), restricted to valid set if given.""" + best_i = -1 + best_d = float("inf") + for i in range(len(points)): + if valid is not None and i not in valid: + continue + px, py = points[i] + d = (px - x) ** 2 + (py - y) ** 2 + if d < best_d: + best_d = d + best_i = i + if best_i == -1: + # fallback: ignore valid constraint + best_i = 0 + best_d = (points[0][0] - x) ** 2 + (points[0][1] - y) ** 2 + for i in range(1, len(points)): + px, py = points[i] + d = (px - x) ** 2 + (py - y) ** 2 + if d < best_d: + best_d = d + best_i = i + return best_i + def _load_polygons() -> list[list[tuple[float, float]]]: global _polygons_cache @@ -63,8 +185,12 @@ def _point_in_polygon(x: float, y: float, polygon: list[tuple[float, float]]) -> return inside -def is_walkable(x: float, y: float) -> bool: - """True if (x, y) is inside any walkable polygon. Uses game coordinates 0..MAP_WIDTH, 0..MAP_HEIGHT.""" +def _point_in_rect(x: float, y: float, rx: float, ry: float, w: float, h: float) -> bool: + return rx <= x < rx + w and ry <= y < ry + h + + +def _static_walkable(x: float, y: float) -> bool: + """True if (x,y) is in any walkable polygon (all polygons from walkable.json).""" if x < 0 or x > MAP_WIDTH or y < 0 or y > MAP_HEIGHT: return False polygons = _load_polygons() @@ -76,6 +202,25 @@ def is_walkable(x: float, y: float) -> bool: return False +def is_walkable( + x: float, + y: float, + *, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> bool: + """ + True if (x, y) is walkable (inside exterior, not in holes, not in any blocked rect). + blocked_rects: optional list of (x, y, width, height) in game coordinates (e.g. building footprints). + """ + if not _static_walkable(x, y): + return False + if blocked_rects: + for rx, ry, w, h in blocked_rects: + if _point_in_rect(x, y, rx, ry, w, h): + return False + return True + + def _cell_center(ci: int, cj: int) -> tuple[float, float]: return (ci * _GRID_STEP, cj * _GRID_STEP) @@ -88,35 +233,126 @@ def _to_cell(x: float, y: float) -> tuple[int, int]: return (ci, cj) -def _cell_walkable(ci: int, cj: int) -> bool: +def _cell_walkable( + ci: int, + cj: int, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> bool: cx, cy = _cell_center(ci, cj) - return is_walkable(cx, cy) + return is_walkable(cx, cy, blocked_rects=blocked_rects) -def _neighbors(ci: int, cj: int) -> list[tuple[int, int, float]]: +def _neighbors( + ci: int, + cj: int, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> list[tuple[int, int, float]]: out: list[tuple[int, int, float]] = [] for di in (-1, 0, 1): for dj in (-1, 0, 1): if di == 0 and dj == 0: continue ni, nj = ci + di, cj + dj - if 0 <= ni < _GRID_W and 0 <= nj < _GRID_H and _cell_walkable(ni, nj): + if 0 <= ni < _GRID_W and 0 <= nj < _GRID_H and _cell_walkable(ni, nj, blocked_rects): cost = 1.414 if di != 0 and dj != 0 else 1.0 out.append((ni, nj, cost)) return out +def _find_path_navgraph( + sx: float, sy: float, tx: float, ty: float, + points: list[tuple[float, float]], + adj: dict[int, list[tuple[int, float]]], + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> Optional[list[tuple[float, float]]]: + """A* on nav graph, skipping points inside buildings. Returns waypoints or None.""" + if not points or not adj: + return None + + valid = _nav_valid_set(points, blocked_rects) + start_i = _nearest_nav_index(sx, sy, points, valid) + end_i = _nearest_nav_index(tx, ty, points, valid) + if start_i == end_i: + return [(tx, ty)] + + def heuristic(i: int) -> float: + px, py = points[i] + return ((tx - px) ** 2 + (ty - py) ** 2) ** 0.5 + + counter = 0 + best_g: dict[int, float] = {start_i: 0.0} + came_from: dict[int, Optional[int]] = {start_i: None} + open_set: list[tuple[float, int, int]] = [] + heapq.heappush(open_set, (heuristic(start_i), counter, start_i)) + counter += 1 + + while open_set: + f, _, node_i = heapq.heappop(open_set) + g = best_g.get(node_i, float("inf")) + if f > g + heuristic(node_i) + 1e-9: + continue # stale entry + if node_i == end_i: + # Reconstruct + path_indices: list[int] = [] + cur: Optional[int] = node_i + while cur is not None: + path_indices.append(cur) + cur = came_from[cur] + path_indices.reverse() + result = [points[i] for i in path_indices] + if result: + result[-1] = (tx, ty) + else: + result = [(tx, ty)] + return result + for j, cost in adj.get(node_i, []): + if valid is not None and j not in valid: + continue + new_g = g + cost + if new_g < best_g.get(j, float("inf")): + best_g[j] = new_g + came_from[j] = node_i + heapq.heappush(open_set, (new_g + heuristic(j), counter, j)) + counter += 1 + return None + + def find_path( - sx: float, sy: float, tx: float, ty: float + sx: float, + sy: float, + tx: float, + ty: float, + *, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, ) -> Optional[list[tuple[float, float]]]: """ A* path from (sx,sy) to (tx,ty) in game coordinates. + Uses nav-point graph when available and no dynamic obstacles; else grid A*. + blocked_rects: optional list of (x, y, width, height) to avoid (e.g. buildings). Returns list of waypoints (including end), or None if no path. - Returns [] if start/end not walkable or start==end. """ + nav = _load_nav_graph() + if nav is not None: + points, adj = nav + path = _find_path_navgraph(sx, sy, tx, ty, points, adj, blocked_rects=blocked_rects) + if path is not None: + return path + + return _find_path_grid(sx, sy, tx, ty, blocked_rects=blocked_rects) + + +def _find_path_grid( + sx: float, + sy: float, + tx: float, + ty: float, + *, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> Optional[list[tuple[float, float]]]: + """Grid A* path (used when nav graph unavailable or blocked_rects present).""" si, sj = _to_cell(sx, sy) ti, tj = _to_cell(tx, ty) - if not _cell_walkable(si, sj) or not _cell_walkable(ti, tj): + if not _cell_walkable(si, sj, blocked_rects) or not _cell_walkable(ti, tj, blocked_rects): return None if si == ti and sj == tj: return [(tx, ty)] @@ -124,59 +360,85 @@ def find_path( def heuristic(i: int, j: int) -> float: return ((ti - i) ** 2 + (tj - j) ** 2) ** 0.5 - # (f, counter, (ci, cj), path) counter = 0 - open_set: list[tuple[float, int, int, int, list[tuple[int, int]]]] = [] - heapq.heappush( - open_set, - (heuristic(si, sj), counter, si, sj, [(si, sj)]) - ) + # Heap entries: (f, counter, ci, cj, g, parent_cell_or_None) + open_set: list[tuple[float, int, int, int, float, Optional[tuple[int, int]]]] = [] + heapq.heappush(open_set, (heuristic(si, sj), counter, si, sj, 0.0, None)) counter += 1 - closed: set[tuple[int, int]] = set() + # best g seen per cell; also acts as closed set when set to a finite value + best_g: dict[tuple[int, int], float] = {(si, sj): 0.0} + came_from: dict[tuple[int, int], Optional[tuple[int, int]]] = {(si, sj): None} while open_set: - _, _, ci, cj, path = heapq.heappop(open_set) - if (ci, cj) in closed: - continue - closed.add((ci, cj)) + f, _, ci, cj, g, _ = heapq.heappop(open_set) + if g > best_g.get((ci, cj), float("inf")): + continue # stale entry if ci == ti and cj == tj: - # path = [start_cell, ..., end_cell]; waypoints from first step to end - waypoints = [_cell_center(i, j) for i, j in path[1:]] + # Reconstruct path + cells: list[tuple[int, int]] = [] + cur: Optional[tuple[int, int]] = (ci, cj) + while cur is not None: + cells.append(cur) + cur = came_from[cur] + cells.reverse() + waypoints = [_cell_center(i, j) for i, j in cells[1:]] if waypoints: waypoints[-1] = (tx, ty) else: waypoints = [(tx, ty)] return waypoints - for ni, nj, cost in _neighbors(ci, cj): - if (ni, nj) in closed: - continue - new_path = path + [(ni, nj)] - g = sum( - (new_path[k+1][0] - new_path[k][0]) ** 2 + (new_path[k+1][1] - new_path[k][1]) ** 2 - for k in range(len(new_path) - 1) - ) ** 0.5 - f = g + heuristic(ni, nj) - heapq.heappush(open_set, (f, counter, ni, nj, new_path)) - counter += 1 + for ni, nj, cost in _neighbors(ci, cj, blocked_rects): + new_g = g + cost + if new_g < best_g.get((ni, nj), float("inf")): + best_g[(ni, nj)] = new_g + came_from[(ni, nj)] = (ci, cj) + f_new = new_g + heuristic(ni, nj) + heapq.heappush(open_set, (f_new, counter, ni, nj, new_g, (ci, cj))) + counter += 1 return None -def snap_to_walkable(x: float, y: float) -> tuple[float, float]: - """Return nearest walkable point to (x,y). If (x,y) is walkable, return it.""" - if is_walkable(x, y): +def nearest_walkable_navpoint( + x: float, + y: float, + *, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> tuple[float, float]: + """Return the nearest nav point that is walkable (not inside any building). + Falls back to snap_to_walkable if no nav graph available.""" + nav = _load_nav_graph() + if nav is not None: + points, _ = nav + valid = _nav_valid_set(points, blocked_rects) + best_i = _nearest_nav_index(x, y, points, valid) + if best_i >= 0: + return points[best_i] + return snap_to_walkable(x, y, blocked_rects=blocked_rects) + + +def snap_to_walkable( + x: float, + y: float, + *, + blocked_rects: Optional[list[tuple[float, float, float, float]]] = None, +) -> tuple[float, float]: + """Return nearest walkable point to (x,y).""" + if is_walkable(x, y, blocked_rects=blocked_rects): return (x, y) best = (x, y) best_d = 1e9 - for radius in [0.5, 1.0, 1.5, 2.0]: - for dx in [-radius, 0, radius]: - for dy in [-radius, 0, radius]: - if dx == 0 and dy == 0 and radius != 0: + for radius in [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0]: + for dx in [-radius, -radius * 0.5, 0, radius * 0.5, radius]: + for dy in [-radius, -radius * 0.5, 0, radius * 0.5, radius]: + if dx == 0 and dy == 0: continue nx = max(0, min(MAP_WIDTH, x + dx)) ny = max(0, min(MAP_HEIGHT, y + dy)) - if is_walkable(nx, ny): + if is_walkable(nx, ny, blocked_rects=blocked_rects): d = (nx - x) ** 2 + (ny - y) ** 2 if d < best_d: best_d = d best = (nx, ny) + if best_d < 1e9: + break return best diff --git a/backend/game/state.py b/backend/game/state.py index 91956fed4337205ce55c05643be63889f81f2e24..17e82ee95829f57f86052373188fa7921db8596c 100644 --- a/backend/game/state.py +++ b/backend/game/state.py @@ -6,7 +6,8 @@ from typing import Optional from pydantic import BaseModel, Field from .buildings import Building, BuildingStatus, BuildingType, BUILDING_DEFS -from .map import GameMap, get_start_positions +from .map import GameMap, get_start_data, get_all_map_resources +from .pathfinding import snap_to_walkable from .units import Unit, UnitType, UNIT_DEFS, UnitStatus @@ -68,7 +69,7 @@ class PlayerState(BaseModel): None, ) - def summary(self) -> str: + def summary(self, lang: str = "fr") -> str: active = [ b.building_type.value for b in self.buildings.values() if b.status not in (BuildingStatus.CONSTRUCTING, BuildingStatus.DESTROYED) @@ -82,14 +83,21 @@ class PlayerState(BaseModel): for u in self.units.values(): counts[u.unit_type.value] = counts.get(u.unit_type.value, 0) + 1 + if (lang or "fr").lower() == "en": + minerals_l, gas_l, supply_l = "Minerals", "Gas", "Supply" + buildings_l, constructing_l, units_l, none_l = "Active buildings", "Under construction", "Units", "none" + else: + minerals_l, gas_l, supply_l = "Minéraux", "Gaz", "Supply" + buildings_l, constructing_l, units_l, none_l = "Bâtiments actifs", "En construction", "Unités", "aucun" + lines = [ - f"Minéraux: {self.minerals}, Gaz: {self.gas}, Supply: {self.supply_used}/{self.supply_max}", - f"Bâtiments actifs: {', '.join(active) or 'aucun'}", + f"{minerals_l}: {self.minerals}, {gas_l}: {self.gas}, {supply_l}: {self.supply_used}/{self.supply_max}", + f"{buildings_l}: {', '.join(active) or none_l}", ] if constructing: - lines.append(f"En construction: {', '.join(constructing)}") + lines.append(f"{constructing_l}: {', '.join(constructing)}") if counts: - lines.append(f"Unités: {', '.join(f'{v} {k}' for k, v in counts.items())}") + lines.append(f"{units_l}: {', '.join(f'{v} {k}' for k, v in counts.items())}") return "\n".join(lines) @@ -115,29 +123,38 @@ class GameState(BaseModel): p1 = PlayerState(player_id=player1_id, player_name=player1_name) p2 = PlayerState(player_id=player2_id, player_name=player2_name) - start1, start2 = get_start_positions() + start1, _, start2, _ = get_start_data() + state.game_map = GameMap.create_default(resources=get_all_map_resources()) - # Starting Command Centers (already built) - cc1 = Building.create(BuildingType.COMMAND_CENTER, player1_id, *start1) + # Starting Command Centers (already built); x,y is center of building footprint + cc1 = Building.create(BuildingType.COMMAND_CENTER, player1_id, float(start1[0]), float(start1[1])) cc1.status = BuildingStatus.ACTIVE cc1.construction_ticks_remaining = 0 + cc1.hp = float(cc1.max_hp) p1.buildings[cc1.id] = cc1 - cc2 = Building.create(BuildingType.COMMAND_CENTER, player2_id, *start2) + cc2 = Building.create(BuildingType.COMMAND_CENTER, player2_id, float(start2[0]), float(start2[1])) cc2.status = BuildingStatus.ACTIVE cc2.construction_ticks_remaining = 0 + cc2.hp = float(cc2.max_hp) p2.buildings[cc2.id] = cc2 - # 5 starting SCVs per player, positioned south of CC - for i in range(5): - scv1 = Unit.create(UnitType.SCV, player1_id, - start1[0] + 1 + i * 0.8, - start1[1] + 4) + # 5 starting SCVs per player, spread in x around CC; snap each to nearest walkable point + _MAP_W = 80.0 + _MAP_H = 80.0 + # Spacing must be > 2*UNIT_RADIUS (1.0) to avoid collision-blocking at spawn + _scv_offsets = [(-2.4, 3), (-1.2, 3), (0.0, 3), (1.2, 3), (2.4, 3)] + for i, (ox, oy) in enumerate(_scv_offsets): + raw1x = max(0.5, min(_MAP_W - 0.5, start1[0] + ox)) + raw1y = max(0.5, min(_MAP_H - 0.5, start1[1] + oy)) + sx1, sy1 = snap_to_walkable(raw1x, raw1y) + scv1 = Unit.create(UnitType.SCV, player1_id, sx1, sy1) p1.units[scv1.id] = scv1 - scv2 = Unit.create(UnitType.SCV, player2_id, - start2[0] + 1 + i * 0.8, - start2[1] - 1) + raw2x = max(0.5, min(_MAP_W - 0.5, start2[0] + ox)) + raw2y = max(0.5, min(_MAP_H - 0.5, start2[1] - oy)) + sx2, sy2 = snap_to_walkable(raw2x, raw2y) + scv2 = Unit.create(UnitType.SCV, player2_id, sx2, sy2) p2.units[scv2.id] = scv2 p1.recalculate_supply() diff --git a/backend/game/tech_tree.py b/backend/game/tech_tree.py index 1327072d80de294ea14322358b8f465331c3212e..3c747bdb69500b51b66728260b54c3e32cde4a5d 100644 --- a/backend/game/tech_tree.py +++ b/backend/game/tech_tree.py @@ -24,7 +24,7 @@ BUILDING_REQUIREMENTS: dict[BuildingType, list[BuildingType]] = { BuildingType.COMMAND_CENTER: [], BuildingType.SUPPLY_DEPOT: [], BuildingType.REFINERY: [], - BuildingType.BARRACKS: [], + BuildingType.BARRACKS: [BuildingType.SUPPLY_DEPOT], BuildingType.ENGINEERING_BAY: [BuildingType.BARRACKS], BuildingType.FACTORY: [BuildingType.BARRACKS], BuildingType.ARMORY: [BuildingType.FACTORY], diff --git a/backend/game/units.py b/backend/game/units.py index 73262bac13b48a79f293ee70326efa8c6119f285..25268ffa628ab21317da26a14f49443a59a7f314 100644 --- a/backend/game/units.py +++ b/backend/game/units.py @@ -22,6 +22,7 @@ class UnitStatus(str, Enum): ATTACKING = "attacking" MINING_MINERALS = "mining_minerals" MINING_GAS = "mining_gas" + MOVING_TO_BUILD = "moving_to_build" BUILDING = "building" HEALING = "healing" SIEGED = "sieged" @@ -74,17 +75,15 @@ UNIT_DEFS: dict[UnitType, UnitDef] = { supply_cost=2, build_time_ticks=40, attack_cooldown_ticks=4, ), UnitType.TANK: UnitDef( - max_hp=150, armor=1, ground_damage=15, air_damage=0, + max_hp=150, armor=1, ground_damage=35, air_damage=0, attack_range=7, move_speed=0.75, mineral_cost=150, gas_cost=100, - supply_cost=2, build_time_ticks=50, can_siege=True, + supply_cost=3, build_time_ticks=50, attack_cooldown_ticks=5, - siege_damage=35, siege_range=12.0, siege_splash_radius=2.0, - siege_cooldown_ticks=8, ), UnitType.WRAITH: UnitDef( max_hp=120, armor=0, ground_damage=8, air_damage=20, attack_range=5, move_speed=2.5, mineral_cost=150, gas_cost=100, - supply_cost=2, build_time_ticks=60, is_flying=True, can_cloak=True, + supply_cost=2, build_time_ticks=60, is_flying=True, attack_cooldown_ticks=4, ), } @@ -118,6 +117,7 @@ class Unit(BaseModel): building_target_id: Optional[str] = None harvest_carry: bool = False # True while carrying resources back to CC harvest_amount: int = 0 # amount being carried (deposited on CC arrival) + stuck_ticks: int = 0 # consecutive ticks the unit couldn't move @classmethod def create(cls, unit_type: UnitType, owner: str, x: float, y: float) -> "Unit": diff --git a/backend/lobby/manager.py b/backend/lobby/manager.py index d8214c4417ecac6f8403d764975596311da520eb..3756e20b4bf331c34b308761d8a69e6af8ee367f 100644 --- a/backend/lobby/manager.py +++ b/backend/lobby/manager.py @@ -199,6 +199,17 @@ class LobbyManager: def get_room(self, room_id: str) -> Optional[Room]: return self._rooms.get(room_id) + def get_playing_count(self) -> int: + """Return number of rooms currently in 'playing' status.""" + return sum(1 for r in self._rooms.values() if r.status == "playing") + + def get_a_playing_room_id(self) -> Optional[str]: + """Return one room_id in 'playing' status for observe, or None.""" + for room_id, room in self._rooms.items(): + if room.status == "playing": + return room_id + return None + def get_room_for_sid(self, sid: str) -> Optional[Room]: return self._get_room(sid) diff --git a/backend/lobby/safe_name.py b/backend/lobby/safe_name.py new file mode 100644 index 0000000000000000000000000000000000000000..fa43110c141626912e1308461ba4c342e1bd9958 --- /dev/null +++ b/backend/lobby/safe_name.py @@ -0,0 +1,75 @@ +""" +Validation du pseudo joueur via Mistral : détection d'insultes / noms inappropriés. +Si le nom est jugé insultant, il est remplacé par un nom aléatoire inoffensif. +""" + +from __future__ import annotations + +import json +import logging +import random + +from config import MISTRAL_API_KEY, MISTRAL_CHAT_MODEL + +log = logging.getLogger(__name__) + +_FALLBACK_NAMES = [ + "Player", + "Guest", + "Strategist", + "Commander", + "Pilot", + "Scout", + "Rookie", +] + +_PROMPT = """Tu dois juger si un pseudo de joueur pour un jeu en ligne est insultant, vulgaire, discriminatoire ou inapproprié pour un public familial. + +Réponds UNIQUEMENT avec un JSON valide, sans texte avant ni après, avec exactement ces deux champs : +- "is_insulting": true si le pseudo est inapproprié (insulte, gros mot, contenu choquant), false sinon. +- "replacement": si is_insulting est true, un pseudo de remplacement court, sympa et inoffensif (ex: "Chevalier", "Pilote"); sinon la chaîne vide "". + +Pseudo à évaluer : "{name}" +""" + + +async def sanitize_player_name(name: str) -> str: + """ + Vérifie le nom via Mistral. Si insultant, retourne un remplacement ; sinon retourne le nom tel quel. + En cas d'absence de clé API ou d'erreur, retourne le nom original. + """ + name = (name or "").strip() or "Player" + if not MISTRAL_API_KEY: + return name + + try: + from mistralai import Mistral + + client = Mistral(api_key=MISTRAL_API_KEY) + response = await client.chat.complete_async( + model=MISTRAL_CHAT_MODEL, + messages=[{"role": "user", "content": _PROMPT.format(name=name)}], + response_format={"type": "json_object"}, + ) + content = response.choices[0].message.content + if isinstance(content, str): + raw = content.strip() + elif content: + first = content[0] + raw = (getattr(first, "text", None) or str(first)).strip() + else: + raw = "" + # Extraire un éventuel bloc JSON + if "```" in raw: + raw = raw.split("```")[1] + if raw.startswith("json"): + raw = raw[4:] + data = json.loads(raw) + if data.get("is_insulting") and data.get("replacement"): + replacement = str(data["replacement"]).strip() or random.choice(_FALLBACK_NAMES) + log.info("Safe name: %r replaced by %r", name, replacement) + return replacement[:50] + return name[:50] + except Exception as e: + log.warning("Safe name check failed for %r: %s", name, e) + return name[:50] diff --git a/backend/main.py b/backend/main.py index 1e128f5199638bf79e4c95d1be4ab1594bf1095e..03a9a15e7644423a564e590b145cc1b82bb44872 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,6 +11,8 @@ Client → Server join_room { room_id, name } quick_match { name } player_ready {} + get_playing_count {} — server responds with playing_count + observe {} — server responds with observe_room or error voice_input { audio_b64, mime_type? } text_input { text } disconnect (automatic) @@ -25,6 +27,8 @@ Server → Client voice_result { transcription, feedback_text, feedback_audio_b64, results } game_over { winner_id, winner_name } error { message } + playing_count { count } — in response to get_playing_count + observe_room { room_id } — in response to observe (spectator) """ from __future__ import annotations @@ -33,13 +37,14 @@ import asyncio import base64 import logging import os +import re import subprocess import sys from pathlib import Path -from typing import Optional +from typing import Any, Optional import socketio -from fastapi import Body, FastAPI, HTTPException +from fastapi import APIRouter, Body, FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse @@ -48,6 +53,7 @@ from game.bot import BOT_PLAYER_ID, BotPlayer from game.engine import GameEngine from game.state import GameState from lobby.manager import LobbyManager +from lobby.safe_name import sanitize_player_name from voice import command_parser, stt BOT_OFFER_DELAY = 10 # seconds before offering bot opponent @@ -55,6 +61,35 @@ BOT_OFFER_DELAY = 10 # seconds before offering bot opponent logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) + +def _fill_template(template: str, data: dict[str, Any]) -> str: + """Fill placeholders {key} in template with data; missing keys become empty string.""" + placeholders = re.findall(r"\{(\w+)\}", template) + safe = {k: str(data.get(k, "")) for k in placeholders} + try: + return template.format(**safe) + except KeyError: + return template + + +def _compute_feedback_level(cmd_result) -> str: + """Derive ok/warning/error from command results.""" + if cmd_result.feedback_override: + return "error" + results = cmd_result.results + if not results: + return "error" + successes = sum(1 for r in results if r.success) + if successes == 0: + return "error" + if successes < len(results): + return "warning" + # All succeeded — check if any result carries an error key + for r in results: + if r.data and "error" in r.data: + return "warning" + return "ok" + # --------------------------------------------------------------------------- # Socket.IO + FastAPI setup # --------------------------------------------------------------------------- @@ -76,6 +111,32 @@ fastapi_app.add_middleware( allow_headers=["*"], ) +# Paths for static assets (must serve before any catch-all mount) +_STATIC_DIR = Path(__file__).parent / "static" +_SPRITES_DIR = Path(__file__).parent / "static" / "sprites" + +_static_router = APIRouter() + +@_static_router.get("/static/MAP.png", response_class=FileResponse) +def _serve_map_png(): + p = _STATIC_DIR / "MAP.png" + if not p.is_file(): + raise HTTPException(404, "MAP.png not found") + return FileResponse(p, media_type="image/png") + +@_static_router.get("/sprites/{kind}/{filename:path}", response_class=FileResponse) +def _serve_sprite(kind: str, filename: str): + p = _SPRITES_DIR / kind / filename + if not p.is_file(): + raise HTTPException(404, "Sprite not found") + return FileResponse( + p, + media_type="image/png", + headers={"Cache-Control": "no-store"}, + ) + +fastapi_app.include_router(_static_router) + # Unit sound effects (generated by scripts/generate_unit_sounds.py) _SOUNDS_DIR = Path(__file__).parent / "static" / "sounds" _UNITS_SOUNDS_DIR = _SOUNDS_DIR / "units" @@ -84,23 +145,32 @@ _UNITS_SOUNDS_DIR.mkdir(parents=True, exist_ok=True) fastapi_app.mount("/sounds", StaticFiles(directory=str(_SOUNDS_DIR)), name="sounds") # Sprites (unit/building icons generated via Mistral image API) -_SPRITES_DIR = Path(__file__).parent / "static" / "sprites" _SPRITES_DIR.mkdir(parents=True, exist_ok=True) if _SPRITES_DIR.exists(): fastapi_app.mount("/sprites", StaticFiles(directory=str(_SPRITES_DIR)), name="sprites") -# Map image, map.json, walkable polygon -_STATIC_DIR = Path(__file__).parent / "static" +# Map image, map.json, walkable polygon (fallback for other static files) if _STATIC_DIR.exists(): fastapi_app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static") _MAP_JSON = _STATIC_DIR / "map.json" _WALKABLE_JSON = _STATIC_DIR / "walkable.json" +_COMPILED_MAP_JSON = _STATIC_DIR / "compiled_map.json" _GAME_POSITIONS_JSON = _STATIC_DIR / "game_positions.json" # Game grid size (must match game/map.py) -_MAP_GRID_W = 40 -_MAP_GRID_H = 40 +_MAP_GRID_W = 80 +_MAP_GRID_H = 80 + +# Compile walkable → exterior + holes once at server startup +if _WALKABLE_JSON.exists(): + try: + from game.map import MAP_WIDTH as _MAP_W, MAP_HEIGHT as _MAP_H + from game.map_compiler import run_compiler + run_compiler(_WALKABLE_JSON, _COMPILED_MAP_JSON, map_width=_MAP_W, map_height=_MAP_H) + log.info("Map compiled: %s", _COMPILED_MAP_JSON) + except Exception as e: + log.warning("Map compiler skipped: %s", e) def _load_game_positions() -> dict: @@ -238,12 +308,12 @@ def _generate_geysers_around(cx: int, cy: int, count: int = 1, radius: float = 4 @fastapi_app.put("/api/map/positions") async def save_map_positions(body: dict = Body(...)): - """Enregistre positions de départ (exactement 3, 2 seront tirées au sort par partie) et expansions, génère minerais (et geysers) autour de chacune.""" + """Enregistre positions de départ (2 ou 3, si 3 alors 2 seront tirées au sort par partie) et expansions.""" import json starts = body.get("starting_positions") expansions = body.get("expansion_positions") - if not isinstance(starts, list) or len(starts) != 3: - raise HTTPException(400, "starting_positions requis (exactement 3 positions {x, y} en 0-100)") + if not isinstance(starts, list) or len(starts) < 2: + raise HTTPException(400, "starting_positions requis (au moins 2 positions {x, y} en 0-100)") if not isinstance(expansions, list): expansions = [] for i, p in enumerate(starts): @@ -259,27 +329,37 @@ async def save_map_positions(body: dict = Body(...)): if not (0 <= x <= 100 and 0 <= y <= 100): raise HTTPException(400, f"expansion_positions[{i}] hors [0,100]") - # Keep positions in 0-100 for admin display - starting_positions = [{"x": float(p["x"]), "y": float(p["y"])} for p in starts] - expansion_positions = [{"x": float(p["x"]), "y": float(p["y"])} for p in expansions] - - minerals = [] - geysers = [] - for p in starting_positions + expansion_positions: - gx, gy = _admin_to_game_x(p["x"]), _admin_to_game_y(p["y"]) - minerals.extend(_generate_minerals_around(gx, gy, count=7)) - geysers.extend(_generate_geysers_around(gx, gy, count=1)) + # Keep positions in 0-100 for admin display; embed minerals/geysers per start so we load only the 2 chosen bases' resources + starting_positions = [] + total_minerals = 0 + total_geysers = 0 + for p in starts: + x, y = float(p["x"]), float(p["y"]) + gx, gy = _admin_to_game_x(x), _admin_to_game_y(y) + minerals = _generate_minerals_around(gx, gy, count=7) + geysers = _generate_geysers_around(gx, gy, count=1) + total_minerals += len(minerals) + total_geysers += len(geysers) + starting_positions.append({"x": x, "y": y, "minerals": minerals, "geysers": geysers}) + + expansion_positions = [] + for p in expansions: + x, y = float(p["x"]), float(p["y"]) + gx, gy = _admin_to_game_x(x), _admin_to_game_y(y) + minerals = _generate_minerals_around(gx, gy, count=7) + geysers = _generate_geysers_around(gx, gy, count=1) + total_minerals += len(minerals) + total_geysers += len(geysers) + expansion_positions.append({"x": x, "y": y, "minerals": minerals, "geysers": geysers}) payload = { "starting_positions": starting_positions, "expansion_positions": expansion_positions, - "minerals": minerals, - "geysers": geysers, } _STATIC_DIR.mkdir(parents=True, exist_ok=True) with open(_GAME_POSITIONS_JSON, "w", encoding="utf-8") as f: json.dump(payload, f, indent=2) - return {"status": "ok", "minerals_count": len(minerals), "geysers_count": len(geysers)} + return {"status": "ok", "minerals_count": total_minerals, "geysers_count": total_geysers} @fastapi_app.get("/api/sounds/units") @@ -340,7 +420,7 @@ async def generate_sprites(): def run(): return subprocess.run( - [sys.executable, "-m", "scripts.generate_sprites"], + [sys.executable, "-m", "scripts.generate_sprites", "--skip-existing"], cwd=str(Path(__file__).parent), capture_output=True, text=True, @@ -398,6 +478,49 @@ async def generate_one_building_sprite(building_id: str): return {"status": "ok", "id": building_id} +@fastapi_app.get("/api/sprites/resources") +async def list_resource_sprites(): + """Liste les sprites de ressources présents.""" + return {"sprites": _list_sprites(_SPRITES_DIR / "resources")} + + +@fastapi_app.post("/api/sprites/generate/resources/{resource_id}") +async def generate_one_resource_sprite(resource_id: str): + """Régénère une seule icône de ressource (mineral, geyser).""" + resource_id = resource_id.strip().lower() + valid = ["mineral", "geyser"] + if resource_id not in valid: + raise HTTPException(400, f"Resource invalide. Valides: {valid}") + loop = asyncio.get_event_loop() + result = await loop.run_in_executor(None, lambda: _run_sprite_script(["--resource", resource_id])) + if result.returncode != 0: + raise HTTPException(500, result.stderr or result.stdout or "Échec génération") + return {"status": "ok", "id": resource_id} + + +_ICONS_DIR = _SPRITES_DIR / "icons" + + +@fastapi_app.get("/api/icons") +async def list_icons(): + """Liste les icônes UI présentes.""" + return {"icons": _list_sprites(_ICONS_DIR)} + + +@fastapi_app.post("/api/icons/generate/{icon_id}") +async def generate_one_icon(icon_id: str): + """Génère une icône UI symbolique (mineral, gas, supply).""" + icon_id = icon_id.strip().lower() + valid = ["mineral", "gas", "supply"] + if icon_id not in valid: + raise HTTPException(400, f"Icône invalide. Valides: {valid}") + loop = asyncio.get_event_loop() + result = await loop.run_in_executor(None, lambda: _run_sprite_script(["--icon", icon_id])) + if result.returncode != 0: + raise HTTPException(500, result.stderr or result.stdout or "Échec génération") + return {"status": "ok", "id": icon_id} + + # Serve SvelteKit static build if present (production / HF Spaces) _FRONTEND_BUILD = Path(__file__).parent.parent / "frontend" / "build" if _FRONTEND_BUILD.exists(): @@ -435,6 +558,28 @@ async def _emit_error(sid: str, message: str) -> None: await sio.emit("error", {"message": message}, to=sid) +def _build_resource_zones(engine: Any, player_id: str) -> list[str]: + """Return sorted resource zone names (mineral_1…N, geyser_1…M) by proximity to player base.""" + from game.map import ResourceType as _RT + player = engine.state.players.get(player_id) + cc = player.command_center() if player else None + base_x = float(cc.x) + 2 if cc else 40.0 + base_y = float(cc.y) + 2 if cc else 40.0 + resources = engine.state.game_map.resources + + minerals = sorted( + [r for r in resources if r.resource_type == _RT.MINERAL and not r.is_depleted], + key=lambda r: (r.x - base_x) ** 2 + (r.y - base_y) ** 2, + ) + geysers = sorted( + [r for r in resources if r.resource_type == _RT.GEYSER], + key=lambda r: (r.x - base_x) ** 2 + (r.y - base_y) ** 2, + ) + zones = [f"mineral_{i + 1}" for i in range(len(minerals))] + zones += [f"geyser_{i + 1}" for i in range(len(geysers))] + return zones + + async def _start_game(room_id: str) -> None: """Create game state + engine, emit game_start to human players.""" room = lobby.get_room(room_id) @@ -540,9 +685,30 @@ async def disconnect(sid: str) -> None: # --------------------------------------------------------------------------- +@sio.event +async def get_playing_count(sid: str, data: dict) -> None: + count = lobby.get_playing_count() + await sio.emit("playing_count", {"count": count}, to=sid) + + +@sio.event +async def observe(sid: str, data: dict) -> None: + room_id = lobby.get_a_playing_room_id() + if not room_id: + await _emit_error(sid, "Aucune partie en cours à observer.") + return + await sio.enter_room(sid, room_id) + engine = engines.get(room_id) + payload = {"room_id": room_id} + if engine: + payload["game_state"] = engine.state.model_dump(mode="json") + await sio.emit("observe_room", payload, to=sid) + + @sio.event async def create_room(sid: str, data: dict) -> None: name = str(data.get("name", "Player")).strip() or "Player" + name = await sanitize_player_name(name) room = lobby.create_room(sid, name) await sio.enter_room(sid, room.room_id) await sio.emit("room_created", {"room_id": room.room_id, "room": room.to_dict()}, to=sid) @@ -556,6 +722,7 @@ async def create_room(sid: str, data: dict) -> None: async def join_room(sid: str, data: dict) -> None: room_id = str(data.get("room_id", "")).upper().strip() name = str(data.get("name", "Player")).strip() or "Player" + name = await sanitize_player_name(name) room, err = lobby.join_room(sid, room_id, name) if err: @@ -576,6 +743,7 @@ async def join_room(sid: str, data: dict) -> None: @sio.event async def quick_match(sid: str, data: dict) -> None: name = str(data.get("name", "Player")).strip() or "Player" + name = await sanitize_player_name(name) room, is_new = lobby.quick_match(sid, name) if not is_new: @@ -706,14 +874,15 @@ async def voice_input(sid: str, data: dict) -> None: await sio.emit("voice_result", { "transcription": "", "feedback_text": "Je n'ai rien entendu. Appuie et parle!", - "feedback_audio_b64": "", + "feedback_level": "warning", "results": [], }, to=sid) return # 2. Parse command with Mistral + resource_zones = _build_resource_zones(engine, sid) try: - parsed = await command_parser.parse(transcription, player) + parsed = await command_parser.parse(transcription, player, resource_zones=resource_zones) except Exception as exc: log.exception("Command parsing failed") await _emit_error(sid, f"Erreur d'interprétation: {exc}") @@ -722,21 +891,20 @@ async def voice_input(sid: str, data: dict) -> None: # 3. Apply commands to game engine cmd_result = engine.apply_command(sid, parsed) - feedback_text = ( - cmd_result.feedback_override - if cmd_result.feedback_override - else parsed.feedback - ) - - # Replace/append actual engine results so failures are always heard - failures = [r.message for r in cmd_result.results if not r.success and r.message] - if failures: - feedback_text = " ".join(failures) - - # Append query result to feedback if present - for r in cmd_result.results: - if r.action_type == "query" and r.success: - feedback_text += "\n" + r.message + if cmd_result.feedback_override: + feedback_text = await command_parser.generate_feedback( + cmd_result.feedback_override, parsed.language + ) + else: + merged: dict[str, Any] = {} + for r in cmd_result.results: + merged.update(r.data or {}) + if "error" in merged: + feedback_text = await command_parser.generate_feedback( + merged["error"], parsed.language + ) + else: + feedback_text = _fill_template(parsed.feedback_template, merged) sound_events = [] for r in cmd_result.results: @@ -744,6 +912,7 @@ async def voice_input(sid: str, data: dict) -> None: await sio.emit("voice_result", { "transcription": transcription, "feedback_text": feedback_text, + "feedback_level": _compute_feedback_level(cmd_result), "results": [r.model_dump() for r in cmd_result.results], "sound_events": sound_events, }, to=sid) @@ -779,8 +948,9 @@ async def _process_text_command(sid: str, transcription: str) -> None: return # Parse command with Mistral + resource_zones = _build_resource_zones(engine, sid) try: - parsed = await command_parser.parse(transcription, player) + parsed = await command_parser.parse(transcription, player, resource_zones=resource_zones) except Exception as exc: log.exception("Command parsing failed") await _emit_error(sid, f"Erreur d'interprétation: {exc}") @@ -789,19 +959,20 @@ async def _process_text_command(sid: str, transcription: str) -> None: # Apply commands to game engine cmd_result = engine.apply_command(sid, parsed) - feedback_text = ( - cmd_result.feedback_override - if cmd_result.feedback_override - else parsed.feedback - ) - - failures = [r.message for r in cmd_result.results if not r.success and r.message] - if failures: - feedback_text = " ".join(failures) - - for r in cmd_result.results: - if r.action_type == "query" and r.success: - feedback_text += "\n" + r.message + if cmd_result.feedback_override: + feedback_text = await command_parser.generate_feedback( + cmd_result.feedback_override, parsed.language + ) + else: + merged = {} + for r in cmd_result.results: + merged.update(r.data or {}) + if "error" in merged: + feedback_text = await command_parser.generate_feedback( + merged["error"], parsed.language + ) + else: + feedback_text = _fill_template(parsed.feedback_template, merged) sound_events = [] for r in cmd_result.results: @@ -809,6 +980,7 @@ async def _process_text_command(sid: str, transcription: str) -> None: await sio.emit("voice_result", { "transcription": transcription, "feedback_text": feedback_text, + "feedback_level": _compute_feedback_level(cmd_result), "results": [r.model_dump() for r in cmd_result.results], "sound_events": sound_events, }, to=sid) diff --git a/backend/requirements.txt b/backend/requirements.txt index 5274addb06a74f316f15d1095dcdc931023cc2bc..fa0d10b03d905a78a685ede7647b3e875ef0eab6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -5,3 +5,5 @@ python-dotenv>=1.0.0 pydantic>=2.0.0 mistralai>=1.0.0 httpx>=0.27.0 +google-genai>=1.0.0 +Pillow>=10.0.0 diff --git a/backend/scripts/generate_map_lod.py b/backend/scripts/generate_map_lod.py new file mode 100644 index 0000000000000000000000000000000000000000..b0fb6c60e06fde5f18bbd87837b8862777c9f2c1 --- /dev/null +++ b/backend/scripts/generate_map_lod.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +Génère des versions basse résolution de MAP.png pour le chargement progressif. + + MAP_quarter.png → 1/4 de la taille originale (chargement rapide ~1-2 Mo) + MAP_half.png → 1/2 de la taille originale (qualité intermédiaire ~7 Mo) + +Usage : + cd backend && python -m scripts.generate_map_lod + cd backend && python -m scripts.generate_map_lod --input static/MAP.png +""" +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +from PIL import Image + +_backend = Path(__file__).resolve().parent.parent +if str(_backend) not in sys.path: + sys.path.insert(0, str(_backend)) + + +def generate_lods(input_path: Path) -> None: + src = Image.open(input_path).convert("RGB") + w, h = src.size + size_mb = input_path.stat().st_size / 1024 / 1024 + print(f"Source : {w}×{h} ({size_mb:.1f} Mo) → {input_path}") + + lods = [ + ("MAP_half.png", (w // 2, h // 2)), + ("MAP_quarter.png", (w // 4, h // 4)), + ] + + for filename, size in lods: + out_path = input_path.parent / filename + resized = src.resize(size, Image.LANCZOS) + resized.save(out_path, format="PNG", optimize=True) + out_mb = out_path.stat().st_size / 1024 / 1024 + print(f" {filename:25s} {size[0]}×{size[1]} ({out_mb:.1f} Mo) → {out_path}") + + +def main() -> None: + default = _backend / "static" / "MAP.png" + parser = argparse.ArgumentParser(description="Génère MAP_half.png et MAP_quarter.png") + parser.add_argument("--input", type=Path, default=default) + args = parser.parse_args() + if not args.input.is_file(): + raise SystemExit(f"Fichier introuvable : {args.input}") + generate_lods(args.input) + + +if __name__ == "__main__": + main() diff --git a/backend/scripts/generate_sprites.py b/backend/scripts/generate_sprites.py index 77009121b8b62025f76c491a416dcdfeeffee637..6d325513833077b2b8184f6d73eb01198f563a69 100644 --- a/backend/scripts/generate_sprites.py +++ b/backend/scripts/generate_sprites.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 """ -Génère les sprites PNG (vues de dessus, transparent, style SF Starcraft/Warhammer) -via l'API Mistral (agent avec outil image_generation). -Chaque unité et bâtiment a un prompt détaillé pour un rendu homogène. +Génère les sprites PNG (vues de dessus, style SF Starcraft/Warhammer). + +Backend au choix (priorité GCP si configuré) : +- GCP Vertex AI Imagen : définir GCP_PROJECT_ID (et optionnellement GCP_LOCATION) dans .env +- Mistral : définir MISTRAL_API_KEY dans .env Usage : cd backend && python -m scripts.generate_sprites -Nécessite MISTRAL_API_KEY dans .env """ from __future__ import annotations +import os import sys from pathlib import Path @@ -21,12 +23,10 @@ import random import time from typing import Any -from mistralai import Mistral -from mistralai.models import ToolFileChunk - -from config import MISTRAL_API_KEY +from config import GCP_PROJECT_ID, GCP_LOCATION, MISTRAL_API_KEY from game.units import UnitType from game.buildings import BuildingType, BUILDING_DEFS +from game.map import ResourceType logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) @@ -34,6 +34,18 @@ log = logging.getLogger(__name__) SPRITES_DIR = _backend / "static" / "sprites" UNITS_DIR = SPRITES_DIR / "units" BUILDINGS_DIR = SPRITES_DIR / "buildings" +RESOURCES_DIR = SPRITES_DIR / "resources" +ICONS_DIR = SPRITES_DIR / "icons" + +# Taille cible et fond transparent obligatoires +TARGET_SIZE = 128 + +# Chroma vert : fond #00ff00 demandé à l'API, détourage sur cette couleur uniquement +CHROMA_GREEN_RGB = (0, 255, 0) # #00ff00 pour le prompt +CHROMA_GREEN_TOLERANCE = 55 # pour vert vif proche de #00ff00 +# Vert "dominant" (G > R et G > B) pour attraper les verts plus foncés renvoyés par Imagen +CHROMA_GREEN_MIN_G = 60 # G >= ce seuil et G dominant → considéré fond vert +CHROMA_GREEN_PROMPT = " The background must be solid green #00ff00 only, like a green screen. Subject in gray/blue/metallic, no white or other background." # Backoff pour rate limit (429) BACKOFF_BASE_SEC = 30 @@ -49,25 +61,41 @@ CRITICAL - SMALL SIZE (to avoid rate limits): - Generate SMALL, LOW-RESOLUTION images only. For units: exactly 128x128 pixels. For buildings: longest side 128 pixels. Do NOT generate large or high-resolution images (no 512, 1024, etc.). Small output is required. - MANDATORY transparent background: only the subject has visible pixels; everything else fully transparent (PNG with alpha). No ground, no floor, no solid color. -CRITICAL - VIEW ANGLE: -- Strict top-down view (bird's eye, camera directly above). Only the top surface/footprint visible. No side view, no 3/4, no isometric. +CRITICAL - VIEW ANGLE (NO EXCEPTIONS): +- Strict top-down view ONLY (bird's eye, camera at exactly 90 degrees directly above the subject). This applies to EVERY sprite without exception, including soldiers, mechs, vehicles, and buildings. +- You must ONLY see the top surface/footprint of the subject. No side view, no 3/4 view, no isometric, no perspective angle. +- For units with weapons (marine, goliath, tank, etc.): the weapon barrel must point straight forward, visible from above as a shape pointing toward the top of the image. +- Reject any tendency to show a front/side/3D perspective — the camera is directly overhead, period. Other: subject fills frame, minimal margins. Dark metallic, blue/gray palette, industrial sci-fi. No text or labels. One image per request, no commentary.""" # Vue strictement de dessus + fond transparent + remplir le cadre _TOP = "Strict top-down view only (bird's eye, camera directly above, 90°). Only the top surface/footprint visible, no side or perspective. " -_FILL = "MANDATORY transparent background only (no ground, no floor, no solid color). Subject must fill the frame tightly, edge to edge. " +_FILL = "MANDATORY green #00ff00 background only (no ground, no floor, no details). Subject must fill the frame tightly, edge to edge. " # Petite image demandée à l'API pour éviter le rate limit (pas de post-traitement) _SMALL = "Generate a SMALL low-resolution image only: 128x128 pixels for square sprites, or longest side 128 for rectangles. Tiny size. Do NOT output large or high-resolution (no 512, 1024, etc.). " # Prompts par unité : petite 128x128, fond transparent UNIT_PROMPTS: dict[str, str] = { - UnitType.SCV.value: _TOP + _FILL + _SMALL + "Single SCV worker from above: mech footprint, two arms, cockpit. Industrial gray and blue.", - UnitType.MARINE.value: _TOP + _FILL + _SMALL + "Single Terran Marine from above: oval footprint, weapon. Dark blue armor.", - UnitType.MEDIC.value: _TOP + _FILL + _SMALL + "Single Medic from above: compact footprint, cross/medical symbol. Sci-fi armor.", - UnitType.GOLIATH.value: _TOP + _FILL + _SMALL + "Goliath walker from above: two gun pods, central body. Dark metal and blue.", - UnitType.TANK.value: _TOP + _FILL + _SMALL + "Siege Tank from above: elongated hull, round turret, treads. Military gray and blue.", - UnitType.WRAITH.value: _TOP + _FILL + _SMALL + "Wraith starfighter from above: delta wing, cockpit. Dark with blue.", + UnitType.SCV.value: _TOP + _FILL + _SMALL + "Single SCV worker robot seen strictly from directly above at 90 degrees, bird's eye view only. Top surface visible: mechanical body footprint, two mechanical arms extended sideways, cockpit hatch on top. Industrial gray and blue. Camera is directly overhead, no side or perspective angle.", + UnitType.MARINE.value: _TOP + _FILL + _SMALL + "Single Terran Marine soldier seen strictly from directly above at 90 degrees, bird's eye view only. Top of armored helmet visible at center, heavy shoulder pads on both sides, rifle/assault gun barrel pointing straight forward (upward in the image). Dark blue armor. No side view, no isometric, camera directly overhead.", + UnitType.MEDIC.value: _TOP + _FILL + _SMALL + "Single Terran Medic soldier seen strictly from directly above at 90 degrees, bird's eye view only. Top of helmet visible at center, white/light armor with red cross medical symbol on back, medkit or injector pointing forward. Sci-fi armor. Camera directly overhead, no side or perspective.", + UnitType.GOLIATH.value: _TOP + _FILL + _SMALL + "Goliath combat walker mech seen strictly from directly above at 90 degrees, bird's eye view only. Top surface: wide armored torso, two autocannon gun pods mounted on shoulders pointing straight forward (upward in image), legs spread below. Dark metal and blue. Camera directly overhead at 90 degrees, no side view.", + UnitType.TANK.value: _TOP + _FILL + _SMALL + "Siege Tank seen strictly from directly above at 90 degrees, bird's eye view only. Top surface: elongated armored hull, round turret on top with long cannon barrel pointing straight forward (upward in image), tank treads visible on both sides. Military gray and blue. Camera directly overhead.", + UnitType.WRAITH.value: _TOP + _FILL + _SMALL + "Wraith starfighter aircraft seen strictly from directly above at 90 degrees, bird's eye view only. Top surface: delta wing shape, cockpit bubble at center-front, weapon pods on wing edges pointing forward. Dark metallic with blue highlights. Camera directly overhead.", +} + +# Icônes UI : style flat symbolique, fond vert chroma pour détourage +ICON_PROMPTS: dict[str, str] = { + "mineral": _FILL + _SMALL + "Flat symbolic icon of a blue mineral crystal, bold angular shape, solid blue-cyan color, thick black outline, 2D game UI icon style. No background, no text, no shading.", + "gas": _FILL + _SMALL + "Flat symbolic icon of a purple vespene gas canister or flask, bold shape, solid purple-violet color, thick black outline, 2D game UI icon style. No green. No background, no text, no shading.", + "supply": _FILL + _SMALL + "Flat symbolic icon of a chevron/arrow pointing up or a small house shape, bold, solid yellow-orange color, thick black outline, 2D game UI icon style. No background, no text, no shading.", +} + +# Prompts par ressource : icône top-down 64x64 +RESOURCE_PROMPTS: dict[str, str] = { + ResourceType.MINERAL.value: _TOP + _FILL + _SMALL + "Mineral crystal cluster from above: angular blue-teal crystalline shards, glowing. Sci-fi RTS style. No text.", + ResourceType.GEYSER.value: _TOP + _FILL + _SMALL + "Vespene gas geyser from above: purple/violet glowing vent/crater with purple gas fumes rising. Purple and magenta colors only, NO green. Sci-fi RTS style. No text.", } # Prompts par bâtiment : petite image (longest side 128), fond transparent @@ -86,8 +114,110 @@ def _building_prompt(building_type: BuildingType) -> str: return base + " Transparent background only. Dark metal and blue." +def _is_chroma_green(r: int, g: int, b: int) -> bool: + """True si le pixel est fond vert à détourer : proche de #00ff00 ou vert dominant (G > R, G > B).""" + # Vert vif type #00ff00 + r0, g0, b0 = CHROMA_GREEN_RGB + if ( + abs(r - r0) <= CHROMA_GREEN_TOLERANCE + and abs(g - g0) <= CHROMA_GREEN_TOLERANCE + and abs(b - b0) <= CHROMA_GREEN_TOLERANCE + ): + return True + # Vert plus foncé (Imagen renvoie souvent ~50,127,70) : G dominant et G assez élevé + if g >= CHROMA_GREEN_MIN_G and g >= r and g >= b: + # exclure les gris (r≈g≈b) et garder un vrai vert (g nettement > r ou b) + if r > 100 and b > 100 and abs(r - g) < 30 and abs(b - g) < 30: + return False # gris, pas vert + return True + return False + + +def _resize_and_make_transparent(data: bytes, out_path: Path, size: int = TARGET_SIZE) -> None: + """Redimensionne à size px (côté max). Rend transparent : fond vert chroma ET blanc/clair. Sauvegarde en PNG.""" + from PIL import Image + import io + img = Image.open(io.BytesIO(data)).convert("RGBA") + w, h = img.size + if w > size or h > size: + scale = size / max(w, h) + nw, nh = max(1, int(w * scale)), max(1, int(h * scale)) + img = img.resize((nw, nh), Image.Resampling.LANCZOS) + pixels = img.load() + for y in range(img.height): + for x in range(img.width): + r, g, b, a = pixels[x, y] + if _is_chroma_green(r, g, b): + pixels[x, y] = (r, g, b, 0) + out_path.parent.mkdir(parents=True, exist_ok=True) + img.save(out_path, "PNG") + + +# --------------------------------------------------------------------------- +# GCP Vertex AI Imagen (optionnel) +# --------------------------------------------------------------------------- + +def _generate_one_vertex(prompt: str, out_path: Path, progress: str = "") -> bool: + """Génère une image via Vertex AI Imagen et la sauvegarde.""" + from google import genai + from google.genai.types import GenerateImagesConfig + + name = out_path.stem + for attempt in range(BACKOFF_MAX_RETRIES): + try: + if attempt == 0: + log.info("[%s] Appel Vertex Imagen pour %s...", progress or name, name) + else: + log.info("[%s] Retry %s/%s — Imagen pour %s...", progress or name, attempt + 1, BACKOFF_MAX_RETRIES, name) + # GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, GOOGLE_GENAI_USE_VERTEXAI set in main() + client = genai.Client(vertexai=True) + vertex_prompt = prompt + CHROMA_GREEN_PROMPT + response = client.models.generate_images( + model="imagen-3.0-fast-generate-001", + prompt=vertex_prompt, + config=GenerateImagesConfig( + numberOfImages=1, + aspectRatio="1:1", + ), + ) + if not response.generated_images or not response.generated_images[0].image: + log.warning("[%s] Réponse Imagen vide pour %s", progress or name, name) + return False + img = response.generated_images[0].image + raw_bytes: bytes + if hasattr(img, "image_bytes") and img.image_bytes is not None: + raw_bytes = img.image_bytes + elif hasattr(img, "save") and callable(img.save): + import io + buf = io.BytesIO() + img.save(buf) + raw_bytes = buf.getvalue() + else: + log.error("[%s] Format image Imagen inattendu pour %s", progress or name, name) + return False + _resize_and_make_transparent(raw_bytes, out_path) + size_kb = out_path.stat().st_size / 1024 + log.info("[%s] OK %s — sauvegardé 128px transparent (%s Ko) → %s", progress or name, name, round(size_kb, 1), out_path) + return True + except Exception as e: + err_msg = str(e) + if ("429" in err_msg or "rate limit" in err_msg.lower() or "resource exhausted" in err_msg.lower()) and attempt < BACKOFF_MAX_RETRIES - 1: + delay = min(BACKOFF_BASE_SEC * (2 ** attempt), BACKOFF_MAX_SEC) + jitter = random.uniform(0, delay * 0.2) + time.sleep(delay + jitter) + else: + log.error("[%s] Échec Imagen pour %s: %s", progress or name, name, err_msg) + return False + return False + + +# --------------------------------------------------------------------------- +# Mistral (fallback si GCP non configuré) +# --------------------------------------------------------------------------- + def _find_file_id_in_response(response: Any) -> str | None: """Extrait le file_id du premier ToolFileChunk dans response.outputs.""" + from mistralai.models import ToolFileChunk for entry in getattr(response, "outputs", []) or []: content = getattr(entry, "content", None) if content is None: @@ -172,98 +302,186 @@ def _generate_one(client: Mistral, agent_id: str, prompt: str, out_path: Path, p if data is None: return False - out_path.parent.mkdir(parents=True, exist_ok=True) - out_path.write_bytes(data) - size_kb = len(data) / 1024 - log.info("[%s] OK %s — sauvegardé (%s Ko) → %s", progress or name, name, round(size_kb, 1), out_path) + _resize_and_make_transparent(data, out_path) + size_kb = out_path.stat().st_size / 1024 + log.info("[%s] OK %s — sauvegardé 128px transparent (%s Ko) → %s", progress or name, name, round(size_kb, 1), out_path) return True def main() -> None: import argparse - parser = argparse.ArgumentParser(description="Generate unit/building sprites via Mistral") + parser = argparse.ArgumentParser(description="Generate unit/building sprites (Vertex Imagen or Mistral)") parser.add_argument("--unit", type=str, metavar="ID", help="Generate only this unit (e.g. marine, scv)") parser.add_argument("--building", type=str, metavar="ID", help="Generate only this building (e.g. barracks)") + parser.add_argument("--resource", type=str, metavar="ID", help="Generate only this resource (e.g. mineral, geyser)") + parser.add_argument("--icon", type=str, metavar="ID", help="Generate only this UI icon (e.g. mineral, gas, supply)") + parser.add_argument("--skip-existing", action="store_true", help="Skip sprites that already exist (do not regenerate)") args = parser.parse_args() only_unit = args.unit.strip().lower() if args.unit else None + skip_existing = args.skip_existing only_building = args.building.strip().lower().replace(" ", "_") if args.building else None + only_resource = args.resource.strip().lower() if args.resource else None + only_icon = args.icon.strip().lower() if args.icon else None + + use_vertex = bool(GCP_PROJECT_ID) + if use_vertex: + os.environ["GOOGLE_CLOUD_PROJECT"] = GCP_PROJECT_ID + os.environ["GOOGLE_CLOUD_LOCATION"] = GCP_LOCATION + os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "true" + log.info("Backend: Vertex AI Imagen (project=%s, location=%s)", GCP_PROJECT_ID, GCP_LOCATION) + + def do_generate(prompt: str, out: Path, progress: str) -> bool: + return _generate_one_vertex(prompt, out, progress) + else: + if not MISTRAL_API_KEY: + log.error("Aucun backend image configuré. Définir GCP_PROJECT_ID (ex: mtgbinder) ou MISTRAL_API_KEY dans .env") + sys.exit(1) + from mistralai import Mistral + client = Mistral(api_key=MISTRAL_API_KEY) + log.info("Creating Mistral image generation agent...") + try: + agent = client.beta.agents.create( + model="mistral-medium-2505", + name="Sprite Generator", + description="Generates top-down game sprites for units and buildings.", + instructions=AGENT_INSTRUCTIONS, + tools=[{"type": "image_generation"}], + completion_args={"temperature": 0.3, "top_p": 0.95}, + ) + agent_id = agent.id + log.info("Agent id: %s", agent_id) + except Exception as e: + log.exception("Failed to create agent: %s", e) + sys.exit(1) - if not MISTRAL_API_KEY: - log.error("MISTRAL_API_KEY not set. Add it to .env") - sys.exit(1) + def do_generate(prompt: str, out: Path, progress: str) -> bool: + return _generate_one(client, agent_id, prompt, out, progress=progress) - client = Mistral(api_key=MISTRAL_API_KEY) - - log.info("Creating image generation agent...") - try: - agent = client.beta.agents.create( - model="mistral-medium-2505", - name="Sprite Generator", - description="Generates top-down game sprites for units and buildings.", - instructions=AGENT_INSTRUCTIONS, - tools=[{"type": "image_generation"}], - completion_args={"temperature": 0.3, "top_p": 0.95}, - ) - agent_id = agent.id - log.info("Agent id: %s", agent_id) - except Exception as e: - log.exception("Failed to create agent: %s", e) - sys.exit(1) + only_resource_flag = only_resource is not None + only_unit_flag = only_unit is not None + only_building_flag = only_building is not None + only_icon_flag = only_icon is not None UNITS_DIR.mkdir(parents=True, exist_ok=True) BUILDINGS_DIR.mkdir(parents=True, exist_ok=True) + RESOURCES_DIR.mkdir(parents=True, exist_ok=True) + ICONS_DIR.mkdir(parents=True, exist_ok=True) - # Si --unit X : uniquement cette unité (pas de bâtiments). Si --building Y : uniquement ce bâtiment (pas d'unités). units_to_run: list = [] - if not only_building: - units_to_run = [ut for ut in UnitType if not only_unit or ut.value == only_unit] - if only_unit and not units_to_run: + if not only_building_flag and not only_resource_flag and not only_icon_flag: + units_to_run = [ut for ut in UnitType if not only_unit_flag or ut.value == only_unit] + if only_unit_flag and not units_to_run: log.error("Unknown unit: %s. Valid: %s", only_unit, [u.value for u in UnitType]) sys.exit(1) buildings_to_run: list = [] - if not only_unit: - buildings_to_run = [bt for bt in BuildingType if not only_building or bt.value == only_building] - if only_building and not buildings_to_run: + if not only_unit_flag and not only_resource_flag and not only_icon_flag: + buildings_to_run = [bt for bt in BuildingType if not only_building_flag or bt.value == only_building] + if only_building_flag and not buildings_to_run: log.error("Unknown building: %s. Valid: %s", only_building, [b.value for b in BuildingType]) sys.exit(1) - total = len(units_to_run) + len(buildings_to_run) + resources_to_run: list = [] + if not only_unit_flag and not only_building_flag and not only_icon_flag: + valid_resources = list(RESOURCE_PROMPTS.keys()) + resources_to_run = [r for r in valid_resources if not only_resource_flag or r == only_resource] + if only_resource_flag and not resources_to_run: + log.error("Unknown resource: %s. Valid: %s", only_resource, valid_resources) + sys.exit(1) + + icons_to_run: list = [] + if not only_unit_flag and not only_building_flag and not only_resource_flag: + valid_icons = list(ICON_PROMPTS.keys()) + icons_to_run = [ic for ic in valid_icons if not only_icon_flag or ic == only_icon] + if only_icon_flag and not icons_to_run: + log.error("Unknown icon: %s. Valid: %s", only_icon, valid_icons) + sys.exit(1) + + total = len(units_to_run) + len(buildings_to_run) + len(resources_to_run) + len(icons_to_run) log.info("========== GÉNÉRATION SPRITES ==========") log.info("Unités à générer: %s (%s)", [u.value for u in units_to_run], len(units_to_run)) log.info("Bâtiments à générer: %s (%s)", [b.value for b in buildings_to_run], len(buildings_to_run)) + log.info("Ressources à générer: %s (%s)", resources_to_run, len(resources_to_run)) + log.info("Icônes UI à générer: %s (%s)", icons_to_run, len(icons_to_run)) log.info("Total: %s sprites. Délai entre chaque: %s s.", total, DELAY_BETWEEN_SPRITES_SEC) log.info("=========================================") ok, fail = 0, 0 t0 = time.monotonic() + generated_count = 0 for i, ut in enumerate(units_to_run): - if i > 0: - log.info("--- Pause %s s (rate limit) avant prochaine unité ---", DELAY_BETWEEN_SPRITES_SEC) - time.sleep(DELAY_BETWEEN_SPRITES_SEC) out = UNITS_DIR / f"{ut.value}.png" + if skip_existing and out.exists(): + log.info("[unit %s/%s] Déjà présent, ignoré: %s", i + 1, len(units_to_run), out.name) + ok += 1 + continue + if generated_count > 0: + log.info("--- Pause %s s (rate limit) ---", DELAY_BETWEEN_SPRITES_SEC) + time.sleep(DELAY_BETWEEN_SPRITES_SEC) prompt = UNIT_PROMPTS.get(ut.value, _TOP + _FILL + _SMALL + f"{ut.value} unit from above, sci-fi RTS. Transparent background only.") progress = "unit %s/%s" % (i + 1, len(units_to_run)) - if _generate_one(client, agent_id, prompt, out, progress=progress): + if do_generate(prompt, out, progress): ok += 1 else: fail += 1 log.warning("Échec pour unité %s", ut.value) + generated_count += 1 for i, bt in enumerate(buildings_to_run): - if i > 0 or units_to_run: - log.info("--- Pause %s s (rate limit) avant prochain bâtiment ---", DELAY_BETWEEN_SPRITES_SEC) - time.sleep(DELAY_BETWEEN_SPRITES_SEC) out = BUILDINGS_DIR / f"{bt.value}.png" + if skip_existing and out.exists(): + log.info("[building %s/%s] Déjà présent, ignoré: %s", i + 1, len(buildings_to_run), out.name) + ok += 1 + continue + if generated_count > 0: + log.info("--- Pause %s s (rate limit) ---", DELAY_BETWEEN_SPRITES_SEC) + time.sleep(DELAY_BETWEEN_SPRITES_SEC) prompt = _building_prompt(bt) progress = "building %s/%s" % (i + 1, len(buildings_to_run)) - if _generate_one(client, agent_id, prompt, out, progress=progress): + if do_generate(prompt, out, progress): ok += 1 else: fail += 1 log.warning("Échec pour bâtiment %s", bt.value) + generated_count += 1 + + for i, rid in enumerate(resources_to_run): + out = RESOURCES_DIR / f"{rid}.png" + if skip_existing and out.exists(): + log.info("[resource %s/%s] Déjà présent, ignoré: %s", i + 1, len(resources_to_run), out.name) + ok += 1 + continue + if generated_count > 0: + log.info("--- Pause %s s (rate limit) ---", DELAY_BETWEEN_SPRITES_SEC) + time.sleep(DELAY_BETWEEN_SPRITES_SEC) + prompt = RESOURCE_PROMPTS[rid] + progress = "resource %s/%s" % (i + 1, len(resources_to_run)) + if do_generate(prompt, out, progress): + ok += 1 + else: + fail += 1 + log.warning("Échec pour ressource %s", rid) + generated_count += 1 + + for i, ic in enumerate(icons_to_run): + out = ICONS_DIR / f"{ic}.png" + if skip_existing and out.exists(): + log.info("[icon %s/%s] Déjà présent, ignoré: %s", i + 1, len(icons_to_run), out.name) + ok += 1 + continue + if generated_count > 0: + log.info("--- Pause %s s (rate limit) ---", DELAY_BETWEEN_SPRITES_SEC) + time.sleep(DELAY_BETWEEN_SPRITES_SEC) + prompt = ICON_PROMPTS[ic] + progress = "icon %s/%s" % (i + 1, len(icons_to_run)) + if do_generate(prompt, out, progress): + ok += 1 + else: + fail += 1 + log.warning("Échec pour icône %s", ic) + generated_count += 1 elapsed = time.monotonic() - t0 log.info("========== FIN ==========") diff --git a/backend/scripts/upscale_cover.py b/backend/scripts/upscale_cover.py new file mode 100644 index 0000000000000000000000000000000000000000..48eb6b9ed6233f6550342b843fe57062ed480d08 --- /dev/null +++ b/backend/scripts/upscale_cover.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +Upscale l'image cover (lobby) via Vertex AI Imagen 4.0 upscale. + +Prérequis : GCP_PROJECT_ID et GCP_LOCATION dans .env, et `gcloud auth application-default login` +(ou GOOGLE_APPLICATION_CREDENTIALS). + +Usage : + cd backend && python -m scripts.upscale_cover + cd backend && python -m scripts.upscale_cover --factor x2 --input ../frontend/src/cover.jpg --output ../frontend/src/cover.jpg +""" +from __future__ import annotations + +import argparse +import base64 +import subprocess +import sys +from pathlib import Path + +_backend = Path(__file__).resolve().parent.parent +if str(_backend) not in sys.path: + sys.path.insert(0, str(_backend)) + +from config import GCP_PROJECT_ID, GCP_LOCATION + + +def get_access_token() -> str: + """Token via gcloud (ADC).""" + out = subprocess.run( + ["gcloud", "auth", "print-access-token"], + capture_output=True, + text=True, + check=False, + ) + if out.returncode != 0: + raise RuntimeError( + "Échec gcloud auth. Lancez: gcloud auth application-default login" + ) + return out.stdout.strip() + + +def upscale_image( + image_path: Path, + out_path: Path, + factor: str = "x4", + region: str | None = None, + project_id: str | None = None, +) -> None: + region = region or GCP_LOCATION + project_id = project_id or GCP_PROJECT_ID + if not project_id: + raise SystemExit("Définir GCP_PROJECT_ID dans .env") + + data = image_path.read_bytes() + if len(data) > 10 * 1024 * 1024: + raise SystemExit("Image trop lourde (max 10 Mo)") + + b64 = base64.standard_b64encode(data).decode("ascii") + token = get_access_token() + url = ( + f"https://{region}-aiplatform.googleapis.com/v1/projects/{project_id}" + f"/locations/{region}/publishers/google/models/imagen-4.0-upscale-preview:predict" + ) + import httpx + + payload = { + "instances": [ + {"prompt": "Upscale the image", "image": {"bytesBase64Encoded": b64}} + ], + "parameters": { + "mode": "upscale", + "upscaleConfig": {"upscaleFactor": factor}, + "outputOptions": {"mimeType": "image/png"}, + }, + } + resp = httpx.post( + url, + json=payload, + headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + }, + timeout=120.0, + ) + resp.raise_for_status() + body = resp.json() + preds = body.get("predictions") or [] + if not preds or "bytesBase64Encoded" not in preds[0]: + raise SystemExit("Réponse Vertex sans image: " + str(body)[:500]) + out_b64 = preds[0]["bytesBase64Encoded"] + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_bytes(base64.standard_b64decode(out_b64)) + print(f"Upscale {factor} OK → {out_path} ({out_path.stat().st_size / 1024:.1f} Ko)") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Upscale cover image via Vertex AI Imagen") + parser.add_argument( + "--input", + type=Path, + default=_backend.parent / "frontend" / "src" / "cover.jpg", + help="Image source", + ) + parser.add_argument( + "--output", + type=Path, + default=None, + help="Image de sortie (défaut: --input)", + ) + parser.add_argument( + "--factor", + choices=("x2", "x3", "x4"), + default="x4", + help="Facteur d'upscale (défaut: x4)", + ) + args = parser.parse_args() + out = args.output or args.input + if not args.input.is_file(): + raise SystemExit(f"Fichier introuvable: {args.input}") + upscale_image(args.input, out, factor=args.factor) + + +if __name__ == "__main__": + main() diff --git a/backend/scripts/upscale_map.py b/backend/scripts/upscale_map.py new file mode 100644 index 0000000000000000000000000000000000000000..30a88e432394528db4c281f749ec36220cc360cc --- /dev/null +++ b/backend/scripts/upscale_map.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +""" +Upscale MAP.png en 4 passes (4 quadrants) via Vertex AI Imagen upscale. + +La map étant trop lourde pour l'API en un seul appel (> 10 Mo), on : + 1. Découpe en 4 quadrants de 2048×2048 + 2. Upscale chaque quadrant ×2 → 4096×4096 chacun + 3. Recolle en une image finale 8192×8192 + +Usage : + cd backend && python -m scripts.upscale_map + cd backend && python -m scripts.upscale_map --factor x2 --input static/MAP.png --output static/MAP.png +""" +from __future__ import annotations + +import argparse +import io +import sys +from pathlib import Path + +from PIL import Image + +_backend = Path(__file__).resolve().parent.parent +if str(_backend) not in sys.path: + sys.path.insert(0, str(_backend)) + +from scripts.upscale_cover import upscale_image + + +def upscale_map( + input_path: Path, + output_path: Path, + factor: str = "x2", +) -> None: + src = Image.open(input_path).convert("RGB") + w, h = src.size + print(f"Image source : {w}×{h} ({input_path.stat().st_size / 1024 / 1024:.1f} Mo)") + + half_w, half_h = w // 2, h // 2 + quadrants = [ + (0, 0, half_w, half_h), # top-left + (half_w, 0, w, half_h), # top-right + (0, half_h, half_w, h ), # bottom-left + (half_w, half_h, w, h ), # bottom-right + ] + labels = ["top-left", "top-right", "bottom-left", "bottom-right"] + + upscaled_quadrants: list[Image.Image] = [] + + for i, (box, label) in enumerate(zip(quadrants, labels)): + print(f"\n[{i+1}/4] Quadrant {label} {box}") + + quad_img = src.crop(box) + tmp_in = Path(f"/tmp/map_quad_{i}.png") + tmp_out = Path(f"/tmp/map_quad_{i}_up.png") + + buf = io.BytesIO() + quad_img.save(buf, format="PNG", optimize=False) + size_mb = len(buf.getvalue()) / 1024 / 1024 + print(f" Taille quadrant : {size_mb:.1f} Mo", end="") + if size_mb > 10: + print(" ⚠ > 10 Mo, passage en JPEG lossy pour respecter la limite API") + buf = io.BytesIO() + quad_img.save(buf, format="JPEG", quality=92) + tmp_in = Path(f"/tmp/map_quad_{i}.jpg") + else: + print() + + tmp_in.write_bytes(buf.getvalue()) + + upscale_image(tmp_in, tmp_out, factor=factor) + upscaled_quadrants.append(Image.open(tmp_out).convert("RGB")) + + uw, uh = upscaled_quadrants[0].size + print(f"\nRecomposition : quadrants upscalés {uw}×{uh} → image finale {uw*2}×{uh*2}") + final = Image.new("RGB", (uw * 2, uh * 2)) + final.paste(upscaled_quadrants[0], (0, 0)) + final.paste(upscaled_quadrants[1], (uw, 0)) + final.paste(upscaled_quadrants[2], (0, uh)) + final.paste(upscaled_quadrants[3], (uw, uh)) + + output_path.parent.mkdir(parents=True, exist_ok=True) + final.save(output_path, format="PNG", optimize=False) + print(f"\nSauvegardé : {output_path} ({output_path.stat().st_size / 1024 / 1024:.1f} Mo)") + + +def main() -> None: + default_map = _backend / "static" / "MAP.png" + parser = argparse.ArgumentParser(description="Upscale MAP.png en 4 quadrants via Vertex AI") + parser.add_argument("--input", type=Path, default=default_map) + parser.add_argument("--output", type=Path, default=None) + parser.add_argument("--factor", choices=("x2", "x4"), default="x2") + args = parser.parse_args() + + out = args.output or args.input + if not args.input.is_file(): + raise SystemExit(f"Fichier introuvable : {args.input}") + + upscale_map(args.input, out, factor=args.factor) + + +if __name__ == "__main__": + main() diff --git a/backend/static/MAP.png b/backend/static/MAP.png index 478337cd7adb3c003b61ab23d4a312d1d7ff4eae..5354eb491c5ab6e913d8b0c526e86ed170c98354 100644 --- a/backend/static/MAP.png +++ b/backend/static/MAP.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9cae7f23990a7aabcb16e876f3995e2362a474ee5f93392a04271aab19c7bc2a -size 2793697 +oid sha256:5f81f0fcbe37f3beb86b0babb4970b95992f303dd81b48ea2f7cae9abe212912 +size 121619726 diff --git a/backend/static/MAP_half.png b/backend/static/MAP_half.png new file mode 100644 index 0000000000000000000000000000000000000000..206eb3558ba67ca3440b47c965e1f77d83fb2f56 --- /dev/null +++ b/backend/static/MAP_half.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d0882150a2810c73f251862d8ef201a2074e0605c5674b38ae383d64e0da8ba +size 34314517 diff --git a/backend/static/MAP_quarter.png b/backend/static/MAP_quarter.png new file mode 100644 index 0000000000000000000000000000000000000000..7c4d4f255921a8df4d789e6eafe88e2e959d4bc8 --- /dev/null +++ b/backend/static/MAP_quarter.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c086468b655e4f86723b4024ea572cd75908b04bf4dabd894b5b09de3ec1413 +size 9155713 diff --git a/backend/static/compiled_map.json b/backend/static/compiled_map.json new file mode 100644 index 0000000000000000000000000000000000000000..4135d4fbe437ffb7b60e48f8fd6df299012f9403 --- /dev/null +++ b/backend/static/compiled_map.json @@ -0,0 +1,4211 @@ +{ + "exterior": [ + [ + 43.7365, + 65.5492 + ], + [ + 47.7007, + 67.2865 + ], + [ + 41.6913, + 75.7753 + ], + [ + 45.6539, + 77.8206 + ], + [ + 41.18, + 80.8884 + ], + [ + 46.4852, + 85.1941 + ], + [ + 41.9342, + 88.3091 + ], + [ + 51.1504, + 95.2049 + ], + [ + 58.3087, + 94.2606 + ], + [ + 62.1435, + 99.1675 + ], + [ + 46.8043, + 98.9119 + ], + [ + 41.18, + 95.4606 + ], + [ + 36.5783, + 98.1449 + ], + [ + 29.5478, + 93.2875 + ], + [ + 25.0739, + 94.6936 + ], + [ + 18.4269, + 93.0319 + ], + [ + 14.72, + 90.4753 + ], + [ + 11.2687, + 87.9188 + ], + [ + 7.6895, + 80.7606 + ], + [ + 5.6443, + 77.8206 + ], + [ + 10.1182, + 76.5423 + ], + [ + 11.1408, + 72.5797 + ], + [ + 16.1261, + 72.1962 + ], + [ + 16.1261, + 74.6249 + ], + [ + 23.2843, + 72.9632 + ], + [ + 29.6756, + 69.8953 + ], + [ + 33.8939, + 73.091 + ], + [ + 37.6009, + 73.9858 + ], + [ + 43.0878, + 65.1726 + ] + ], + "holes": [], + "nav_points": [ + [ + 2.7408, + 47.5359 + ], + [ + 4.3408, + 45.9359 + ], + [ + 4.3408, + 47.5359 + ], + [ + 5.9408, + 44.3359 + ], + [ + 5.9408, + 45.9359 + ], + [ + 5.9408, + 47.5359 + ], + [ + 5.9408, + 49.1359 + ], + [ + 5.9408, + 61.9359 + ], + [ + 5.9408, + 63.5359 + ], + [ + 7.5408, + 20.3359 + ], + [ + 7.5408, + 44.3359 + ], + [ + 7.5408, + 45.9359 + ], + [ + 7.5408, + 47.5359 + ], + [ + 7.5408, + 61.9359 + ], + [ + 7.5408, + 63.5359 + ], + [ + 7.5408, + 65.1359 + ], + [ + 7.5408, + 66.7359 + ], + [ + 9.1408, + 17.1359 + ], + [ + 9.1408, + 18.7359 + ], + [ + 9.1408, + 20.3359 + ], + [ + 9.1408, + 21.9359 + ], + [ + 9.1408, + 44.3359 + ], + [ + 9.1408, + 45.9359 + ], + [ + 9.1408, + 47.5359 + ], + [ + 9.1408, + 58.7359 + ], + [ + 9.1408, + 60.3359 + ], + [ + 9.1408, + 61.9359 + ], + [ + 9.1408, + 63.5359 + ], + [ + 9.1408, + 65.1359 + ], + [ + 9.1408, + 66.7359 + ], + [ + 9.1408, + 68.3359 + ], + [ + 9.1408, + 69.9359 + ], + [ + 10.7408, + 17.1359 + ], + [ + 10.7408, + 18.7359 + ], + [ + 10.7408, + 20.3359 + ], + [ + 10.7408, + 21.9359 + ], + [ + 10.7408, + 23.5359 + ], + [ + 10.7408, + 28.3359 + ], + [ + 10.7408, + 44.3359 + ], + [ + 10.7408, + 45.9359 + ], + [ + 10.7408, + 47.5359 + ], + [ + 10.7408, + 58.7359 + ], + [ + 10.7408, + 60.3359 + ], + [ + 10.7408, + 61.9359 + ], + [ + 10.7408, + 63.5359 + ], + [ + 10.7408, + 65.1359 + ], + [ + 10.7408, + 66.7359 + ], + [ + 10.7408, + 68.3359 + ], + [ + 10.7408, + 69.9359 + ], + [ + 10.7408, + 71.5359 + ], + [ + 12.3408, + 7.5359 + ], + [ + 12.3408, + 9.1359 + ], + [ + 12.3408, + 15.5359 + ], + [ + 12.3408, + 17.1359 + ], + [ + 12.3408, + 18.7359 + ], + [ + 12.3408, + 20.3359 + ], + [ + 12.3408, + 21.9359 + ], + [ + 12.3408, + 23.5359 + ], + [ + 12.3408, + 26.7359 + ], + [ + 12.3408, + 28.3359 + ], + [ + 12.3408, + 42.7359 + ], + [ + 12.3408, + 44.3359 + ], + [ + 12.3408, + 45.9359 + ], + [ + 12.3408, + 47.5359 + ], + [ + 12.3408, + 49.1359 + ], + [ + 12.3408, + 58.7359 + ], + [ + 12.3408, + 60.3359 + ], + [ + 12.3408, + 61.9359 + ], + [ + 12.3408, + 63.5359 + ], + [ + 12.3408, + 65.1359 + ], + [ + 12.3408, + 66.7359 + ], + [ + 12.3408, + 68.3359 + ], + [ + 12.3408, + 69.9359 + ], + [ + 12.3408, + 71.5359 + ], + [ + 13.9408, + 7.5359 + ], + [ + 13.9408, + 9.1359 + ], + [ + 13.9408, + 10.7359 + ], + [ + 13.9408, + 12.3359 + ], + [ + 13.9408, + 15.5359 + ], + [ + 13.9408, + 17.1359 + ], + [ + 13.9408, + 18.7359 + ], + [ + 13.9408, + 20.3359 + ], + [ + 13.9408, + 21.9359 + ], + [ + 13.9408, + 23.5359 + ], + [ + 13.9408, + 25.1359 + ], + [ + 13.9408, + 26.7359 + ], + [ + 13.9408, + 28.3359 + ], + [ + 13.9408, + 41.1359 + ], + [ + 13.9408, + 42.7359 + ], + [ + 13.9408, + 44.3359 + ], + [ + 13.9408, + 45.9359 + ], + [ + 13.9408, + 47.5359 + ], + [ + 13.9408, + 49.1359 + ], + [ + 13.9408, + 50.7359 + ], + [ + 13.9408, + 60.3359 + ], + [ + 13.9408, + 61.9359 + ], + [ + 13.9408, + 63.5359 + ], + [ + 13.9408, + 65.1359 + ], + [ + 13.9408, + 66.7359 + ], + [ + 13.9408, + 68.3359 + ], + [ + 13.9408, + 69.9359 + ], + [ + 13.9408, + 71.5359 + ], + [ + 13.9408, + 73.1359 + ], + [ + 15.5408, + 5.9359 + ], + [ + 15.5408, + 7.5359 + ], + [ + 15.5408, + 9.1359 + ], + [ + 15.5408, + 10.7359 + ], + [ + 15.5408, + 12.3359 + ], + [ + 15.5408, + 13.9359 + ], + [ + 15.5408, + 15.5359 + ], + [ + 15.5408, + 17.1359 + ], + [ + 15.5408, + 18.7359 + ], + [ + 15.5408, + 20.3359 + ], + [ + 15.5408, + 21.9359 + ], + [ + 15.5408, + 23.5359 + ], + [ + 15.5408, + 25.1359 + ], + [ + 15.5408, + 26.7359 + ], + [ + 15.5408, + 28.3359 + ], + [ + 15.5408, + 29.9359 + ], + [ + 15.5408, + 39.5359 + ], + [ + 15.5408, + 41.1359 + ], + [ + 15.5408, + 42.7359 + ], + [ + 15.5408, + 44.3359 + ], + [ + 15.5408, + 45.9359 + ], + [ + 15.5408, + 47.5359 + ], + [ + 15.5408, + 49.1359 + ], + [ + 15.5408, + 50.7359 + ], + [ + 15.5408, + 60.3359 + ], + [ + 15.5408, + 61.9359 + ], + [ + 15.5408, + 63.5359 + ], + [ + 15.5408, + 65.1359 + ], + [ + 15.5408, + 66.7359 + ], + [ + 15.5408, + 68.3359 + ], + [ + 15.5408, + 69.9359 + ], + [ + 15.5408, + 71.5359 + ], + [ + 15.5408, + 73.1359 + ], + [ + 17.1408, + 5.9359 + ], + [ + 17.1408, + 7.5359 + ], + [ + 17.1408, + 9.1359 + ], + [ + 17.1408, + 10.7359 + ], + [ + 17.1408, + 12.3359 + ], + [ + 17.1408, + 13.9359 + ], + [ + 17.1408, + 15.5359 + ], + [ + 17.1408, + 17.1359 + ], + [ + 17.1408, + 18.7359 + ], + [ + 17.1408, + 21.9359 + ], + [ + 17.1408, + 23.5359 + ], + [ + 17.1408, + 25.1359 + ], + [ + 17.1408, + 26.7359 + ], + [ + 17.1408, + 28.3359 + ], + [ + 17.1408, + 29.9359 + ], + [ + 17.1408, + 39.5359 + ], + [ + 17.1408, + 41.1359 + ], + [ + 17.1408, + 42.7359 + ], + [ + 17.1408, + 47.5359 + ], + [ + 17.1408, + 49.1359 + ], + [ + 17.1408, + 50.7359 + ], + [ + 17.1408, + 52.3359 + ], + [ + 17.1408, + 58.7359 + ], + [ + 17.1408, + 60.3359 + ], + [ + 17.1408, + 61.9359 + ], + [ + 17.1408, + 63.5359 + ], + [ + 17.1408, + 65.1359 + ], + [ + 17.1408, + 66.7359 + ], + [ + 17.1408, + 68.3359 + ], + [ + 17.1408, + 69.9359 + ], + [ + 17.1408, + 71.5359 + ], + [ + 17.1408, + 73.1359 + ], + [ + 17.1408, + 74.7359 + ], + [ + 18.7408, + 5.9359 + ], + [ + 18.7408, + 7.5359 + ], + [ + 18.7408, + 9.1359 + ], + [ + 18.7408, + 10.7359 + ], + [ + 18.7408, + 12.3359 + ], + [ + 18.7408, + 13.9359 + ], + [ + 18.7408, + 15.5359 + ], + [ + 18.7408, + 17.1359 + ], + [ + 18.7408, + 23.5359 + ], + [ + 18.7408, + 25.1359 + ], + [ + 18.7408, + 26.7359 + ], + [ + 18.7408, + 28.3359 + ], + [ + 18.7408, + 29.9359 + ], + [ + 18.7408, + 37.9359 + ], + [ + 18.7408, + 39.5359 + ], + [ + 18.7408, + 41.1359 + ], + [ + 18.7408, + 49.1359 + ], + [ + 18.7408, + 50.7359 + ], + [ + 18.7408, + 52.3359 + ], + [ + 18.7408, + 53.9359 + ], + [ + 18.7408, + 58.7359 + ], + [ + 18.7408, + 60.3359 + ], + [ + 18.7408, + 61.9359 + ], + [ + 18.7408, + 63.5359 + ], + [ + 18.7408, + 65.1359 + ], + [ + 18.7408, + 66.7359 + ], + [ + 18.7408, + 68.3359 + ], + [ + 18.7408, + 69.9359 + ], + [ + 18.7408, + 71.5359 + ], + [ + 18.7408, + 73.1359 + ], + [ + 18.7408, + 74.7359 + ], + [ + 20.3408, + 5.9359 + ], + [ + 20.3408, + 7.5359 + ], + [ + 20.3408, + 9.1359 + ], + [ + 20.3408, + 10.7359 + ], + [ + 20.3408, + 12.3359 + ], + [ + 20.3408, + 13.9359 + ], + [ + 20.3408, + 15.5359 + ], + [ + 20.3408, + 17.1359 + ], + [ + 20.3408, + 23.5359 + ], + [ + 20.3408, + 25.1359 + ], + [ + 20.3408, + 26.7359 + ], + [ + 20.3408, + 29.9359 + ], + [ + 20.3408, + 31.5359 + ], + [ + 20.3408, + 37.9359 + ], + [ + 20.3408, + 39.5359 + ], + [ + 20.3408, + 41.1359 + ], + [ + 20.3408, + 49.1359 + ], + [ + 20.3408, + 52.3359 + ], + [ + 20.3408, + 53.9359 + ], + [ + 20.3408, + 55.5359 + ], + [ + 20.3408, + 58.7359 + ], + [ + 20.3408, + 60.3359 + ], + [ + 20.3408, + 61.9359 + ], + [ + 20.3408, + 63.5359 + ], + [ + 20.3408, + 65.1359 + ], + [ + 20.3408, + 66.7359 + ], + [ + 20.3408, + 68.3359 + ], + [ + 20.3408, + 69.9359 + ], + [ + 20.3408, + 71.5359 + ], + [ + 20.3408, + 73.1359 + ], + [ + 20.3408, + 74.7359 + ], + [ + 21.9408, + 5.9359 + ], + [ + 21.9408, + 7.5359 + ], + [ + 21.9408, + 9.1359 + ], + [ + 21.9408, + 10.7359 + ], + [ + 21.9408, + 12.3359 + ], + [ + 21.9408, + 13.9359 + ], + [ + 21.9408, + 15.5359 + ], + [ + 21.9408, + 17.1359 + ], + [ + 21.9408, + 25.1359 + ], + [ + 21.9408, + 29.9359 + ], + [ + 21.9408, + 31.5359 + ], + [ + 21.9408, + 39.5359 + ], + [ + 21.9408, + 47.5359 + ], + [ + 21.9408, + 49.1359 + ], + [ + 21.9408, + 53.9359 + ], + [ + 21.9408, + 55.5359 + ], + [ + 21.9408, + 57.1359 + ], + [ + 21.9408, + 58.7359 + ], + [ + 21.9408, + 60.3359 + ], + [ + 21.9408, + 61.9359 + ], + [ + 21.9408, + 63.5359 + ], + [ + 21.9408, + 65.1359 + ], + [ + 21.9408, + 66.7359 + ], + [ + 21.9408, + 68.3359 + ], + [ + 21.9408, + 69.9359 + ], + [ + 21.9408, + 71.5359 + ], + [ + 21.9408, + 73.1359 + ], + [ + 21.9408, + 74.7359 + ], + [ + 23.5408, + 5.9359 + ], + [ + 23.5408, + 7.5359 + ], + [ + 23.5408, + 9.1359 + ], + [ + 23.5408, + 10.7359 + ], + [ + 23.5408, + 12.3359 + ], + [ + 23.5408, + 13.9359 + ], + [ + 23.5408, + 15.5359 + ], + [ + 23.5408, + 17.1359 + ], + [ + 23.5408, + 31.5359 + ], + [ + 23.5408, + 42.7359 + ], + [ + 23.5408, + 45.9359 + ], + [ + 23.5408, + 47.5359 + ], + [ + 23.5408, + 57.1359 + ], + [ + 23.5408, + 58.7359 + ], + [ + 23.5408, + 60.3359 + ], + [ + 23.5408, + 61.9359 + ], + [ + 23.5408, + 63.5359 + ], + [ + 23.5408, + 65.1359 + ], + [ + 23.5408, + 66.7359 + ], + [ + 23.5408, + 68.3359 + ], + [ + 23.5408, + 69.9359 + ], + [ + 23.5408, + 71.5359 + ], + [ + 23.5408, + 73.1359 + ], + [ + 25.1408, + 7.5359 + ], + [ + 25.1408, + 9.1359 + ], + [ + 25.1408, + 10.7359 + ], + [ + 25.1408, + 12.3359 + ], + [ + 25.1408, + 13.9359 + ], + [ + 25.1408, + 15.5359 + ], + [ + 25.1408, + 17.1359 + ], + [ + 25.1408, + 31.5359 + ], + [ + 25.1408, + 33.1359 + ], + [ + 25.1408, + 42.7359 + ], + [ + 25.1408, + 45.9359 + ], + [ + 25.1408, + 57.1359 + ], + [ + 25.1408, + 58.7359 + ], + [ + 25.1408, + 60.3359 + ], + [ + 25.1408, + 61.9359 + ], + [ + 25.1408, + 63.5359 + ], + [ + 25.1408, + 65.1359 + ], + [ + 25.1408, + 66.7359 + ], + [ + 25.1408, + 68.3359 + ], + [ + 25.1408, + 69.9359 + ], + [ + 25.1408, + 71.5359 + ], + [ + 25.1408, + 73.1359 + ], + [ + 25.1408, + 74.7359 + ], + [ + 26.7408, + 9.1359 + ], + [ + 26.7408, + 10.7359 + ], + [ + 26.7408, + 12.3359 + ], + [ + 26.7408, + 13.9359 + ], + [ + 26.7408, + 15.5359 + ], + [ + 26.7408, + 17.1359 + ], + [ + 26.7408, + 18.7359 + ], + [ + 26.7408, + 31.5359 + ], + [ + 26.7408, + 33.1359 + ], + [ + 26.7408, + 42.7359 + ], + [ + 26.7408, + 44.3359 + ], + [ + 26.7408, + 45.9359 + ], + [ + 26.7408, + 58.7359 + ], + [ + 26.7408, + 60.3359 + ], + [ + 26.7408, + 61.9359 + ], + [ + 26.7408, + 63.5359 + ], + [ + 26.7408, + 65.1359 + ], + [ + 26.7408, + 66.7359 + ], + [ + 26.7408, + 68.3359 + ], + [ + 26.7408, + 69.9359 + ], + [ + 26.7408, + 71.5359 + ], + [ + 26.7408, + 73.1359 + ], + [ + 26.7408, + 74.7359 + ], + [ + 26.7408, + 76.3359 + ], + [ + 28.3408, + 10.7359 + ], + [ + 28.3408, + 12.3359 + ], + [ + 28.3408, + 13.9359 + ], + [ + 28.3408, + 15.5359 + ], + [ + 28.3408, + 17.1359 + ], + [ + 28.3408, + 18.7359 + ], + [ + 28.3408, + 20.3359 + ], + [ + 28.3408, + 31.5359 + ], + [ + 28.3408, + 33.1359 + ], + [ + 28.3408, + 42.7359 + ], + [ + 28.3408, + 44.3359 + ], + [ + 28.3408, + 45.9359 + ], + [ + 28.3408, + 47.5359 + ], + [ + 28.3408, + 60.3359 + ], + [ + 28.3408, + 61.9359 + ], + [ + 28.3408, + 63.5359 + ], + [ + 28.3408, + 65.1359 + ], + [ + 28.3408, + 66.7359 + ], + [ + 28.3408, + 68.3359 + ], + [ + 28.3408, + 69.9359 + ], + [ + 28.3408, + 71.5359 + ], + [ + 28.3408, + 73.1359 + ], + [ + 28.3408, + 74.7359 + ], + [ + 28.3408, + 76.3359 + ], + [ + 29.9408, + 10.7359 + ], + [ + 29.9408, + 12.3359 + ], + [ + 29.9408, + 13.9359 + ], + [ + 29.9408, + 15.5359 + ], + [ + 29.9408, + 18.7359 + ], + [ + 29.9408, + 20.3359 + ], + [ + 29.9408, + 21.9359 + ], + [ + 29.9408, + 23.5359 + ], + [ + 29.9408, + 25.1359 + ], + [ + 29.9408, + 26.7359 + ], + [ + 29.9408, + 33.1359 + ], + [ + 29.9408, + 34.7359 + ], + [ + 29.9408, + 42.7359 + ], + [ + 29.9408, + 44.3359 + ], + [ + 29.9408, + 47.5359 + ], + [ + 29.9408, + 49.1359 + ], + [ + 29.9408, + 60.3359 + ], + [ + 29.9408, + 61.9359 + ], + [ + 29.9408, + 63.5359 + ], + [ + 29.9408, + 65.1359 + ], + [ + 29.9408, + 66.7359 + ], + [ + 29.9408, + 68.3359 + ], + [ + 29.9408, + 69.9359 + ], + [ + 29.9408, + 71.5359 + ], + [ + 29.9408, + 73.1359 + ], + [ + 29.9408, + 74.7359 + ], + [ + 29.9408, + 76.3359 + ], + [ + 29.9408, + 77.9359 + ], + [ + 31.5408, + 10.7359 + ], + [ + 31.5408, + 12.3359 + ], + [ + 31.5408, + 13.9359 + ], + [ + 31.5408, + 21.9359 + ], + [ + 31.5408, + 23.5359 + ], + [ + 31.5408, + 25.1359 + ], + [ + 31.5408, + 26.7359 + ], + [ + 31.5408, + 33.1359 + ], + [ + 31.5408, + 34.7359 + ], + [ + 31.5408, + 42.7359 + ], + [ + 31.5408, + 49.1359 + ], + [ + 31.5408, + 50.7359 + ], + [ + 31.5408, + 57.1359 + ], + [ + 31.5408, + 58.7359 + ], + [ + 31.5408, + 60.3359 + ], + [ + 31.5408, + 61.9359 + ], + [ + 31.5408, + 63.5359 + ], + [ + 31.5408, + 65.1359 + ], + [ + 31.5408, + 66.7359 + ], + [ + 31.5408, + 68.3359 + ], + [ + 31.5408, + 69.9359 + ], + [ + 31.5408, + 71.5359 + ], + [ + 31.5408, + 73.1359 + ], + [ + 31.5408, + 74.7359 + ], + [ + 31.5408, + 76.3359 + ], + [ + 33.1408, + 10.7359 + ], + [ + 33.1408, + 12.3359 + ], + [ + 33.1408, + 23.5359 + ], + [ + 33.1408, + 25.1359 + ], + [ + 33.1408, + 26.7359 + ], + [ + 33.1408, + 28.3359 + ], + [ + 33.1408, + 33.1359 + ], + [ + 33.1408, + 34.7359 + ], + [ + 33.1408, + 42.7359 + ], + [ + 33.1408, + 47.5359 + ], + [ + 33.1408, + 49.1359 + ], + [ + 33.1408, + 50.7359 + ], + [ + 33.1408, + 55.5359 + ], + [ + 33.1408, + 57.1359 + ], + [ + 33.1408, + 58.7359 + ], + [ + 33.1408, + 60.3359 + ], + [ + 33.1408, + 61.9359 + ], + [ + 33.1408, + 63.5359 + ], + [ + 33.1408, + 65.1359 + ], + [ + 33.1408, + 66.7359 + ], + [ + 33.1408, + 68.3359 + ], + [ + 33.1408, + 69.9359 + ], + [ + 33.1408, + 71.5359 + ], + [ + 33.1408, + 73.1359 + ], + [ + 33.1408, + 74.7359 + ], + [ + 33.1408, + 76.3359 + ], + [ + 34.7408, + 10.7359 + ], + [ + 34.7408, + 12.3359 + ], + [ + 34.7408, + 25.1359 + ], + [ + 34.7408, + 26.7359 + ], + [ + 34.7408, + 28.3359 + ], + [ + 34.7408, + 33.1359 + ], + [ + 34.7408, + 34.7359 + ], + [ + 34.7408, + 42.7359 + ], + [ + 34.7408, + 47.5359 + ], + [ + 34.7408, + 49.1359 + ], + [ + 34.7408, + 50.7359 + ], + [ + 34.7408, + 52.3359 + ], + [ + 34.7408, + 53.9359 + ], + [ + 34.7408, + 55.5359 + ], + [ + 34.7408, + 57.1359 + ], + [ + 34.7408, + 61.9359 + ], + [ + 34.7408, + 66.7359 + ], + [ + 34.7408, + 68.3359 + ], + [ + 34.7408, + 73.1359 + ], + [ + 34.7408, + 74.7359 + ], + [ + 34.7408, + 76.3359 + ], + [ + 36.3408, + 10.7359 + ], + [ + 36.3408, + 12.3359 + ], + [ + 36.3408, + 25.1359 + ], + [ + 36.3408, + 26.7359 + ], + [ + 36.3408, + 33.1359 + ], + [ + 36.3408, + 34.7359 + ], + [ + 36.3408, + 36.3359 + ], + [ + 36.3408, + 45.9359 + ], + [ + 36.3408, + 47.5359 + ], + [ + 36.3408, + 49.1359 + ], + [ + 36.3408, + 50.7359 + ], + [ + 36.3408, + 52.3359 + ], + [ + 36.3408, + 53.9359 + ], + [ + 36.3408, + 55.5359 + ], + [ + 36.3408, + 68.3359 + ], + [ + 36.3408, + 73.1359 + ], + [ + 36.3408, + 74.7359 + ], + [ + 36.3408, + 76.3359 + ], + [ + 36.3408, + 77.9359 + ], + [ + 37.9408, + 10.7359 + ], + [ + 37.9408, + 12.3359 + ], + [ + 37.9408, + 25.1359 + ], + [ + 37.9408, + 34.7359 + ], + [ + 37.9408, + 36.3359 + ], + [ + 37.9408, + 45.9359 + ], + [ + 37.9408, + 47.5359 + ], + [ + 37.9408, + 49.1359 + ], + [ + 37.9408, + 50.7359 + ], + [ + 37.9408, + 52.3359 + ], + [ + 37.9408, + 53.9359 + ], + [ + 37.9408, + 74.7359 + ], + [ + 37.9408, + 76.3359 + ], + [ + 37.9408, + 77.9359 + ], + [ + 39.5408, + 10.7359 + ], + [ + 39.5408, + 12.3359 + ], + [ + 39.5408, + 34.7359 + ], + [ + 39.5408, + 36.3359 + ], + [ + 39.5408, + 41.1359 + ], + [ + 39.5408, + 45.9359 + ], + [ + 39.5408, + 47.5359 + ], + [ + 39.5408, + 49.1359 + ], + [ + 39.5408, + 50.7359 + ], + [ + 39.5408, + 52.3359 + ], + [ + 39.5408, + 53.9359 + ], + [ + 39.5408, + 76.3359 + ], + [ + 39.5408, + 77.9359 + ], + [ + 41.1408, + 10.7359 + ], + [ + 41.1408, + 12.3359 + ], + [ + 41.1408, + 34.7359 + ], + [ + 41.1408, + 36.3359 + ], + [ + 41.1408, + 37.9359 + ], + [ + 41.1408, + 39.5359 + ], + [ + 41.1408, + 44.3359 + ], + [ + 41.1408, + 45.9359 + ], + [ + 41.1408, + 47.5359 + ], + [ + 41.1408, + 49.1359 + ], + [ + 41.1408, + 50.7359 + ], + [ + 41.1408, + 52.3359 + ], + [ + 41.1408, + 53.9359 + ], + [ + 41.1408, + 76.3359 + ], + [ + 41.1408, + 77.9359 + ], + [ + 42.7408, + 10.7359 + ], + [ + 42.7408, + 12.3359 + ], + [ + 42.7408, + 34.7359 + ], + [ + 42.7408, + 36.3359 + ], + [ + 42.7408, + 37.9359 + ], + [ + 42.7408, + 39.5359 + ], + [ + 42.7408, + 44.3359 + ], + [ + 42.7408, + 45.9359 + ], + [ + 42.7408, + 47.5359 + ], + [ + 42.7408, + 49.1359 + ], + [ + 42.7408, + 50.7359 + ], + [ + 42.7408, + 52.3359 + ], + [ + 42.7408, + 53.9359 + ], + [ + 42.7408, + 76.3359 + ], + [ + 42.7408, + 77.9359 + ], + [ + 44.3408, + 10.7359 + ], + [ + 44.3408, + 12.3359 + ], + [ + 44.3408, + 34.7359 + ], + [ + 44.3408, + 36.3359 + ], + [ + 44.3408, + 37.9359 + ], + [ + 44.3408, + 39.5359 + ], + [ + 44.3408, + 44.3359 + ], + [ + 44.3408, + 45.9359 + ], + [ + 44.3408, + 47.5359 + ], + [ + 44.3408, + 49.1359 + ], + [ + 44.3408, + 50.7359 + ], + [ + 44.3408, + 52.3359 + ], + [ + 44.3408, + 53.9359 + ], + [ + 44.3408, + 76.3359 + ], + [ + 44.3408, + 77.9359 + ], + [ + 45.9408, + 9.1359 + ], + [ + 45.9408, + 10.7359 + ], + [ + 45.9408, + 12.3359 + ], + [ + 45.9408, + 13.9359 + ], + [ + 45.9408, + 34.7359 + ], + [ + 45.9408, + 36.3359 + ], + [ + 45.9408, + 39.5359 + ], + [ + 45.9408, + 41.1359 + ], + [ + 45.9408, + 45.9359 + ], + [ + 45.9408, + 47.5359 + ], + [ + 45.9408, + 49.1359 + ], + [ + 45.9408, + 50.7359 + ], + [ + 45.9408, + 52.3359 + ], + [ + 45.9408, + 53.9359 + ], + [ + 45.9408, + 76.3359 + ], + [ + 45.9408, + 77.9359 + ], + [ + 47.5408, + 9.1359 + ], + [ + 47.5408, + 10.7359 + ], + [ + 47.5408, + 12.3359 + ], + [ + 47.5408, + 25.1359 + ], + [ + 47.5408, + 26.7359 + ], + [ + 47.5408, + 34.7359 + ], + [ + 47.5408, + 36.3359 + ], + [ + 47.5408, + 41.1359 + ], + [ + 47.5408, + 45.9359 + ], + [ + 47.5408, + 47.5359 + ], + [ + 47.5408, + 49.1359 + ], + [ + 47.5408, + 50.7359 + ], + [ + 47.5408, + 52.3359 + ], + [ + 47.5408, + 53.9359 + ], + [ + 47.5408, + 76.3359 + ], + [ + 47.5408, + 77.9359 + ], + [ + 49.1408, + 9.1359 + ], + [ + 49.1408, + 10.7359 + ], + [ + 49.1408, + 23.5359 + ], + [ + 49.1408, + 25.1359 + ], + [ + 49.1408, + 26.7359 + ], + [ + 49.1408, + 34.7359 + ], + [ + 49.1408, + 36.3359 + ], + [ + 49.1408, + 41.1359 + ], + [ + 49.1408, + 47.5359 + ], + [ + 49.1408, + 49.1359 + ], + [ + 49.1408, + 50.7359 + ], + [ + 49.1408, + 52.3359 + ], + [ + 49.1408, + 53.9359 + ], + [ + 49.1408, + 74.7359 + ], + [ + 49.1408, + 76.3359 + ], + [ + 49.1408, + 77.9359 + ], + [ + 50.7408, + 9.1359 + ], + [ + 50.7408, + 10.7359 + ], + [ + 50.7408, + 23.5359 + ], + [ + 50.7408, + 25.1359 + ], + [ + 50.7408, + 26.7359 + ], + [ + 50.7408, + 28.3359 + ], + [ + 50.7408, + 29.9359 + ], + [ + 50.7408, + 31.5359 + ], + [ + 50.7408, + 34.7359 + ], + [ + 50.7408, + 36.3359 + ], + [ + 50.7408, + 41.1359 + ], + [ + 50.7408, + 42.7359 + ], + [ + 50.7408, + 47.5359 + ], + [ + 50.7408, + 49.1359 + ], + [ + 50.7408, + 50.7359 + ], + [ + 50.7408, + 52.3359 + ], + [ + 50.7408, + 53.9359 + ], + [ + 50.7408, + 55.5359 + ], + [ + 50.7408, + 73.1359 + ], + [ + 50.7408, + 74.7359 + ], + [ + 50.7408, + 76.3359 + ], + [ + 52.3408, + 9.1359 + ], + [ + 52.3408, + 10.7359 + ], + [ + 52.3408, + 23.5359 + ], + [ + 52.3408, + 25.1359 + ], + [ + 52.3408, + 26.7359 + ], + [ + 52.3408, + 28.3359 + ], + [ + 52.3408, + 29.9359 + ], + [ + 52.3408, + 31.5359 + ], + [ + 52.3408, + 33.1359 + ], + [ + 52.3408, + 34.7359 + ], + [ + 52.3408, + 36.3359 + ], + [ + 52.3408, + 39.5359 + ], + [ + 52.3408, + 41.1359 + ], + [ + 52.3408, + 42.7359 + ], + [ + 52.3408, + 47.5359 + ], + [ + 52.3408, + 49.1359 + ], + [ + 52.3408, + 50.7359 + ], + [ + 52.3408, + 52.3359 + ], + [ + 52.3408, + 53.9359 + ], + [ + 52.3408, + 55.5359 + ], + [ + 52.3408, + 57.1359 + ], + [ + 52.3408, + 58.7359 + ], + [ + 52.3408, + 63.5359 + ], + [ + 52.3408, + 65.1359 + ], + [ + 52.3408, + 71.5359 + ], + [ + 52.3408, + 73.1359 + ], + [ + 52.3408, + 74.7359 + ], + [ + 52.3408, + 76.3359 + ], + [ + 53.9408, + 9.1359 + ], + [ + 53.9408, + 15.5359 + ], + [ + 53.9408, + 21.9359 + ], + [ + 53.9408, + 23.5359 + ], + [ + 53.9408, + 25.1359 + ], + [ + 53.9408, + 26.7359 + ], + [ + 53.9408, + 28.3359 + ], + [ + 53.9408, + 29.9359 + ], + [ + 53.9408, + 31.5359 + ], + [ + 53.9408, + 33.1359 + ], + [ + 53.9408, + 34.7359 + ], + [ + 53.9408, + 39.5359 + ], + [ + 53.9408, + 41.1359 + ], + [ + 53.9408, + 42.7359 + ], + [ + 53.9408, + 47.5359 + ], + [ + 53.9408, + 49.1359 + ], + [ + 53.9408, + 50.7359 + ], + [ + 53.9408, + 52.3359 + ], + [ + 53.9408, + 57.1359 + ], + [ + 53.9408, + 58.7359 + ], + [ + 53.9408, + 60.3359 + ], + [ + 53.9408, + 61.9359 + ], + [ + 53.9408, + 63.5359 + ], + [ + 53.9408, + 65.1359 + ], + [ + 53.9408, + 66.7359 + ], + [ + 53.9408, + 71.5359 + ], + [ + 53.9408, + 73.1359 + ], + [ + 53.9408, + 74.7359 + ], + [ + 55.5408, + 9.1359 + ], + [ + 55.5408, + 10.7359 + ], + [ + 55.5408, + 13.9359 + ], + [ + 55.5408, + 15.5359 + ], + [ + 55.5408, + 17.1359 + ], + [ + 55.5408, + 18.7359 + ], + [ + 55.5408, + 20.3359 + ], + [ + 55.5408, + 21.9359 + ], + [ + 55.5408, + 23.5359 + ], + [ + 55.5408, + 25.1359 + ], + [ + 55.5408, + 26.7359 + ], + [ + 55.5408, + 28.3359 + ], + [ + 55.5408, + 29.9359 + ], + [ + 55.5408, + 31.5359 + ], + [ + 55.5408, + 33.1359 + ], + [ + 55.5408, + 34.7359 + ], + [ + 55.5408, + 37.9359 + ], + [ + 55.5408, + 39.5359 + ], + [ + 55.5408, + 41.1359 + ], + [ + 55.5408, + 42.7359 + ], + [ + 55.5408, + 44.3359 + ], + [ + 55.5408, + 47.5359 + ], + [ + 55.5408, + 49.1359 + ], + [ + 55.5408, + 50.7359 + ], + [ + 55.5408, + 52.3359 + ], + [ + 55.5408, + 58.7359 + ], + [ + 55.5408, + 60.3359 + ], + [ + 55.5408, + 61.9359 + ], + [ + 55.5408, + 63.5359 + ], + [ + 55.5408, + 65.1359 + ], + [ + 55.5408, + 66.7359 + ], + [ + 55.5408, + 68.3359 + ], + [ + 55.5408, + 69.9359 + ], + [ + 55.5408, + 71.5359 + ], + [ + 55.5408, + 73.1359 + ], + [ + 55.5408, + 74.7359 + ], + [ + 57.1408, + 9.1359 + ], + [ + 57.1408, + 10.7359 + ], + [ + 57.1408, + 12.3359 + ], + [ + 57.1408, + 13.9359 + ], + [ + 57.1408, + 15.5359 + ], + [ + 57.1408, + 17.1359 + ], + [ + 57.1408, + 18.7359 + ], + [ + 57.1408, + 20.3359 + ], + [ + 57.1408, + 21.9359 + ], + [ + 57.1408, + 26.7359 + ], + [ + 57.1408, + 28.3359 + ], + [ + 57.1408, + 29.9359 + ], + [ + 57.1408, + 31.5359 + ], + [ + 57.1408, + 33.1359 + ], + [ + 57.1408, + 37.9359 + ], + [ + 57.1408, + 39.5359 + ], + [ + 57.1408, + 41.1359 + ], + [ + 57.1408, + 42.7359 + ], + [ + 57.1408, + 44.3359 + ], + [ + 57.1408, + 45.9359 + ], + [ + 57.1408, + 47.5359 + ], + [ + 57.1408, + 60.3359 + ], + [ + 57.1408, + 61.9359 + ], + [ + 57.1408, + 63.5359 + ], + [ + 57.1408, + 65.1359 + ], + [ + 57.1408, + 66.7359 + ], + [ + 57.1408, + 68.3359 + ], + [ + 57.1408, + 69.9359 + ], + [ + 57.1408, + 71.5359 + ], + [ + 57.1408, + 73.1359 + ], + [ + 58.7408, + 7.5359 + ], + [ + 58.7408, + 9.1359 + ], + [ + 58.7408, + 10.7359 + ], + [ + 58.7408, + 12.3359 + ], + [ + 58.7408, + 13.9359 + ], + [ + 58.7408, + 15.5359 + ], + [ + 58.7408, + 17.1359 + ], + [ + 58.7408, + 18.7359 + ], + [ + 58.7408, + 20.3359 + ], + [ + 58.7408, + 21.9359 + ], + [ + 58.7408, + 29.9359 + ], + [ + 58.7408, + 31.5359 + ], + [ + 58.7408, + 37.9359 + ], + [ + 58.7408, + 39.5359 + ], + [ + 58.7408, + 41.1359 + ], + [ + 58.7408, + 42.7359 + ], + [ + 58.7408, + 44.3359 + ], + [ + 58.7408, + 45.9359 + ], + [ + 58.7408, + 58.7359 + ], + [ + 58.7408, + 60.3359 + ], + [ + 58.7408, + 61.9359 + ], + [ + 58.7408, + 63.5359 + ], + [ + 58.7408, + 65.1359 + ], + [ + 58.7408, + 66.7359 + ], + [ + 58.7408, + 68.3359 + ], + [ + 58.7408, + 69.9359 + ], + [ + 58.7408, + 71.5359 + ], + [ + 60.3408, + 7.5359 + ], + [ + 60.3408, + 9.1359 + ], + [ + 60.3408, + 10.7359 + ], + [ + 60.3408, + 12.3359 + ], + [ + 60.3408, + 13.9359 + ], + [ + 60.3408, + 15.5359 + ], + [ + 60.3408, + 17.1359 + ], + [ + 60.3408, + 18.7359 + ], + [ + 60.3408, + 20.3359 + ], + [ + 60.3408, + 21.9359 + ], + [ + 60.3408, + 23.5359 + ], + [ + 60.3408, + 29.9359 + ], + [ + 60.3408, + 31.5359 + ], + [ + 60.3408, + 36.3359 + ], + [ + 60.3408, + 37.9359 + ], + [ + 60.3408, + 39.5359 + ], + [ + 60.3408, + 41.1359 + ], + [ + 60.3408, + 42.7359 + ], + [ + 60.3408, + 44.3359 + ], + [ + 60.3408, + 58.7359 + ], + [ + 60.3408, + 60.3359 + ], + [ + 60.3408, + 63.5359 + ], + [ + 60.3408, + 65.1359 + ], + [ + 60.3408, + 66.7359 + ], + [ + 60.3408, + 68.3359 + ], + [ + 60.3408, + 69.9359 + ], + [ + 60.3408, + 71.5359 + ], + [ + 61.9408, + 10.7359 + ], + [ + 61.9408, + 12.3359 + ], + [ + 61.9408, + 13.9359 + ], + [ + 61.9408, + 15.5359 + ], + [ + 61.9408, + 17.1359 + ], + [ + 61.9408, + 18.7359 + ], + [ + 61.9408, + 20.3359 + ], + [ + 61.9408, + 21.9359 + ], + [ + 61.9408, + 23.5359 + ], + [ + 61.9408, + 25.1359 + ], + [ + 61.9408, + 29.9359 + ], + [ + 61.9408, + 33.1359 + ], + [ + 61.9408, + 34.7359 + ], + [ + 61.9408, + 36.3359 + ], + [ + 61.9408, + 37.9359 + ], + [ + 61.9408, + 39.5359 + ], + [ + 61.9408, + 41.1359 + ], + [ + 61.9408, + 42.7359 + ], + [ + 61.9408, + 57.1359 + ], + [ + 61.9408, + 58.7359 + ], + [ + 61.9408, + 60.3359 + ], + [ + 61.9408, + 63.5359 + ], + [ + 61.9408, + 65.1359 + ], + [ + 61.9408, + 66.7359 + ], + [ + 61.9408, + 68.3359 + ], + [ + 61.9408, + 69.9359 + ], + [ + 63.5408, + 9.1359 + ], + [ + 63.5408, + 10.7359 + ], + [ + 63.5408, + 12.3359 + ], + [ + 63.5408, + 13.9359 + ], + [ + 63.5408, + 15.5359 + ], + [ + 63.5408, + 17.1359 + ], + [ + 63.5408, + 18.7359 + ], + [ + 63.5408, + 20.3359 + ], + [ + 63.5408, + 21.9359 + ], + [ + 63.5408, + 23.5359 + ], + [ + 63.5408, + 25.1359 + ], + [ + 63.5408, + 33.1359 + ], + [ + 63.5408, + 34.7359 + ], + [ + 63.5408, + 36.3359 + ], + [ + 63.5408, + 37.9359 + ], + [ + 63.5408, + 39.5359 + ], + [ + 63.5408, + 41.1359 + ], + [ + 63.5408, + 42.7359 + ], + [ + 63.5408, + 57.1359 + ], + [ + 63.5408, + 58.7359 + ], + [ + 63.5408, + 65.1359 + ], + [ + 63.5408, + 66.7359 + ], + [ + 63.5408, + 68.3359 + ], + [ + 63.5408, + 69.9359 + ], + [ + 63.5408, + 71.5359 + ], + [ + 65.1408, + 7.5359 + ], + [ + 65.1408, + 9.1359 + ], + [ + 65.1408, + 10.7359 + ], + [ + 65.1408, + 12.3359 + ], + [ + 65.1408, + 13.9359 + ], + [ + 65.1408, + 15.5359 + ], + [ + 65.1408, + 17.1359 + ], + [ + 65.1408, + 18.7359 + ], + [ + 65.1408, + 20.3359 + ], + [ + 65.1408, + 21.9359 + ], + [ + 65.1408, + 23.5359 + ], + [ + 65.1408, + 25.1359 + ], + [ + 65.1408, + 31.5359 + ], + [ + 65.1408, + 33.1359 + ], + [ + 65.1408, + 34.7359 + ], + [ + 65.1408, + 36.3359 + ], + [ + 65.1408, + 37.9359 + ], + [ + 65.1408, + 39.5359 + ], + [ + 65.1408, + 41.1359 + ], + [ + 65.1408, + 42.7359 + ], + [ + 65.1408, + 57.1359 + ], + [ + 65.1408, + 58.7359 + ], + [ + 65.1408, + 60.3359 + ], + [ + 65.1408, + 65.1359 + ], + [ + 65.1408, + 66.7359 + ], + [ + 65.1408, + 68.3359 + ], + [ + 65.1408, + 69.9359 + ], + [ + 66.7408, + 7.5359 + ], + [ + 66.7408, + 9.1359 + ], + [ + 66.7408, + 10.7359 + ], + [ + 66.7408, + 12.3359 + ], + [ + 66.7408, + 13.9359 + ], + [ + 66.7408, + 15.5359 + ], + [ + 66.7408, + 17.1359 + ], + [ + 66.7408, + 18.7359 + ], + [ + 66.7408, + 20.3359 + ], + [ + 66.7408, + 21.9359 + ], + [ + 66.7408, + 23.5359 + ], + [ + 66.7408, + 25.1359 + ], + [ + 66.7408, + 31.5359 + ], + [ + 66.7408, + 33.1359 + ], + [ + 66.7408, + 34.7359 + ], + [ + 66.7408, + 36.3359 + ], + [ + 66.7408, + 37.9359 + ], + [ + 66.7408, + 39.5359 + ], + [ + 66.7408, + 41.1359 + ], + [ + 66.7408, + 42.7359 + ], + [ + 66.7408, + 58.7359 + ], + [ + 66.7408, + 60.3359 + ], + [ + 66.7408, + 61.9359 + ], + [ + 66.7408, + 66.7359 + ], + [ + 66.7408, + 68.3359 + ], + [ + 68.3408, + 7.5359 + ], + [ + 68.3408, + 9.1359 + ], + [ + 68.3408, + 10.7359 + ], + [ + 68.3408, + 12.3359 + ], + [ + 68.3408, + 13.9359 + ], + [ + 68.3408, + 15.5359 + ], + [ + 68.3408, + 17.1359 + ], + [ + 68.3408, + 18.7359 + ], + [ + 68.3408, + 20.3359 + ], + [ + 68.3408, + 21.9359 + ], + [ + 68.3408, + 23.5359 + ], + [ + 68.3408, + 25.1359 + ], + [ + 68.3408, + 31.5359 + ], + [ + 68.3408, + 33.1359 + ], + [ + 68.3408, + 34.7359 + ], + [ + 68.3408, + 36.3359 + ], + [ + 68.3408, + 37.9359 + ], + [ + 68.3408, + 39.5359 + ], + [ + 68.3408, + 41.1359 + ], + [ + 68.3408, + 42.7359 + ], + [ + 68.3408, + 44.3359 + ], + [ + 68.3408, + 58.7359 + ], + [ + 68.3408, + 60.3359 + ], + [ + 68.3408, + 61.9359 + ], + [ + 68.3408, + 66.7359 + ], + [ + 68.3408, + 68.3359 + ], + [ + 69.9408, + 9.1359 + ], + [ + 69.9408, + 10.7359 + ], + [ + 69.9408, + 12.3359 + ], + [ + 69.9408, + 13.9359 + ], + [ + 69.9408, + 15.5359 + ], + [ + 69.9408, + 17.1359 + ], + [ + 69.9408, + 18.7359 + ], + [ + 69.9408, + 20.3359 + ], + [ + 69.9408, + 21.9359 + ], + [ + 69.9408, + 23.5359 + ], + [ + 69.9408, + 25.1359 + ], + [ + 69.9408, + 31.5359 + ], + [ + 69.9408, + 33.1359 + ], + [ + 69.9408, + 34.7359 + ], + [ + 69.9408, + 36.3359 + ], + [ + 69.9408, + 37.9359 + ], + [ + 69.9408, + 39.5359 + ], + [ + 69.9408, + 41.1359 + ], + [ + 69.9408, + 42.7359 + ], + [ + 69.9408, + 44.3359 + ], + [ + 69.9408, + 60.3359 + ], + [ + 69.9408, + 61.9359 + ], + [ + 69.9408, + 63.5359 + ], + [ + 71.5408, + 7.5359 + ], + [ + 71.5408, + 9.1359 + ], + [ + 71.5408, + 10.7359 + ], + [ + 71.5408, + 12.3359 + ], + [ + 71.5408, + 13.9359 + ], + [ + 71.5408, + 18.7359 + ], + [ + 71.5408, + 20.3359 + ], + [ + 71.5408, + 21.9359 + ], + [ + 71.5408, + 23.5359 + ], + [ + 71.5408, + 25.1359 + ], + [ + 71.5408, + 29.9359 + ], + [ + 71.5408, + 31.5359 + ], + [ + 71.5408, + 34.7359 + ], + [ + 71.5408, + 36.3359 + ], + [ + 71.5408, + 37.9359 + ], + [ + 71.5408, + 39.5359 + ], + [ + 71.5408, + 41.1359 + ], + [ + 71.5408, + 42.7359 + ], + [ + 71.5408, + 44.3359 + ], + [ + 71.5408, + 58.7359 + ], + [ + 71.5408, + 60.3359 + ], + [ + 71.5408, + 61.9359 + ], + [ + 71.5408, + 63.5359 + ], + [ + 73.1408, + 7.5359 + ], + [ + 73.1408, + 9.1359 + ], + [ + 73.1408, + 10.7359 + ], + [ + 73.1408, + 12.3359 + ], + [ + 73.1408, + 21.9359 + ], + [ + 73.1408, + 23.5359 + ], + [ + 73.1408, + 25.1359 + ], + [ + 73.1408, + 29.9359 + ], + [ + 73.1408, + 34.7359 + ], + [ + 73.1408, + 36.3359 + ], + [ + 73.1408, + 37.9359 + ], + [ + 73.1408, + 39.5359 + ], + [ + 73.1408, + 41.1359 + ], + [ + 73.1408, + 42.7359 + ], + [ + 73.1408, + 44.3359 + ], + [ + 73.1408, + 58.7359 + ], + [ + 73.1408, + 60.3359 + ], + [ + 74.7408, + 7.5359 + ], + [ + 74.7408, + 9.1359 + ], + [ + 74.7408, + 10.7359 + ], + [ + 74.7408, + 12.3359 + ], + [ + 74.7408, + 21.9359 + ], + [ + 74.7408, + 23.5359 + ], + [ + 74.7408, + 25.1359 + ], + [ + 74.7408, + 29.9359 + ], + [ + 74.7408, + 34.7359 + ], + [ + 74.7408, + 36.3359 + ], + [ + 74.7408, + 37.9359 + ], + [ + 74.7408, + 39.5359 + ], + [ + 74.7408, + 41.1359 + ], + [ + 74.7408, + 42.7359 + ], + [ + 74.7408, + 44.3359 + ], + [ + 74.7408, + 58.7359 + ], + [ + 74.7408, + 60.3359 + ], + [ + 74.7408, + 61.9359 + ], + [ + 76.3408, + 9.1359 + ], + [ + 76.3408, + 10.7359 + ], + [ + 76.3408, + 21.9359 + ], + [ + 76.3408, + 23.5359 + ], + [ + 76.3408, + 25.1359 + ], + [ + 76.3408, + 29.9359 + ], + [ + 76.3408, + 34.7359 + ], + [ + 76.3408, + 36.3359 + ], + [ + 76.3408, + 37.9359 + ], + [ + 76.3408, + 39.5359 + ], + [ + 76.3408, + 41.1359 + ], + [ + 76.3408, + 44.3359 + ], + [ + 77.9408, + 20.3359 + ], + [ + 77.9408, + 21.9359 + ], + [ + 77.9408, + 23.5359 + ], + [ + 77.9408, + 25.1359 + ], + [ + 77.9408, + 36.3359 + ], + [ + 77.9408, + 37.9359 + ], + [ + 77.9408, + 39.5359 + ], + [ + 77.9408, + 44.3359 + ], + [ + 79.5408, + 20.3359 + ], + [ + 79.5408, + 21.9359 + ], + [ + 79.5408, + 23.5359 + ] + ] +} \ No newline at end of file diff --git a/backend/static/game_positions.json b/backend/static/game_positions.json new file mode 100644 index 0000000000000000000000000000000000000000..4647f35de66257b64f71fd7752efbd8160d40967 --- /dev/null +++ b/backend/static/game_positions.json @@ -0,0 +1,362 @@ +{ + "starting_positions": [ + { + "x": 25.57603645324707, + "y": 84.8847885131836, + "minerals": [ + { + "x": 19, + "y": 67 + }, + { + "x": 21, + "y": 67 + }, + { + "x": 21, + "y": 66 + }, + { + "x": 21, + "y": 69 + }, + { + "x": 19, + "y": 70 + }, + { + "x": 21, + "y": 71 + } + ], + "geysers": [ + { + "x": 22, + "y": 66 + } + ] + }, + { + "x": 85.71428680419922, + "y": 47.096771240234375, + "minerals": [ + { + "x": 72, + "y": 37 + }, + { + "x": 67, + "y": 36 + }, + { + "x": 68, + "y": 41 + }, + { + "x": 70, + "y": 37 + }, + { + "x": 66, + "y": 39 + }, + { + "x": 69, + "y": 41 + }, + { + "x": 70, + "y": 36 + } + ], + "geysers": [ + { + "x": 66, + "y": 37 + } + ] + }, + { + "x": 24.654376983642578, + "y": 13.80184268951416, + "minerals": [ + { + "x": 21, + "y": 8 + }, + { + "x": 22, + "y": 9 + }, + { + "x": 19, + "y": 10 + }, + { + "x": 21, + "y": 10 + }, + { + "x": 20, + "y": 13 + }, + { + "x": 22, + "y": 11 + }, + { + "x": 18, + "y": 12 + } + ], + "geysers": [ + { + "x": 22, + "y": 13 + } + ] + } + ], + "expansion_positions": [ + { + "x": 81.10598754882812, + "y": 17.373271942138672, + "minerals": [ + { + "x": 64, + "y": 11 + }, + { + "x": 66, + "y": 15 + }, + { + "x": 65, + "y": 16 + }, + { + "x": 66, + "y": 14 + }, + { + "x": 65, + "y": 15 + }, + { + "x": 63, + "y": 16 + }, + { + "x": 67, + "y": 13 + } + ], + "geysers": [ + { + "x": 67, + "y": 10 + } + ] + }, + { + "x": 54.838706970214844, + "y": 61.49769592285156, + "minerals": [ + { + "x": 42, + "y": 49 + }, + { + "x": 45, + "y": 52 + }, + { + "x": 44, + "y": 48 + }, + { + "x": 44, + "y": 50 + }, + { + "x": 45, + "y": 48 + }, + { + "x": 44, + "y": 46 + }, + { + "x": 43, + "y": 47 + } + ], + "geysers": [ + { + "x": 44, + "y": 53 + } + ] + }, + { + "x": 76.61289978027344, + "y": 83.38709259033203, + "minerals": [ + { + "x": 61, + "y": 68 + }, + { + "x": 61, + "y": 70 + }, + { + "x": 61, + "y": 69 + }, + { + "x": 59, + "y": 65 + }, + { + "x": 62, + "y": 64 + }, + { + "x": 60, + "y": 66 + }, + { + "x": 60, + "y": 70 + } + ], + "geysers": [ + { + "x": 65, + "y": 66 + } + ] + }, + { + "x": 20.967741012573242, + "y": 32.926265716552734, + "minerals": [ + { + "x": 19, + "y": 25 + }, + { + "x": 18, + "y": 27 + }, + { + "x": 20, + "y": 26 + }, + { + "x": 16, + "y": 26 + }, + { + "x": 14, + "y": 27 + }, + { + "x": 18, + "y": 28 + }, + { + "x": 17, + "y": 23 + } + ], + "geysers": [ + { + "x": 17, + "y": 23 + } + ] + }, + { + "x": 67.97235107421875, + "y": 36.036865234375, + "minerals": [ + { + "x": 56, + "y": 31 + }, + { + "x": 57, + "y": 29 + }, + { + "x": 54, + "y": 28 + }, + { + "x": 52, + "y": 30 + }, + { + "x": 54, + "y": 29 + }, + { + "x": 52, + "y": 27 + }, + { + "x": 53, + "y": 31 + } + ], + "geysers": [ + { + "x": 53, + "y": 33 + } + ] + }, + { + "x": 16.705068588256836, + "y": 57.926265716552734, + "minerals": [ + { + "x": 16, + "y": 46 + }, + { + "x": 15, + "y": 47 + }, + { + "x": 14, + "y": 46 + }, + { + "x": 14, + "y": 48 + }, + { + "x": 12, + "y": 49 + }, + { + "x": 10, + "y": 45 + }, + { + "x": 15, + "y": 44 + } + ], + "geysers": [ + { + "x": 15, + "y": 43 + } + ] + } + ] +} \ No newline at end of file diff --git a/backend/static/sprites/buildings/armory.png b/backend/static/sprites/buildings/armory.png new file mode 100644 index 0000000000000000000000000000000000000000..0a0970974cd2f80eb30e0e7773d719e63bdf8831 --- /dev/null +++ b/backend/static/sprites/buildings/armory.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a864f8a0c9d06bd7c8db6ea810c188abcc6a667368e7f19526bebef827ce0751 +size 22413 diff --git a/backend/static/sprites/buildings/barracks.png b/backend/static/sprites/buildings/barracks.png new file mode 100644 index 0000000000000000000000000000000000000000..ad3a0da10eccfb4d1092c81beb020d93e30f4a7c --- /dev/null +++ b/backend/static/sprites/buildings/barracks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87156d68975712a50f388ab6435ebd158742138cc14c004cb22a82257ba94b89 +size 21224 diff --git a/backend/static/sprites/buildings/command_center.png b/backend/static/sprites/buildings/command_center.png index 108975fe3558e84a5262e949ec5d00f914e8025d..e8aa590b04dc5a5968ad8d97dc2bf77d3fd7bf03 100644 --- a/backend/static/sprites/buildings/command_center.png +++ b/backend/static/sprites/buildings/command_center.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8ca0874151eddb85d0e71fe0b2f43c57c9d4af1eae3241ce160f21572c42e18 -size 182555 +oid sha256:9e50ecac5ec9944b049b4cf7cbbcad8995f76ad07748084880317dd28acc4be3 +size 21473 diff --git a/backend/static/sprites/buildings/engineering_bay.png b/backend/static/sprites/buildings/engineering_bay.png new file mode 100644 index 0000000000000000000000000000000000000000..de462740ca8c7a0d9b5bdf4eace1ebe44ec1962e --- /dev/null +++ b/backend/static/sprites/buildings/engineering_bay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91d85a6e9aa63ff690bc7b711eb5165a0e20be275a5808bc62d41cb2d4abfe2e +size 20862 diff --git a/backend/static/sprites/buildings/factory.png b/backend/static/sprites/buildings/factory.png new file mode 100644 index 0000000000000000000000000000000000000000..b065fef4900d31ab6c54f5902a9a184c873d2583 --- /dev/null +++ b/backend/static/sprites/buildings/factory.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb67a56aa740b4fce5d2e240047d2ce2566272f04e9752ec7da4ce0d9915ff06 +size 21551 diff --git a/backend/static/sprites/buildings/refinery.png b/backend/static/sprites/buildings/refinery.png new file mode 100644 index 0000000000000000000000000000000000000000..87bb12f301d868429b50819e7d7f742c62e3b6cf --- /dev/null +++ b/backend/static/sprites/buildings/refinery.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2760e6faf9f737645b646f665ed9ca73a88b432dc82444732ac1278ef0cec21 +size 28224 diff --git a/backend/static/sprites/buildings/starport.png b/backend/static/sprites/buildings/starport.png new file mode 100644 index 0000000000000000000000000000000000000000..2caf4fb16d5509a10b42748bd71609a48ae3e6b2 --- /dev/null +++ b/backend/static/sprites/buildings/starport.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fb25bc20f5cf03c712c3facbee5289d0750e56a705ca3907edf1f7190b09c20 +size 19755 diff --git a/backend/static/sprites/buildings/supply_depot.png b/backend/static/sprites/buildings/supply_depot.png new file mode 100644 index 0000000000000000000000000000000000000000..b5110690e69321a2a4f9bbc341797dba62f43634 --- /dev/null +++ b/backend/static/sprites/buildings/supply_depot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e366256fbd2f59a4a99d71b820c6e7f63cc0acbf5404c7e7f52c683605c3b485 +size 26698 diff --git a/backend/static/sprites/icons/gas.png b/backend/static/sprites/icons/gas.png new file mode 100644 index 0000000000000000000000000000000000000000..d2efb96f248c3b48a1e16a013bec874a8b8877fb --- /dev/null +++ b/backend/static/sprites/icons/gas.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bf2bccce9a1d4c2f86f0543439c040c92fe07a41adb8f286da437d863266924 +size 12864 diff --git a/backend/static/sprites/icons/mineral.png b/backend/static/sprites/icons/mineral.png new file mode 100644 index 0000000000000000000000000000000000000000..621d24f0f827bec595b861e8072ce98447abf77a --- /dev/null +++ b/backend/static/sprites/icons/mineral.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b79480507cbaca437a65c1b437b516a3cdc72c89af6601c4436902b5e132a339 +size 18574 diff --git a/backend/static/sprites/icons/supply.png b/backend/static/sprites/icons/supply.png new file mode 100644 index 0000000000000000000000000000000000000000..2395c82cc48cb3337766e4fcc63bb0f31bab3084 --- /dev/null +++ b/backend/static/sprites/icons/supply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:276dd6a366c22137229030a50100ff956d47b4c45b096b12c350894badd576db +size 12556 diff --git a/backend/static/sprites/resources/geyser.png b/backend/static/sprites/resources/geyser.png new file mode 100644 index 0000000000000000000000000000000000000000..d973c82b6e38469fa8a64d24f9254b8936f2fe09 --- /dev/null +++ b/backend/static/sprites/resources/geyser.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2998e671151cabb5b9371194f04c3a9db38f7179b3ce61dded269960f85a26c0 +size 28305 diff --git a/backend/static/sprites/resources/mineral.png b/backend/static/sprites/resources/mineral.png new file mode 100644 index 0000000000000000000000000000000000000000..ccb18ec444972883c1ec5d6d475f7b61234938fd --- /dev/null +++ b/backend/static/sprites/resources/mineral.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a5fb03684e78fcdabe5806198d2efd51705c011a3d1938af28a502cf1b3efc8 +size 28221 diff --git a/backend/static/sprites/units/goliath.png b/backend/static/sprites/units/goliath.png new file mode 100644 index 0000000000000000000000000000000000000000..9407bb2901e206a8ff417410ac7beda3a908c701 --- /dev/null +++ b/backend/static/sprites/units/goliath.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abc2fd6c5b1ec2c60995fe34d022bf02c1c911a37fd3c1ae656f473f63ff3ab6 +size 21803 diff --git a/backend/static/sprites/units/marine.png b/backend/static/sprites/units/marine.png index f3ed7f0835d73adcc41a3abb9666507a99f921d2..231339b2e1d190f6de1244f895dfa6fdcad4bcde 100644 --- a/backend/static/sprites/units/marine.png +++ b/backend/static/sprites/units/marine.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13aa0977f92a48719af480774df540612aeb2efdf943273ae634de85d9731901 -size 58962 +oid sha256:909a6cdff856fe47b49f19fbbd12ced39215cc1482f119aa3a07cac392b7df4f +size 21449 diff --git a/backend/static/sprites/units/medic.png b/backend/static/sprites/units/medic.png new file mode 100644 index 0000000000000000000000000000000000000000..87f1630f801abe8e1dbb2479141570fdd6ae3687 --- /dev/null +++ b/backend/static/sprites/units/medic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7235289521de24882286b2ed2cd7abcebe7a6bec1d77a902b454ea87c4b936ac +size 19266 diff --git a/backend/static/sprites/units/scv.png b/backend/static/sprites/units/scv.png index 9eaf0244173cf859c9ab015d8e95567413d21919..e9e1cefa0bd274416155da18560f43ca56ad6c1f 100644 --- a/backend/static/sprites/units/scv.png +++ b/backend/static/sprites/units/scv.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c57e3bf925b6cba56b386a1c9a356114c63661c0d4b1b85ddf6e996bbe60d4b -size 118001 +oid sha256:158eb323342eb035a859caef8be75d616cb01b75dc09889ab83cf4bd94b6f3b5 +size 18270 diff --git a/backend/static/sprites/units/tank.png b/backend/static/sprites/units/tank.png new file mode 100644 index 0000000000000000000000000000000000000000..64efc0e32fb04d1fa8fa58fac03dce8010e59672 --- /dev/null +++ b/backend/static/sprites/units/tank.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8171e59c411e8fd9c9b5f2ac3f44d78fb58fa0e20d337065e28172a6cd610ba +size 21612 diff --git a/backend/static/sprites/units/wraith.png b/backend/static/sprites/units/wraith.png new file mode 100644 index 0000000000000000000000000000000000000000..e6f6dfa1718300c10365821630feb578d4ea4914 --- /dev/null +++ b/backend/static/sprites/units/wraith.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a8b7cc137c6ab6ca68b42d7cdaff6aae77685b66e692f2fa03b006be2bccc84 +size 22245 diff --git a/backend/static/walkable.json b/backend/static/walkable.json index 254a90762df56cc1c00ce93d83af80e8769d5337..4d83bab5a38dfbd46e49ec8b7806f3621515af56 100644 --- a/backend/static/walkable.json +++ b/backend/static/walkable.json @@ -356,12 +356,12 @@ 12.373571395874023 ], [ - 83.61827850341797, - 8.538786888122559 + 84.77071380615234, + 7.241750717163086 ], [ - 86.0469741821289, - 10.200526237487793 + 87.53814697265625, + 9.529439926147461 ], [ 91.03219604492188, @@ -380,8 +380,8 @@ 20.17096710205078 ], [ - 90.77654266357422, - 22.727489471435547 + 90.1014633178711, + 22.626026153564453 ], [ 88.7313232421875, @@ -392,24 +392,24 @@ 26.690099716186523 ], [ - 96.65654754638672, - 25.02836036682129 + 100, + 24.53423309326172 ], [ - 98.57393646240234, - 30.141407012939453 + 100, + 30.92753791809082 ], [ - 92.18263244628906, - 31.547494888305664 + 92.45945739746094, + 32.453460693359375 ], [ - 83.74610137939453, - 31.164016723632812 + 84.401611328125, + 31.450428009033203 ], [ - 77.35479736328125, - 32.18662643432617 + 77.79100799560547, + 33.05105209350586 ], [ 72.62522888183594, @@ -466,12 +466,12 @@ 56.21794128417969 ], [ - 28.141727447509766, - 49.187503814697266 + 29.418794631958008, + 49.158748626708984 ], [ - 25.329551696777344, - 46.375328063964844 + 24.801074981689453, + 45.830467224121094 ], [ 16.765199661254883, @@ -532,8 +532,8 @@ 74.11360168457031 ], [ - 77.35479736328125, - 70.40664672851562 + 79.2590103149414, + 69.9880142211914 ], [ 86.55828094482422, @@ -564,8 +564,8 @@ 77.30925750732422 ], [ - 88.22001647949219, - 80.24925994873047 + 88.57514953613281, + 81.07293701171875 ], [ 79.01653289794922, @@ -622,8 +622,8 @@ 65.54924774169922 ], [ - 47.18782424926758, - 67.08316802978516 + 47.70072937011719, + 67.28649139404297 ], [ 41.69129943847656, @@ -638,20 +638,20 @@ 80.88838958740234 ], [ - 45.90956115722656, - 84.59535217285156 + 46.48517608642578, + 85.19413757324219 ], [ - 42.33042907714844, - 88.43013000488281 + 41.934173583984375, + 88.30909729003906 ], [ 51.15043640136719, 95.20491790771484 ], [ - 58.18087387084961, - 94.4379653930664 + 58.30870056152344, + 94.26060485839844 ], [ 62.14348220825195, @@ -730,8 +730,8 @@ 73.98577880859375 ], [ - 43.99216842651367, - 65.03794860839844 + 43.087799072265625, + 65.17256164550781 ] ], [ diff --git a/backend/voice/command_parser.py b/backend/voice/command_parser.py index 32c492553bb911c2ea34cc738098552a3d2af8dc..3277773a87f03438137eadf12f2c77bb5f68a7ec 100644 --- a/backend/voice/command_parser.py +++ b/backend/voice/command_parser.py @@ -50,7 +50,7 @@ supply_depot, barracks, engineering_bay, refinery, factory, armory, starport all, all_military, all_marines, all_medics, all_goliaths, all_tanks, all_wraiths, all_scv, idle_scv, most_damaged === ZONES CIBLES === -my_base, enemy_base, center, top_left, top_right, bottom_left, bottom_right, front_line +my_base, enemy_base, center, top_left, top_right, bottom_left, bottom_right, front_line{resource_zones} === ÉTAT ACTUEL DU JOUEUR === {player_state} @@ -58,9 +58,9 @@ my_base, enemy_base, center, top_left, top_right, bottom_left, bottom_right, fro === CONSIGNES === - Réponds UNIQUEMENT avec un JSON valide, aucun texte avant ou après. - Une commande peut générer PLUSIEURS actions (ex: "entraîne 4 marines et attaque la base"). -- Le champ "feedback" est un message en français à lire au joueur pour confirmer l'action. -- Si la commande est incompréhensible, génère une action de type "query" avec un feedback explicatif. -- Si le joueur demande son état / ses ressources, utilise l'action "query". +- Le champ "feedback_template" est une phrase courte DANS LA MÊME LANGUE que la commande, avec des placeholders pour les infos du jeu. Placeholders possibles: {n} (nombre d'unités), {zone}, {building}, {count}, {unit}, {resource}, {summary}, {mode}, {names}, {raw}. Ex: "Déplacement de {n} unités vers {zone}." ou "État: {summary}". +- Ajoute le champ "language" avec le code ISO de la langue: "fr", "en". +- Si la commande est incompréhensible, génère une action "query" avec feedback_template explicatif (ex: "Je n'ai pas compris. Voici ton état: {summary}"). === FORMAT DE RÉPONSE === { @@ -72,26 +72,41 @@ my_base, enemy_base, center, top_left, top_right, bottom_left, bottom_right, fro "count": , "unit_selector": "", "target_zone": "", - "resource_type": "", + "resource_type": "<'minerals' ou 'gas', omis sinon>", "group_index": <1, 2 ou 3 pour assign_to_group, omis sinon>, - "unit_ids": [] + "unit_ids": [] } ], - "feedback": "" + "feedback_template": "", + "language": "fr" } """ -async def parse(transcription: str, player: PlayerState) -> ParsedCommand: +async def parse( + transcription: str, + player: PlayerState, + resource_zones: list[str] | None = None, +) -> ParsedCommand: """ Send transcription + player state to Mistral and return parsed command. Falls back to a query action if parsing fails. + resource_zones: list of zone names like ["mineral_1", ..., "geyser_1", ...] + sorted by proximity to the player's base. """ if not MISTRAL_API_KEY: raise RuntimeError("MISTRAL_API_KEY not set") client = Mistral(api_key=MISTRAL_API_KEY) - system = _SYSTEM_PROMPT.replace("{player_state}", player.summary()) + if resource_zones: + rz_str = ", " + ", ".join(resource_zones) + else: + rz_str = "" + system = ( + _SYSTEM_PROMPT + .replace("{player_state}", player.summary()) + .replace("{resource_zones}", rz_str) + ) response = await client.chat.complete_async( model=MISTRAL_CHAT_MODEL, @@ -109,13 +124,64 @@ async def parse(transcription: str, player: PlayerState) -> ParsedCommand: try: data = json.loads(raw) actions = [GameAction(**a) for a in data.get("actions", [])] - feedback = data.get("feedback", "Commande reçue.") + language = (data.get("language") or "fr").strip().lower() or "fr" + if language not in ("fr", "en"): + language = "fr" + feedback_template = data.get("feedback_template") or data.get("feedback") or "{summary}" if not actions: raise ValueError("Empty actions list") - return ParsedCommand(actions=actions, feedback=feedback) + return ParsedCommand( + actions=actions, feedback_template=feedback_template, language=language + ) except Exception as exc: log.warning("Failed to parse Mistral response: %s — %s", exc, raw[:200]) + lang = _detect_language(transcription) + fallback_template = ( + "I didn't understand. Here is your status: {summary}" + if lang == "en" + else "Je n'ai pas compris. Voici ton état: {summary}" + ) return ParsedCommand( actions=[GameAction(type=ActionType.QUERY)], - feedback="Je n'ai pas compris cette commande. Voici ton état actuel.", + feedback_template=fallback_template, + language=lang, ) + + +def _detect_language(text: str) -> str: + """Heuristic: if transcription looks like English, return 'en', else 'fr'.""" + if not text or not text.strip(): + return "fr" + lower = text.lower().strip() + en_words = { + "build", "train", "attack", "move", "send", "create", "gather", "stop", + "patrol", "go", "select", "unit", "units", "base", "minerals", "gas", + "barracks", "factory", "scv", "marine", "tank", "all", "my", "the", + } + words = set(lower.split()) + if words & en_words: + return "en" + return "fr" + + +async def generate_feedback(error_key: str, language: str) -> str: + """ + Call the API to generate a short feedback message for an error, in the given language. + error_key: e.g. "game_not_in_progress" + """ + if not MISTRAL_API_KEY: + return "Game is not in progress." if language == "en" else "La partie n'est pas en cours." + + client = Mistral(api_key=MISTRAL_API_KEY) + prompt = f"""Generate exactly one short sentence for a strategy game feedback. +Context/error: {error_key}. +Language: {language}. +Reply with only that sentence, no quotes, no explanation.""" + response = await client.chat.complete_async( + model=MISTRAL_CHAT_MODEL, + messages=[{"role": "user", "content": prompt}], + temperature=0.2, + max_tokens=80, + ) + text = (response.choices[0].message.content or "").strip() + return text or ("Game is not in progress." if language == "en" else "La partie n'est pas en cours.") diff --git a/frontend/.svelte-kit/non-ambient.d.ts b/frontend/.svelte-kit/non-ambient.d.ts index 7a2fff52165f1a22b5f6c8625872c75d6723ae00..c3dbf4c2e677dfe2fb4055f2540d541db3748e09 100644 --- a/frontend/.svelte-kit/non-ambient.d.ts +++ b/frontend/.svelte-kit/non-ambient.d.ts @@ -27,20 +27,21 @@ export {}; declare module "$app/types" { export interface AppTypes { - RouteId(): "/" | "/admin" | "/admin/map" | "/admin/sounds" | "/admin/sprites" | "/game"; + RouteId(): "/" | "/admin" | "/admin/compiled-map" | "/admin/map" | "/admin/sounds" | "/admin/sprites" | "/game"; RouteParams(): { }; LayoutParams(): { "/": Record; "/admin": Record; + "/admin/compiled-map": Record; "/admin/map": Record; "/admin/sounds": Record; "/admin/sprites": Record; "/game": Record }; - Pathname(): "/" | "/admin/map" | "/admin/sounds" | "/admin/sprites" | "/game"; + Pathname(): "/" | "/admin/compiled-map" | "/admin/map" | "/admin/sounds" | "/admin/sprites" | "/game"; ResolvedPathname(): `${"" | `/${string}`}${ReturnType}`; - Asset(): string & {}; + Asset(): "/logos/elevenlabs.svg" | "/logos/huggingface.svg" | "/logos/mistral.svg" | string & {}; } } \ No newline at end of file diff --git a/frontend/.svelte-kit/types/route_meta_data.json b/frontend/.svelte-kit/types/route_meta_data.json index bbd4cbb34e4a608abfa34585d8d4683cf3fabe42..d28d4fb5f045c64cc26f50757a81806bf5b54090 100644 --- a/frontend/.svelte-kit/types/route_meta_data.json +++ b/frontend/.svelte-kit/types/route_meta_data.json @@ -2,6 +2,10 @@ "/": [ "src/routes/+page.js" ], + "/admin": [], + "/admin/compiled-map": [ + "src/routes/admin/compiled-map/+page.ts" + ], "/admin/map": [ "src/routes/admin/map/+page.ts" ], diff --git a/frontend/.svelte-kit/types/src/routes/$types.d.ts b/frontend/.svelte-kit/types/src/routes/$types.d.ts index 408fb34f693211def72257e73a3221939543463c..19a748459800e6b893c987a9dfb96e4cc0f8b162 100644 --- a/frontend/.svelte-kit/types/src/routes/$types.d.ts +++ b/frontend/.svelte-kit/types/src/routes/$types.d.ts @@ -12,7 +12,7 @@ type EnsureDefined = T extends null | undefined ? {} : T; type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; export type Snapshot = Kit.Snapshot; type PageParentData = EnsureDefined; -type LayoutRouteId = RouteId | "/" | "/admin/map" | "/admin/sounds" | "/admin/sprites" | "/game" | null +type LayoutRouteId = RouteId | "/" | "/admin/compiled-map" | "/admin/map" | "/admin/sounds" | "/admin/sprites" | "/game" | null type LayoutParams = RouteParams & { } type LayoutParentData = EnsureDefined<{}>; diff --git a/frontend/.svelte-kit/types/src/routes/admin/$types.d.ts b/frontend/.svelte-kit/types/src/routes/admin/$types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c2f3d7dd80862257b2f0234da65d4b2d1dc0bc6 --- /dev/null +++ b/frontend/.svelte-kit/types/src/routes/admin/$types.d.ts @@ -0,0 +1,20 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +// @ts-ignore +type MatcherParam = M extends (param : string) => param is infer U ? U extends string ? U : string : string; +type RouteParams = { }; +type RouteId = '/admin'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type LayoutRouteId = RouteId | "/admin/compiled-map" | "/admin/map" | "/admin/sounds" | "/admin/sprites" +type LayoutParams = RouteParams & { } +type LayoutParentData = EnsureDefined; + +export type LayoutServerData = null; +export type LayoutData = Expand; +export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet } \ No newline at end of file diff --git a/frontend/.svelte-kit/types/src/routes/admin/compiled-map/$types.d.ts b/frontend/.svelte-kit/types/src/routes/admin/compiled-map/$types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc86c95e0c54553f288e98bda9350cdc62a2b535 --- /dev/null +++ b/frontend/.svelte-kit/types/src/routes/admin/compiled-map/$types.d.ts @@ -0,0 +1,20 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +// @ts-ignore +type MatcherParam = M extends (param : string) => param is infer U ? U extends string ? U : string : string; +type RouteParams = { }; +type RouteId = '/admin/compiled-map'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type PageParentData = Omit, keyof import('../$types.js').LayoutData> & EnsureDefined; + +export type PageServerData = null; +export type PageLoad = OutputDataShape> = Kit.Load; +export type PageLoadEvent = Parameters[0]; +export type PageData = Expand> & OptionalUnion>>>; +export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file diff --git a/frontend/.svelte-kit/types/src/routes/admin/map/$types.d.ts b/frontend/.svelte-kit/types/src/routes/admin/map/$types.d.ts index 1d24c84e4f042225c6c30e4b9106f067276ebf6f..9555429230a7a52185517dc15789ad1318d32218 100644 --- a/frontend/.svelte-kit/types/src/routes/admin/map/$types.d.ts +++ b/frontend/.svelte-kit/types/src/routes/admin/map/$types.d.ts @@ -11,7 +11,7 @@ type OutputDataShape = MaybeWithVoid> & Pa type EnsureDefined = T extends null | undefined ? {} : T; type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; export type Snapshot = Kit.Snapshot; -type PageParentData = EnsureDefined; +type PageParentData = Omit, keyof import('../$types.js').LayoutData> & EnsureDefined; export type PageServerData = null; export type PageLoad = OutputDataShape> = Kit.Load; diff --git a/frontend/.svelte-kit/types/src/routes/admin/sounds/$types.d.ts b/frontend/.svelte-kit/types/src/routes/admin/sounds/$types.d.ts index 7d232feb60d31be66935eb814de824b1ea6da76a..70e47d97d671c2e0a60c887136fdaed6dffe51a6 100644 --- a/frontend/.svelte-kit/types/src/routes/admin/sounds/$types.d.ts +++ b/frontend/.svelte-kit/types/src/routes/admin/sounds/$types.d.ts @@ -11,7 +11,7 @@ type OutputDataShape = MaybeWithVoid> & Pa type EnsureDefined = T extends null | undefined ? {} : T; type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; export type Snapshot = Kit.Snapshot; -type PageParentData = EnsureDefined; +type PageParentData = Omit, keyof import('../$types.js').LayoutData> & EnsureDefined; export type PageServerData = null; export type PageData = Expand; diff --git a/frontend/.svelte-kit/types/src/routes/admin/sprites/$types.d.ts b/frontend/.svelte-kit/types/src/routes/admin/sprites/$types.d.ts index 22e734e990059958dca62eebd621de912308f18b..07fa90a006f29c1b3af26b81ae3cdf411c32358c 100644 --- a/frontend/.svelte-kit/types/src/routes/admin/sprites/$types.d.ts +++ b/frontend/.svelte-kit/types/src/routes/admin/sprites/$types.d.ts @@ -11,7 +11,7 @@ type OutputDataShape = MaybeWithVoid> & Pa type EnsureDefined = T extends null | undefined ? {} : T; type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; export type Snapshot = Kit.Snapshot; -type PageParentData = EnsureDefined; +type PageParentData = Omit, keyof import('../$types.js').LayoutData> & EnsureDefined; export type PageServerData = null; export type PageData = Expand; diff --git a/frontend/build/_app/immutable/assets/2.BFuSHcwB.css b/frontend/build/_app/immutable/assets/2.BFuSHcwB.css new file mode 100644 index 0000000000000000000000000000000000000000..a26352d065150885bb74ab50fe9a5d908951f6b3 --- /dev/null +++ b/frontend/build/_app/immutable/assets/2.BFuSHcwB.css @@ -0,0 +1 @@ +.lobby.svelte-my9qcz.svelte-my9qcz{--lobby-bg:#0f1420;--lobby-surface:#161c2a;--lobby-surface2:#1e2638;--lobby-border:#2a3550;--lobby-accent-cyan:#00c8ff;--lobby-accent-orange:#ff6b35;--lobby-text:#e8ecf4;--lobby-text-muted:#7d8ba8}.admin-links.svelte-my9qcz.svelte-my9qcz{margin-top:auto;padding-top:24px;font-size:.8rem}.admin-links.svelte-my9qcz a.svelte-my9qcz{color:var(--lobby-text-muted);margin:0 8px}.admin-links.svelte-my9qcz a.svelte-my9qcz:hover{color:var(--lobby-accent-cyan)}.lobby.svelte-my9qcz.svelte-my9qcz{min-height:100dvh;display:flex;flex-direction:column;align-items:center;gap:20px;padding:0 20px 32px;max-width:480px;margin:0 auto;background:var(--lobby-bg)}.hero.svelte-my9qcz.svelte-my9qcz{width:100vw;max-width:100%;margin:0 -20px 8px;position:relative;overflow:hidden}.hero-cover.svelte-my9qcz.svelte-my9qcz{width:100%;height:auto;max-height:42vh;object-fit:cover;object-position:center top;display:block}.hero.svelte-my9qcz .tagline.svelte-my9qcz{position:absolute;bottom:12px;left:50%;transform:translate(-50%);color:#ffffffe6;font-size:.8rem;text-shadow:0 1px 4px rgba(0,0,0,.8);white-space:nowrap}.tagline.svelte-my9qcz.svelte-my9qcz{color:var(--lobby-text-muted);font-size:.85rem}.card.svelte-my9qcz.svelte-my9qcz{width:100%;background:var(--lobby-surface);border:1px solid var(--lobby-border);border-radius:var(--radius-lg);padding:20px}.center.svelte-my9qcz.svelte-my9qcz{display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center;padding:40px 20px}.field-label.svelte-my9qcz.svelte-my9qcz{display:block;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--lobby-text-muted);margin-bottom:8px}.text-input.svelte-my9qcz.svelte-my9qcz{width:100%;background:var(--lobby-bg);border:1px solid var(--lobby-border);border-radius:var(--radius);padding:12px 14px;font-size:1rem;color:var(--lobby-text);outline:none;transition:border-color .15s,box-shadow .15s}.text-input.svelte-my9qcz.svelte-my9qcz:focus{border-color:var(--lobby-accent-cyan);box-shadow:0 0 0 2px #00c8ff33}.code-input.svelte-my9qcz.svelte-my9qcz{text-transform:uppercase;letter-spacing:.2em;font-weight:700;font-size:1.1rem;flex:1}.actions.svelte-my9qcz.svelte-my9qcz{width:100%;display:flex;flex-direction:column;gap:10px}.join-row.svelte-my9qcz.svelte-my9qcz{display:flex;gap:8px;align-items:stretch}.btn.svelte-my9qcz.svelte-my9qcz{display:flex;align-items:center;justify-content:center;gap:8px;padding:14px 20px;border-radius:var(--radius-lg);font-size:.95rem;font-weight:600;transition:all .15s;width:100%}.btn-sm.svelte-my9qcz.svelte-my9qcz{padding:12px 16px;width:auto;flex-shrink:0}.btn-large.svelte-my9qcz.svelte-my9qcz{padding:18px 20px;font-size:1.1rem}.btn-icon.svelte-my9qcz.svelte-my9qcz{font-size:1.1rem}.btn-primary.svelte-my9qcz.svelte-my9qcz{background:linear-gradient(135deg,var(--lobby-accent-orange),#e55a2b);color:#fff;box-shadow:0 0 20px #ff6b354d}.btn-primary.svelte-my9qcz.svelte-my9qcz:hover{filter:brightness(1.1);box-shadow:0 0 24px #ff6b3566}.btn-primary.svelte-my9qcz.svelte-my9qcz:active{filter:brightness(.9)}.btn-secondary.svelte-my9qcz.svelte-my9qcz{background:var(--lobby-surface2);border:1px solid var(--lobby-border);color:var(--lobby-text)}.btn-secondary.svelte-my9qcz.svelte-my9qcz:hover{border-color:var(--lobby-accent-cyan);color:var(--lobby-accent-cyan)}.btn-ghost.svelte-my9qcz.svelte-my9qcz{background:transparent;border:1px solid var(--lobby-border);color:var(--lobby-text-muted)}.btn-ghost.svelte-my9qcz.svelte-my9qcz:hover{color:var(--lobby-accent-cyan);border-color:var(--lobby-accent-cyan)}.room-code-block.svelte-my9qcz.svelte-my9qcz{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.room-code-label.svelte-my9qcz.svelte-my9qcz{font-size:.75rem;color:var(--lobby-text-muted);text-transform:uppercase;letter-spacing:.08em}.room-code.svelte-my9qcz.svelte-my9qcz{font-family:var(--font-mono);font-size:1.5rem;font-weight:700;letter-spacing:.15em;color:var(--lobby-accent-cyan);flex:1}.copy-btn.svelte-my9qcz.svelte-my9qcz{padding:6px 12px;border-radius:var(--radius);background:var(--lobby-surface2);border:1px solid var(--lobby-border);font-size:.8rem;color:var(--lobby-text-muted);cursor:pointer}.copy-btn.svelte-my9qcz.svelte-my9qcz:hover{border-color:var(--lobby-accent-cyan);color:var(--lobby-accent-cyan)}.copy-btn.svelte-my9qcz.svelte-my9qcz:active{opacity:.7}.players-list.svelte-my9qcz.svelte-my9qcz{width:100%;display:flex;flex-direction:column;gap:8px}.player-row.svelte-my9qcz.svelte-my9qcz{display:flex;align-items:center;gap:8px;padding:14px 16px;background:var(--lobby-surface);border:1px solid var(--lobby-border);border-radius:var(--radius-lg)}.player-row.is-you.svelte-my9qcz.svelte-my9qcz{border-color:var(--lobby-accent-cyan)}.player-name.svelte-my9qcz.svelte-my9qcz{flex:1;font-weight:600}.player-placeholder.svelte-my9qcz.svelte-my9qcz{opacity:.5}.player-placeholder.svelte-my9qcz .text-muted.svelte-my9qcz{color:var(--lobby-text-muted)}.badge.svelte-my9qcz.svelte-my9qcz{padding:3px 8px;border-radius:99px;font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.badge-you.svelte-my9qcz.svelte-my9qcz{background:#00c8ff33;color:var(--lobby-accent-cyan)}.badge-ready.svelte-my9qcz.svelte-my9qcz{background:#3fb95033;color:var(--success)}.badge-waiting.svelte-my9qcz.svelte-my9qcz{background:#7d8ba833;color:var(--lobby-text-muted)}.status-text.svelte-my9qcz.svelte-my9qcz{color:var(--lobby-text);font-size:1rem;font-weight:500}.status-muted.svelte-my9qcz.svelte-my9qcz{color:var(--lobby-text-muted);font-size:.85rem}.error-toast.svelte-my9qcz.svelte-my9qcz{width:100%;background:#f8514926;border:1px solid var(--danger);color:var(--danger);border-radius:var(--radius);padding:10px 14px;font-size:.9rem;text-align:center}.spinner.svelte-my9qcz.svelte-my9qcz{width:36px;height:36px;border:3px solid var(--lobby-border);border-top-color:var(--lobby-accent-cyan);border-radius:50%;animation:svelte-my9qcz-spin .8s linear infinite}@keyframes svelte-my9qcz-spin{to{transform:rotate(360deg)}}.countdown.svelte-my9qcz.svelte-my9qcz{font-weight:700;color:var(--lobby-accent-cyan);font-variant-numeric:tabular-nums}.countdown-hint.svelte-my9qcz.svelte-my9qcz{text-align:center;font-size:.85rem}.bot-icon.svelte-my9qcz.svelte-my9qcz{font-size:2.5rem}.bot-offer-card.svelte-my9qcz.svelte-my9qcz{width:100%;display:flex;align-items:center;gap:12px;padding:16px;background:var(--lobby-surface);border:1px solid var(--lobby-accent-cyan);border-radius:var(--radius-lg);animation:svelte-my9qcz-fadeIn .3s ease;box-shadow:0 0 20px #00c8ff26}.bot-offer-icon.svelte-my9qcz.svelte-my9qcz{font-size:1.8rem;flex-shrink:0}.bot-offer-text.svelte-my9qcz.svelte-my9qcz{flex:1;font-size:.9rem;font-weight:500}.bot-offer-text.svelte-my9qcz p.svelte-my9qcz{margin:0}@keyframes svelte-my9qcz-fadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.dot-pulse.svelte-my9qcz.svelte-my9qcz{display:flex;gap:4px;align-items:center}.dot-pulse.svelte-my9qcz span.svelte-my9qcz{width:6px;height:6px;background:var(--lobby-accent-cyan);border-radius:50%;animation:svelte-my9qcz-pulse 1.2s ease-in-out infinite}.dot-pulse.svelte-my9qcz span.svelte-my9qcz:nth-child(2){animation-delay:.2s}.dot-pulse.svelte-my9qcz span.svelte-my9qcz:nth-child(3){animation-delay:.4s}@keyframes svelte-my9qcz-pulse{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}} diff --git a/frontend/build/_app/immutable/assets/2.BpyLQ8v8.css b/frontend/build/_app/immutable/assets/2.BpyLQ8v8.css deleted file mode 100644 index 022367d3dd32d0b11cfa0711a6991a8d6bf7c81e..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/assets/2.BpyLQ8v8.css +++ /dev/null @@ -1 +0,0 @@ -.lobby.svelte-cigodj.svelte-cigodj{min-height:100dvh;display:flex;flex-direction:column;align-items:center;gap:20px;padding:40px 20px 32px;max-width:440px;margin:0 auto}.lobby-header.svelte-cigodj.svelte-cigodj{text-align:center;padding:8px 0}.logo.svelte-cigodj.svelte-cigodj{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:6px}.logo-icon.svelte-cigodj.svelte-cigodj{font-size:2rem}.logo-text.svelte-cigodj.svelte-cigodj{font-size:2rem;font-weight:800;letter-spacing:-1px;background:linear-gradient(135deg,var(--player),#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.tagline.svelte-cigodj.svelte-cigodj{color:var(--text-muted);font-size:.85rem}.card.svelte-cigodj.svelte-cigodj{width:100%;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px}.center.svelte-cigodj.svelte-cigodj{display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center;padding:40px 20px}.field-label.svelte-cigodj.svelte-cigodj{display:block;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-bottom:8px}.text-input.svelte-cigodj.svelte-cigodj{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;font-size:1rem;color:var(--text);outline:none;transition:border-color .15s}.text-input.svelte-cigodj.svelte-cigodj:focus{border-color:var(--border-focus)}.code-input.svelte-cigodj.svelte-cigodj{text-transform:uppercase;letter-spacing:.2em;font-weight:700;font-size:1.1rem;flex:1}.actions.svelte-cigodj.svelte-cigodj{width:100%;display:flex;flex-direction:column;gap:10px}.join-row.svelte-cigodj.svelte-cigodj{display:flex;gap:8px;align-items:stretch}.btn.svelte-cigodj.svelte-cigodj{display:flex;align-items:center;justify-content:center;gap:8px;padding:14px 20px;border-radius:var(--radius-lg);font-size:.95rem;font-weight:600;transition:all .15s;width:100%}.btn-sm.svelte-cigodj.svelte-cigodj{padding:12px 16px;width:auto;flex-shrink:0}.btn-large.svelte-cigodj.svelte-cigodj{padding:18px 20px;font-size:1.1rem}.btn-icon.svelte-cigodj.svelte-cigodj{font-size:1.1rem}.btn-primary.svelte-cigodj.svelte-cigodj{background:var(--player);color:#fff}.btn-primary.svelte-cigodj.svelte-cigodj:hover{filter:brightness(1.1)}.btn-primary.svelte-cigodj.svelte-cigodj:active{filter:brightness(.9)}.btn-secondary.svelte-cigodj.svelte-cigodj{background:var(--surface2);border:1px solid var(--border);color:var(--text)}.btn-secondary.svelte-cigodj.svelte-cigodj:hover{border-color:var(--player)}.btn-ghost.svelte-cigodj.svelte-cigodj{background:transparent;border:1px solid var(--border);color:var(--text-muted)}.btn-ghost.svelte-cigodj.svelte-cigodj:hover{color:var(--text);border-color:var(--border-focus)}.room-code-block.svelte-cigodj.svelte-cigodj{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.room-code-label.svelte-cigodj.svelte-cigodj{font-size:.75rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em}.room-code.svelte-cigodj.svelte-cigodj{font-family:var(--font-mono);font-size:1.5rem;font-weight:700;letter-spacing:.15em;color:var(--accent);flex:1}.copy-btn.svelte-cigodj.svelte-cigodj{padding:6px 12px;border-radius:var(--radius);background:var(--surface2);border:1px solid var(--border);font-size:.8rem;color:var(--text-muted);cursor:pointer}.copy-btn.svelte-cigodj.svelte-cigodj:active{opacity:.7}.players-list.svelte-cigodj.svelte-cigodj{width:100%;display:flex;flex-direction:column;gap:8px}.player-row.svelte-cigodj.svelte-cigodj{display:flex;align-items:center;gap:8px;padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg)}.player-row.is-you.svelte-cigodj.svelte-cigodj{border-color:var(--player)}.player-name.svelte-cigodj.svelte-cigodj{flex:1;font-weight:600}.player-placeholder.svelte-cigodj.svelte-cigodj{opacity:.5}.badge.svelte-cigodj.svelte-cigodj{padding:3px 8px;border-radius:99px;font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.badge-you.svelte-cigodj.svelte-cigodj{background:#58a6ff33;color:var(--player)}.badge-ready.svelte-cigodj.svelte-cigodj{background:#3fb95033;color:var(--success)}.badge-waiting.svelte-cigodj.svelte-cigodj{background:#8b949e26;color:var(--text-muted)}.status-text.svelte-cigodj.svelte-cigodj{color:var(--text);font-size:1rem;font-weight:500}.status-muted.svelte-cigodj.svelte-cigodj{color:var(--text-muted);font-size:.85rem}.error-toast.svelte-cigodj.svelte-cigodj{width:100%;background:#f8514926;border:1px solid var(--danger);color:var(--danger);border-radius:var(--radius);padding:10px 14px;font-size:.9rem;text-align:center}.spinner.svelte-cigodj.svelte-cigodj{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--player);border-radius:50%;animation:svelte-cigodj-spin .8s linear infinite}@keyframes svelte-cigodj-spin{to{transform:rotate(360deg)}}.dot-pulse.svelte-cigodj.svelte-cigodj{display:flex;gap:4px;align-items:center}.dot-pulse.svelte-cigodj span.svelte-cigodj{width:6px;height:6px;background:var(--text-muted);border-radius:50%;animation:svelte-cigodj-pulse 1.2s ease-in-out infinite}.dot-pulse.svelte-cigodj span.svelte-cigodj:nth-child(2){animation-delay:.2s}.dot-pulse.svelte-cigodj span.svelte-cigodj:nth-child(3){animation-delay:.4s}@keyframes svelte-cigodj-pulse{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}} diff --git a/frontend/build/_app/immutable/assets/3.CRf0cTrE.css b/frontend/build/_app/immutable/assets/3.CRf0cTrE.css deleted file mode 100644 index eb49175bd3928176785d2f0e93402a3c87ddea0a..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/assets/3.CRf0cTrE.css +++ /dev/null @@ -1 +0,0 @@ -.map-svg.svelte-1uzb49f{width:100%;height:100%;display:block;cursor:grab;touch-action:none}.map-svg.svelte-1uzb49f:active{cursor:grabbing}.resource-bar.svelte-ra6vcp.svelte-ra6vcp{display:flex;align-items:center;justify-content:space-between;padding:0 14px;height:44px;background:#0d1117f2;border-bottom:1px solid var(--border);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);flex-shrink:0;gap:8px}.resources.svelte-ra6vcp.svelte-ra6vcp{display:flex;align-items:center;gap:10px}.res.svelte-ra6vcp.svelte-ra6vcp{display:flex;align-items:center;gap:4px}.res-icon.svelte-ra6vcp.svelte-ra6vcp{font-size:.85rem}.res-val.svelte-ra6vcp.svelte-ra6vcp{font-size:.85rem;font-weight:700}.mineral.svelte-ra6vcp .res-val.svelte-ra6vcp{color:var(--mineral)}.gas.svelte-ra6vcp .res-val.svelte-ra6vcp{color:var(--gas)}.supply.svelte-ra6vcp .res-val.svelte-ra6vcp{color:var(--supply)}.supply-critical.svelte-ra6vcp .res-val.svelte-ra6vcp{color:var(--danger);animation:svelte-ra6vcp-blink .8s ease-in-out infinite alternate}@keyframes svelte-ra6vcp-blink{0%{opacity:1}to{opacity:.4}}.timer.svelte-ra6vcp.svelte-ra6vcp{font-size:.9rem;font-weight:700;color:var(--text-muted);letter-spacing:.05em}.enemy-label.svelte-ra6vcp.svelte-ra6vcp{display:flex;align-items:center;gap:6px;flex-direction:row-reverse}.enemy-name.svelte-ra6vcp.svelte-ra6vcp{font-size:.8rem;color:var(--enemy);font-weight:600;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.enemy-dot.svelte-ra6vcp.svelte-ra6vcp{width:7px;height:7px;border-radius:50%;background:var(--enemy);flex-shrink:0}.voice-btn.svelte-i113ea{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;width:80px;height:80px;border-radius:50%;background:var(--surface2);border:2px solid var(--border);color:var(--text);transition:all .15s;flex-shrink:0;overflow:visible}.voice-btn.svelte-i113ea:not(:disabled):active,.voice-btn.recording.svelte-i113ea{transform:scale(.96);border-color:var(--danger);background:#f8514926}.voice-btn.processing.svelte-i113ea{border-color:var(--player);opacity:.7}.mic-icon.svelte-i113ea{font-size:1.8rem;line-height:1;margin-top:14px}.btn-label.svelte-i113ea{position:absolute;bottom:-22px;font-size:.65rem;color:var(--text-muted);white-space:nowrap;pointer-events:none}.pulse-ring.svelte-i113ea{position:absolute;top:-8px;right:-8px;bottom:-8px;left:-8px;border-radius:50%;border:2px solid var(--danger);opacity:0;animation:svelte-i113ea-pulse-out 1.2s ease-out infinite;pointer-events:none}.ring2.svelte-i113ea{animation-delay:.4s}@keyframes svelte-i113ea-pulse-out{0%{top:-4px;right:-4px;bottom:-4px;left:-4px;opacity:.7}to{top:-20px;right:-20px;bottom:-20px;left:-20px;opacity:0}}.spinner.svelte-i113ea{position:absolute;top:6px;right:6px;bottom:6px;left:6px;border-radius:50%;border:3px solid var(--border);border-top-color:var(--player);animation:svelte-i113ea-spin .7s linear infinite}@keyframes svelte-i113ea-spin{to{transform:rotate(360deg)}}.panel-backdrop.svelte-15lj9ka{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9}.panel.svelte-15lj9ka{position:absolute;bottom:110px;left:12px;right:12px;background:#161b22f7;border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;z-index:10;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 -4px 24px #00000080;animation:svelte-15lj9ka-slide-up .18s ease}.panel.is-enemy.svelte-15lj9ka{border-color:#f8514966}@keyframes svelte-15lj9ka-slide-up{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}.close-btn.svelte-15lj9ka{position:absolute;top:10px;right:12px;font-size:.9rem;color:var(--text-muted);padding:4px 8px}.panel-header.svelte-15lj9ka{display:flex;align-items:center;gap:8px;margin-bottom:10px}.owner-dot.svelte-15lj9ka{width:10px;height:10px;border-radius:50%;background:var(--enemy);flex-shrink:0}.owner-dot.own.svelte-15lj9ka{background:var(--player)}.unit-name.svelte-15lj9ka{font-weight:800;font-size:1rem;letter-spacing:.05em;flex:1}.owner-label.svelte-15lj9ka{font-size:.72rem;color:var(--text-muted);font-weight:600}.description.svelte-15lj9ka{font-size:.8rem;color:var(--text-muted);margin-bottom:12px;line-height:1.4}.stat-row.svelte-15lj9ka{display:flex;align-items:center;gap:8px;margin-bottom:8px}.stat-label.svelte-15lj9ka{font-size:.72rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;width:60px;flex-shrink:0}.stat-val.svelte-15lj9ka{font-size:.82rem;font-weight:600;font-family:var(--font-mono)}.stat-val.status.svelte-15lj9ka{font-family:inherit;color:var(--accent)}.hp-bar-wrap.svelte-15lj9ka{flex:1;height:8px;background:#ffffff14;border-radius:99px;overflow:hidden}.hp-bar.svelte-15lj9ka{height:100%;border-radius:99px;transition:width .3s}.badge-row.svelte-15lj9ka{margin-top:6px}.badge.svelte-15lj9ka{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:99px;font-size:.72rem;font-weight:600}.orange.svelte-15lj9ka{background:#ffa72626;color:var(--supply)}.purple.svelte-15lj9ka{background:#b39ddb26;color:#b39ddb}.section-label.svelte-15lj9ka{font-size:.7rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;margin:12px 0 6px}.queue-item.svelte-15lj9ka{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:var(--radius);font-size:.8rem;color:var(--text-muted)}.queue-item.active.svelte-15lj9ka{background:#58a6ff14;color:var(--text)}.queue-icon.svelte-15lj9ka{font-size:.65rem;color:var(--player)}.queue-name.svelte-15lj9ka{flex:1;font-weight:600;font-family:var(--font-mono)}.queue-time.svelte-15lj9ka{font-family:var(--font-mono);font-size:.75rem;color:var(--supply)}.overlay.svelte-1rkl0l5.svelte-1rkl0l5{padding:8px 14px;background:#0d1117d9;border-top:1px solid var(--border);min-height:44px;display:flex;flex-direction:column;justify-content:center;gap:2px}.transcription.svelte-1rkl0l5.svelte-1rkl0l5{font-size:.75rem;color:var(--text-muted);font-style:italic;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.feedback.svelte-1rkl0l5.svelte-1rkl0l5{font-size:.88rem;color:var(--text);font-weight:500;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.processing-dots.svelte-1rkl0l5.svelte-1rkl0l5{display:flex;gap:5px;align-items:center;justify-content:center;padding:8px 0}.processing-dots.svelte-1rkl0l5 span.svelte-1rkl0l5{width:7px;height:7px;border-radius:50%;background:var(--player);animation:svelte-1rkl0l5-bounce .9s ease-in-out infinite}.processing-dots.svelte-1rkl0l5 span.svelte-1rkl0l5:nth-child(2){animation-delay:.15s}.processing-dots.svelte-1rkl0l5 span.svelte-1rkl0l5:nth-child(3){animation-delay:.3s}@keyframes svelte-1rkl0l5-bounce{0%,80%,to{transform:scale(.7);opacity:.4}40%{transform:scale(1);opacity:1}}.game-layout.svelte-3ubry2{height:100dvh;display:flex;flex-direction:column;overflow:hidden;background:var(--map-bg)}.map-wrap.svelte-3ubry2{flex:1;position:relative;overflow:hidden;min-height:0}.bottom-panel.svelte-3ubry2{flex-shrink:0;display:flex;flex-direction:column;background:var(--bg);border-top:1px solid var(--border)}.voice-row.svelte-3ubry2{display:flex;align-items:center;justify-content:center;padding:16px 20px 24px;padding-bottom:max(24px,env(safe-area-inset-bottom,24px))}.modal-backdrop.svelte-3ubry2{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000bf;display:flex;align-items:center;justify-content:center;z-index:100;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal.svelte-3ubry2{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-xl);padding:36px 28px;text-align:center;max-width:320px;width:90%;animation:svelte-3ubry2-pop-in .25s cubic-bezier(.34,1.56,.64,1)}@keyframes svelte-3ubry2-pop-in{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}.modal-icon.svelte-3ubry2{font-size:3rem;margin-bottom:12px}.modal-title.svelte-3ubry2{font-size:1.6rem;font-weight:800;margin-bottom:10px;background:linear-gradient(135deg,var(--player),#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.modal-body.svelte-3ubry2{color:var(--text-muted);font-size:.9rem;line-height:1.5;margin-bottom:24px}.btn-back.svelte-3ubry2{background:var(--player);color:#fff;padding:13px 28px;border-radius:var(--radius-lg);font-size:.95rem;font-weight:600;width:100%;transition:filter .15s}.btn-back.svelte-3ubry2:hover{filter:brightness(1.1)}.btn-back.svelte-3ubry2:active{filter:brightness(.9)} diff --git a/frontend/build/_app/immutable/assets/3.D3DQ9iMP.css b/frontend/build/_app/immutable/assets/3.D3DQ9iMP.css new file mode 100644 index 0000000000000000000000000000000000000000..c8a8d181f3113e4691677463fa59baa86bc5a39b --- /dev/null +++ b/frontend/build/_app/immutable/assets/3.D3DQ9iMP.css @@ -0,0 +1 @@ +.map-admin.svelte-8d56b4.svelte-8d56b4{max-width:900px;margin:0 auto;padding:1rem}.hint.svelte-8d56b4.svelte-8d56b4{color:var(--text-muted, #6b7280);font-size:.9rem;margin-bottom:1rem}.map-container.svelte-8d56b4.svelte-8d56b4{position:relative;width:100%;max-height:70vh;background:#1f2937;border-radius:8px;overflow:hidden}.map-bg.svelte-8d56b4.svelte-8d56b4{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;object-fit:contain;pointer-events:none}.map-overlay.svelte-8d56b4.svelte-8d56b4{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none}.map-overlay.svelte-8d56b4 .poly-fill.svelte-8d56b4{pointer-events:auto;cursor:crosshair}.map-overlay.svelte-8d56b4 .poly-point.svelte-8d56b4{pointer-events:auto;cursor:grab}.map-overlay.svelte-8d56b4 .poly-point.dragging.svelte-8d56b4{cursor:grabbing}.map-overlay.svelte-8d56b4 .location.svelte-8d56b4,.map-overlay.svelte-8d56b4 .game-position.svelte-8d56b4{pointer-events:none}.map-overlay.svelte-8d56b4 .map-click-layer.svelte-8d56b4{pointer-events:none;cursor:default}.map-overlay.svelte-8d56b4 .map-click-layer.active.svelte-8d56b4{pointer-events:auto;cursor:crosshair}.location-label.svelte-8d56b4.svelte-8d56b4{font-family:system-ui,sans-serif;font-weight:600;text-shadow:0 0 2px #000,0 0 4px #000}.actions.svelte-8d56b4.svelte-8d56b4{margin-top:1rem;display:flex;flex-wrap:wrap;gap:.75rem;align-items:center}.btn-add.svelte-8d56b4.svelte-8d56b4{padding:.5rem 1rem;background:#16a34a;color:#fff;border:none;border-radius:6px;font-weight:600;cursor:pointer}.btn-add.svelte-8d56b4.svelte-8d56b4:hover{background:#15803d}.btn-positions.svelte-8d56b4.svelte-8d56b4{padding:.5rem 1rem;background:#2563eb;color:#fff;border:none;border-radius:6px;font-weight:600;cursor:pointer}.btn-positions.svelte-8d56b4.svelte-8d56b4:hover{background:#1d4ed8}.game-positions-section.svelte-8d56b4.svelte-8d56b4{margin-top:1.5rem;padding:1rem;background:#0000000a;border-radius:8px}.game-positions-section.svelte-8d56b4 h2.svelte-8d56b4{font-size:1.1rem;margin:0 0 .5rem}.game-positions-section.svelte-8d56b4 .positions-list.svelte-8d56b4{list-style:none;padding:0;margin:.5rem 0 1rem;font-size:.9rem}.game-positions-section.svelte-8d56b4 .positions-list li.svelte-8d56b4{margin-bottom:.25rem}.game-positions-section.svelte-8d56b4 .btn-remove.small.svelte-8d56b4{margin-left:.5rem;padding:.15rem .4rem;font-size:.75rem}.positions-save.svelte-8d56b4.svelte-8d56b4{margin-top:.5rem}.add-mode-hint.svelte-8d56b4.svelte-8d56b4{font-size:.9rem;color:var(--text-muted, #6b7280)}.btn-finish.svelte-8d56b4.svelte-8d56b4{padding:.5rem 1rem;background:#16a34a;color:#fff;border:none;border-radius:6px;font-weight:600;cursor:pointer}.btn-finish.svelte-8d56b4.svelte-8d56b4:hover:not(:disabled){background:#15803d}.btn-finish.svelte-8d56b4.svelte-8d56b4:disabled{opacity:.5;cursor:not-allowed}.btn-cancel.svelte-8d56b4.svelte-8d56b4{padding:.5rem 1rem;background:transparent;color:var(--text-muted, #6b7280);border:1px solid currentColor;border-radius:6px;cursor:pointer}.btn-cancel.svelte-8d56b4.svelte-8d56b4:hover{background:#0000000d}.polygon-list.svelte-8d56b4.svelte-8d56b4{margin-top:.75rem;padding:0;list-style:none;font-size:.9rem}.polygon-list.svelte-8d56b4 li.svelte-8d56b4{display:flex;align-items:center;gap:.5rem;margin-bottom:.25rem}.btn-remove.svelte-8d56b4.svelte-8d56b4{padding:.2rem .5rem;font-size:.8rem;background:transparent;color:#ef4444;border:1px solid #ef4444;border-radius:4px;cursor:pointer}.btn-remove.svelte-8d56b4.svelte-8d56b4:hover{background:#ef444426}.save.svelte-8d56b4.svelte-8d56b4{padding:.5rem 1rem;background:var(--player, #3b82f6);color:#fff;border:none;border-radius:6px;font-weight:600;cursor:pointer}.save.svelte-8d56b4.svelte-8d56b4:disabled{opacity:.5;cursor:not-allowed}.save-status.svelte-8d56b4.svelte-8d56b4{font-size:.9rem;margin-top:.5rem}.status-success.svelte-8d56b4.svelte-8d56b4{color:#22c55e}.error.svelte-8d56b4.svelte-8d56b4{color:#ef4444;margin-bottom:.5rem} diff --git a/frontend/build/_app/immutable/assets/4.jNh2kMze.css b/frontend/build/_app/immutable/assets/4.jNh2kMze.css new file mode 100644 index 0000000000000000000000000000000000000000..aa789e50fbca6b91e4fd376796b373a864839434 --- /dev/null +++ b/frontend/build/_app/immutable/assets/4.jNh2kMze.css @@ -0,0 +1 @@ +.admin.svelte-rj6key.svelte-rj6key{max-width:42rem;margin:0 auto;padding:1.5rem;font-family:system-ui,sans-serif}.hint.svelte-rj6key.svelte-rj6key{color:#666;font-size:.9rem;margin-bottom:1rem}.hint.svelte-rj6key code.svelte-rj6key{background:#eee;padding:.2em .4em;border-radius:4px}.error.svelte-rj6key.svelte-rj6key{color:#c00;margin-bottom:.5rem}.sound-list.svelte-rj6key.svelte-rj6key{list-style:none;padding:0;margin:0}.sound-list.svelte-rj6key li.svelte-rj6key{display:flex;align-items:center;gap:.75rem;padding:.5rem 0;border-bottom:1px solid #eee}.label.svelte-rj6key.svelte-rj6key{font-weight:600;min-width:5rem;text-transform:capitalize}.kind.svelte-rj6key.svelte-rj6key{color:#555;min-width:6rem}.play.svelte-rj6key.svelte-rj6key,.delete.svelte-rj6key.svelte-rj6key{padding:.35rem .6rem;border-radius:6px;cursor:pointer;font-size:.9rem}.play.svelte-rj6key.svelte-rj6key{background:#0a7;color:#fff;border:none}.play.svelte-rj6key.svelte-rj6key:disabled{opacity:.6;cursor:not-allowed}.delete.svelte-rj6key.svelte-rj6key{background:transparent;color:#c00;border:1px solid #c00}.delete.svelte-rj6key.svelte-rj6key:hover{background:#fee} diff --git a/frontend/build/_app/immutable/assets/5.CABnF8ZW.css b/frontend/build/_app/immutable/assets/5.CABnF8ZW.css new file mode 100644 index 0000000000000000000000000000000000000000..b5275b87d99bcb1c491cb2eeff84c5868bf07df0 --- /dev/null +++ b/frontend/build/_app/immutable/assets/5.CABnF8ZW.css @@ -0,0 +1 @@ +.admin.svelte-1xp57ho.svelte-1xp57ho{max-width:56rem;margin:0 auto;padding:1.5rem;font-family:system-ui,sans-serif}.hint.svelte-1xp57ho.svelte-1xp57ho{color:#666;font-size:.9rem;margin-bottom:1rem}.actions.svelte-1xp57ho.svelte-1xp57ho{margin-bottom:1rem}.generate.svelte-1xp57ho.svelte-1xp57ho{padding:.5rem 1rem;border-radius:6px;font-size:1rem;cursor:pointer;background:#1a3a6b;color:#fff;border:none}.generate.svelte-1xp57ho.svelte-1xp57ho:hover:not(:disabled){background:#2a4a7b}.generate.svelte-1xp57ho.svelte-1xp57ho:disabled{opacity:.7;cursor:not-allowed}.error.svelte-1xp57ho.svelte-1xp57ho{color:#c00;margin-bottom:.5rem}section.svelte-1xp57ho.svelte-1xp57ho{margin-top:1.5rem}section.svelte-1xp57ho h2.svelte-1xp57ho{font-size:1.1rem;margin-bottom:.5rem}.empty.svelte-1xp57ho.svelte-1xp57ho{color:#888;font-style:italic}.sprite-grid.svelte-1xp57ho.svelte-1xp57ho{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:1rem}.sprite-grid.svelte-1xp57ho li.svelte-1xp57ho{display:flex;flex-direction:column;align-items:center;padding:.5rem;background:#f5f5f5;border-radius:8px}.sprite-grid.svelte-1xp57ho img.svelte-1xp57ho{width:64px;height:64px;object-fit:contain;image-rendering:pixelated;image-rendering:-moz-crisp-edges;image-rendering:crisp-edges}.sprite-grid.svelte-1xp57ho .name.svelte-1xp57ho{margin-top:.35rem;font-size:.8rem;text-transform:capitalize;color:#333}.sprite-grid.svelte-1xp57ho .regen.svelte-1xp57ho{margin-top:.4rem;padding:.25rem .5rem;font-size:.75rem;border-radius:4px;cursor:pointer;background:#333;color:#fff;border:none}.sprite-grid.svelte-1xp57ho .regen.svelte-1xp57ho:hover:not(:disabled){background:#555}.sprite-grid.svelte-1xp57ho .regen.svelte-1xp57ho:disabled{opacity:.6;cursor:not-allowed} diff --git a/frontend/build/_app/immutable/assets/6.A5_P0Qff.css b/frontend/build/_app/immutable/assets/6.A5_P0Qff.css new file mode 100644 index 0000000000000000000000000000000000000000..2414f5c8df4698eeb823f4ea379febf7ed4fae44 --- /dev/null +++ b/frontend/build/_app/immutable/assets/6.A5_P0Qff.css @@ -0,0 +1 @@ +.map-svg.svelte-1uzb49f{width:100%;height:100%;display:block;cursor:grab;touch-action:none}.map-svg.svelte-1uzb49f:active{cursor:grabbing}.minimap-btn.svelte-1cxn6sq{position:absolute;left:12px;bottom:12px;z-index:20;padding:0;margin:0;border:2px solid rgba(88,166,255,.5);border-radius:6px;background:#0009;cursor:pointer;overflow:hidden;box-shadow:0 2px 12px #0006}.minimap-btn.svelte-1cxn6sq:hover{border-color:#58a6ffe6;background:#00000080}.minimap-svg.svelte-1cxn6sq{display:block;width:140px;height:140px;pointer-events:none}.resource-bar-wrap.svelte-55rzsd.svelte-55rzsd{background:#0d1117f2;border-bottom:1px solid var(--border);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);flex-shrink:0}.resource-bar.svelte-55rzsd.svelte-55rzsd{display:flex;align-items:center;justify-content:space-between;padding:0 14px;height:44px;gap:8px}.production-row.svelte-55rzsd.svelte-55rzsd{display:flex;align-items:center;gap:8px;padding:5px 14px 7px;border-top:1px solid rgba(255,255,255,.05);overflow-x:auto;scrollbar-width:none}.production-row.svelte-55rzsd.svelte-55rzsd::-webkit-scrollbar{display:none}.prod-card.svelte-55rzsd.svelte-55rzsd{display:flex;flex-direction:column;align-items:center;gap:4px;flex-shrink:0;width:40px}.prod-icon-wrap.svelte-55rzsd.svelte-55rzsd{position:relative;width:36px;height:36px;background:#58a6ff14;border:1px solid rgba(88,166,255,.22);border-radius:8px;display:flex;align-items:center;justify-content:center}.prod-icon-wrap.constructing.svelte-55rzsd.svelte-55rzsd{background:#ffa72614;border-color:#ffa7264d}.prod-bar-fill.construction.svelte-55rzsd.svelte-55rzsd{background:var(--supply)}.row-sep.svelte-55rzsd.svelte-55rzsd{width:1px;height:32px;background:#ffffff1a;flex-shrink:0;margin:0 2px}.prod-icon.svelte-55rzsd.svelte-55rzsd{font-size:1.25rem;line-height:1}.prod-badge.svelte-55rzsd.svelte-55rzsd{position:absolute;top:-5px;right:-5px;background:var(--player);color:#fff;font-size:.6rem;font-weight:800;min-width:16px;height:16px;border-radius:99px;display:flex;align-items:center;justify-content:center;padding:0 3px;line-height:1}.prod-bar-wrap.svelte-55rzsd.svelte-55rzsd{width:100%;height:3px;background:#ffffff1a;border-radius:99px;overflow:hidden}.prod-bar-fill.svelte-55rzsd.svelte-55rzsd{height:100%;background:var(--player);border-radius:99px;transition:width .25s linear}.resources.svelte-55rzsd.svelte-55rzsd{display:flex;align-items:center;gap:10px}.res.svelte-55rzsd.svelte-55rzsd{display:flex;align-items:center;gap:4px}.res-icon.svelte-55rzsd.svelte-55rzsd{font-size:.85rem}.res-val.svelte-55rzsd.svelte-55rzsd{font-size:.85rem;font-weight:700}.mineral.svelte-55rzsd .res-val.svelte-55rzsd{color:var(--mineral)}.gas.svelte-55rzsd .res-val.svelte-55rzsd{color:var(--gas)}.supply.svelte-55rzsd .res-val.svelte-55rzsd{color:var(--supply)}.supply-critical.svelte-55rzsd .res-val.svelte-55rzsd{color:var(--danger);animation:svelte-55rzsd-blink .8s ease-in-out infinite alternate}@keyframes svelte-55rzsd-blink{0%{opacity:1}to{opacity:.4}}.timer.svelte-55rzsd.svelte-55rzsd{font-size:.9rem;font-weight:700;color:var(--text-muted);letter-spacing:.05em}.enemy-label.svelte-55rzsd.svelte-55rzsd{display:flex;align-items:center;gap:6px;flex-direction:row-reverse}.enemy-name.svelte-55rzsd.svelte-55rzsd{font-size:.8rem;color:var(--enemy);font-weight:600;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.enemy-dot.svelte-55rzsd.svelte-55rzsd{width:7px;height:7px;border-radius:50%;background:var(--enemy);flex-shrink:0}.voice-btn.svelte-i113ea{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;width:80px;height:80px;border-radius:50%;background:var(--surface2);border:2px solid var(--border);color:var(--text);transition:all .15s;flex-shrink:0;overflow:visible}.voice-btn.svelte-i113ea:not(:disabled):active,.voice-btn.recording.svelte-i113ea{transform:scale(.96);border-color:var(--danger);background:#f8514926}.voice-btn.processing.svelte-i113ea{border-color:var(--player);opacity:.7}.mic-icon.svelte-i113ea{font-size:1.8rem;line-height:1;margin-top:14px}.btn-label.svelte-i113ea{position:absolute;bottom:-22px;font-size:.65rem;color:var(--text-muted);white-space:nowrap;pointer-events:none}.pulse-ring.svelte-i113ea{position:absolute;top:-8px;right:-8px;bottom:-8px;left:-8px;border-radius:50%;border:2px solid var(--danger);opacity:0;animation:svelte-i113ea-pulse-out 1.2s ease-out infinite;pointer-events:none}.ring2.svelte-i113ea{animation-delay:.4s}@keyframes svelte-i113ea-pulse-out{0%{top:-4px;right:-4px;bottom:-4px;left:-4px;opacity:.7}to{top:-20px;right:-20px;bottom:-20px;left:-20px;opacity:0}}.spinner.svelte-i113ea{position:absolute;top:6px;right:6px;bottom:6px;left:6px;border-radius:50%;border:3px solid var(--border);border-top-color:var(--player);animation:svelte-i113ea-spin .7s linear infinite}@keyframes svelte-i113ea-spin{to{transform:rotate(360deg)}}.panel-backdrop.svelte-15lj9ka{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9}.panel.svelte-15lj9ka{position:absolute;bottom:110px;left:12px;right:12px;background:#161b22f7;border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;z-index:10;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 -4px 24px #00000080;animation:svelte-15lj9ka-slide-up .18s ease}.panel.is-enemy.svelte-15lj9ka{border-color:#f8514966}@keyframes svelte-15lj9ka-slide-up{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}.close-btn.svelte-15lj9ka{position:absolute;top:10px;right:12px;font-size:.9rem;color:var(--text-muted);padding:4px 8px}.panel-header.svelte-15lj9ka{display:flex;align-items:center;gap:8px;margin-bottom:10px}.owner-dot.svelte-15lj9ka{width:10px;height:10px;border-radius:50%;background:var(--enemy);flex-shrink:0}.owner-dot.own.svelte-15lj9ka{background:var(--player)}.unit-name.svelte-15lj9ka{font-weight:800;font-size:1rem;letter-spacing:.05em;flex:1}.owner-label.svelte-15lj9ka{font-size:.72rem;color:var(--text-muted);font-weight:600}.description.svelte-15lj9ka{font-size:.8rem;color:var(--text-muted);margin-bottom:12px;line-height:1.4}.stat-row.svelte-15lj9ka{display:flex;align-items:center;gap:8px;margin-bottom:8px}.stat-label.svelte-15lj9ka{font-size:.72rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;width:60px;flex-shrink:0}.stat-val.svelte-15lj9ka{font-size:.82rem;font-weight:600;font-family:var(--font-mono)}.stat-val.status.svelte-15lj9ka{font-family:inherit;color:var(--accent)}.hp-bar-wrap.svelte-15lj9ka{flex:1;height:8px;background:#ffffff14;border-radius:99px;overflow:hidden}.hp-bar.svelte-15lj9ka{height:100%;border-radius:99px;transition:width .3s}.badge-row.svelte-15lj9ka{margin-top:6px}.badge.svelte-15lj9ka{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:99px;font-size:.72rem;font-weight:600}.orange.svelte-15lj9ka{background:#ffa72626;color:var(--supply)}.purple.svelte-15lj9ka{background:#b39ddb26;color:#b39ddb}.section-label.svelte-15lj9ka{font-size:.7rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;margin:12px 0 6px}.queue-item.svelte-15lj9ka{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:var(--radius);font-size:.8rem;color:var(--text-muted)}.queue-item.active.svelte-15lj9ka{background:#58a6ff14;color:var(--text)}.queue-icon.svelte-15lj9ka{font-size:.65rem;color:var(--player)}.queue-name.svelte-15lj9ka{flex:1;font-weight:600;font-family:var(--font-mono)}.queue-time.svelte-15lj9ka{font-family:var(--font-mono);font-size:.75rem;color:var(--supply)}.overlay.svelte-1rkl0l5.svelte-1rkl0l5{padding:8px 14px;background:#0d1117d9;border-top:1px solid var(--border);min-height:44px;display:flex;flex-direction:column;justify-content:center;gap:2px}.transcription.svelte-1rkl0l5.svelte-1rkl0l5{font-size:.75rem;color:var(--text-muted);font-style:italic;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.feedback.svelte-1rkl0l5.svelte-1rkl0l5{font-size:.88rem;color:var(--text);font-weight:500;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.processing-dots.svelte-1rkl0l5.svelte-1rkl0l5{display:flex;gap:5px;align-items:center;justify-content:center;padding:8px 0}.processing-dots.svelte-1rkl0l5 span.svelte-1rkl0l5{width:7px;height:7px;border-radius:50%;background:var(--player);animation:svelte-1rkl0l5-bounce .9s ease-in-out infinite}.processing-dots.svelte-1rkl0l5 span.svelte-1rkl0l5:nth-child(2){animation-delay:.15s}.processing-dots.svelte-1rkl0l5 span.svelte-1rkl0l5:nth-child(3){animation-delay:.3s}@keyframes svelte-1rkl0l5-bounce{0%,80%,to{transform:scale(.7);opacity:.4}40%{transform:scale(1);opacity:1}}.game-layout.svelte-1chqw79{height:100dvh;display:flex;flex-direction:column;overflow:hidden;background:var(--map-bg)}.map-wrap.svelte-1chqw79{flex:1;position:relative;overflow:hidden;min-height:0}.bottom-panel.svelte-1chqw79{flex-shrink:0;display:flex;flex-direction:column;background:var(--bg);border-top:1px solid var(--border)}.controls-row.svelte-1chqw79{display:flex;align-items:center;gap:12px;padding:12px 16px 20px;padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}.text-input-wrap.svelte-1chqw79{flex:1;display:flex;align-items:center;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;transition:border-color .15s}.text-input-wrap.svelte-1chqw79:focus-within{border-color:var(--player)}.text-cmd.svelte-1chqw79{flex:1;background:transparent;border:none;outline:none;color:var(--text);font-size:.9rem;padding:11px 14px}.text-cmd.svelte-1chqw79::placeholder{color:var(--text-muted)}.text-cmd.svelte-1chqw79:disabled{opacity:.5;cursor:not-allowed}.text-send-btn.svelte-1chqw79{flex-shrink:0;width:42px;height:42px;display:flex;align-items:center;justify-content:center;background:var(--player);color:#fff;font-size:1rem;border-radius:0;transition:filter .15s,opacity .15s}.text-send-btn.svelte-1chqw79:disabled{opacity:.4;cursor:not-allowed}.text-send-btn.svelte-1chqw79:not(:disabled):hover{filter:brightness(1.15)}.send-spinner.svelte-1chqw79{display:inline-block;width:16px;height:16px;border-radius:50%;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;animation:spin .7s linear infinite}.divider.svelte-1chqw79{flex-shrink:0;font-size:.75rem;color:var(--text-muted);-webkit-user-select:none;user-select:none}.modal-backdrop.svelte-1chqw79{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000bf;display:flex;align-items:center;justify-content:center;z-index:100;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal.svelte-1chqw79{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-xl);padding:36px 28px;text-align:center;max-width:320px;width:90%;animation:svelte-1chqw79-pop-in .25s cubic-bezier(.34,1.56,.64,1)}@keyframes svelte-1chqw79-pop-in{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}.modal-icon.svelte-1chqw79{font-size:3rem;margin-bottom:12px}.modal-title.svelte-1chqw79{font-size:1.6rem;font-weight:800;margin-bottom:10px;background:linear-gradient(135deg,var(--player),#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.modal-body.svelte-1chqw79{color:var(--text-muted);font-size:.9rem;line-height:1.5;margin-bottom:24px}.btn-back.svelte-1chqw79{background:var(--player);color:#fff;padding:13px 28px;border-radius:var(--radius-lg);font-size:.95rem;font-weight:600;width:100%;transition:filter .15s}.btn-back.svelte-1chqw79:hover{filter:brightness(1.1)}.btn-back.svelte-1chqw79:active{filter:brightness(.9)} diff --git a/frontend/build/_app/immutable/assets/cover.CJg7zHH3.jpg b/frontend/build/_app/immutable/assets/cover.CJg7zHH3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16cbf9433456717615736baf2742c014fecd8aea --- /dev/null +++ b/frontend/build/_app/immutable/assets/cover.CJg7zHH3.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bc3d826fb920b534d2ade3c7b80aa59d1cef9556ef804f195bb544fa9a6a1b5 +size 124387 diff --git a/frontend/build/_app/immutable/chunks/B2mXDaCf.js b/frontend/build/_app/immutable/chunks/B2mXDaCf.js new file mode 100644 index 0000000000000000000000000000000000000000..0e57c72f56cb173f1e255154772a04d624a52bd7 --- /dev/null +++ b/frontend/build/_app/immutable/chunks/B2mXDaCf.js @@ -0,0 +1 @@ +import{a as Ae}from"./CbPkTnjI.js";import{x as Te}from"./BWggeOnc.js";function vt(n){return(n==null?void 0:n.length)!==void 0?n:Array.from(n)}function Et(n,e){n.d(1),e.delete(n.key)}function kt(n,e,t,s,i,r,o,c,h,y,l,m){let p=n.length,T=r.length,k=p;const F={};for(;k--;)F[n[k].key]=k;const C=[],V=new Map,M=new Map,ne=[];for(k=T;k--;){const f=m(i,r,k),_=t(f);let w=o.get(_);w?ne.push(()=>w.p(f,e)):(w=y(_,f),w.c()),V.set(_,C[k]=w),_ in F&&M.set(_,Math.abs(k-F[_]))}const ie=new Set,re=new Set;function H(f){Ae(f,1),f.m(c,l),o.set(f.key,f),l=f.first,T--}for(;p&&T;){const f=C[T-1],_=n[p-1],w=f.key,B=_.key;f===_?(l=f.first,p--,T--):V.has(B)?!o.has(w)||ie.has(w)?H(f):re.has(B)?p--:M.get(w)>M.get(B)?(re.add(w),H(f)):(ie.add(B),p--):(h(_,o),p--)}for(;p--;){const f=n[p];V.has(f.key)||h(f,o)}for(;T;)H(C[T-1]);return Te(ne),C}const v=Object.create(null);v.open="0";v.close="1";v.ping="2";v.pong="3";v.message="4";v.upgrade="5";v.noop="6";const L=Object.create(null);Object.keys(v).forEach(n=>{L[v[n]]=n});const Y={type:"error",data:"parser error"},fe=typeof Blob=="function"||typeof Blob<"u"&&Object.prototype.toString.call(Blob)==="[object BlobConstructor]",le=typeof ArrayBuffer=="function",pe=n=>typeof ArrayBuffer.isView=="function"?ArrayBuffer.isView(n):n&&n.buffer instanceof ArrayBuffer,G=({type:n,data:e},t,s)=>fe&&e instanceof Blob?t?s(e):oe(e,s):le&&(e instanceof ArrayBuffer||pe(e))?t?s(e):oe(new Blob([e]),s):s(v[n]+(e||"")),oe=(n,e)=>{const t=new FileReader;return t.onload=function(){const s=t.result.split(",")[1];e("b"+(s||""))},t.readAsDataURL(n)};function ce(n){return n instanceof Uint8Array?n:n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n.buffer,n.byteOffset,n.byteLength)}let K;function Re(n,e){if(fe&&n.data instanceof Blob)return n.data.arrayBuffer().then(ce).then(e);if(le&&(n.data instanceof ArrayBuffer||pe(n.data)))return e(ce(n.data));G(n,!1,t=>{K||(K=new TextEncoder),e(K.encode(t))})}const ae="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",S=typeof Uint8Array>"u"?[]:new Uint8Array(256);for(let n=0;n{let e=n.length*.75,t=n.length,s,i=0,r,o,c,h;n[n.length-1]==="="&&(e--,n[n.length-2]==="="&&e--);const y=new ArrayBuffer(e),l=new Uint8Array(y);for(s=0;s>4,l[i++]=(o&15)<<4|c>>2,l[i++]=(c&3)<<6|h&63;return y},Se=typeof ArrayBuffer=="function",Z=(n,e)=>{if(typeof n!="string")return{type:"message",data:de(n,e)};const t=n.charAt(0);return t==="b"?{type:"message",data:Ce(n.substring(1),e)}:L[t]?n.length>1?{type:L[t],data:n.substring(1)}:{type:L[t]}:Y},Ce=(n,e)=>{if(Se){const t=Oe(n);return de(t,e)}else return{base64:!0,data:n}},de=(n,e)=>{switch(e){case"blob":return n instanceof Blob?n:new Blob([n]);case"arraybuffer":default:return n instanceof ArrayBuffer?n:n.buffer}},ye="",Be=(n,e)=>{const t=n.length,s=new Array(t);let i=0;n.forEach((r,o)=>{G(r,!1,c=>{s[o]=c,++i===t&&e(s.join(ye))})})},xe=(n,e)=>{const t=n.split(ye),s=[];for(let i=0;i{const s=t.length;let i;if(s<126)i=new Uint8Array(1),new DataView(i.buffer).setUint8(0,s);else if(s<65536){i=new Uint8Array(3);const r=new DataView(i.buffer);r.setUint8(0,126),r.setUint16(1,s)}else{i=new Uint8Array(9);const r=new DataView(i.buffer);r.setUint8(0,127),r.setBigUint64(1,BigInt(s))}n.data&&typeof n.data!="string"&&(i[0]|=128),e.enqueue(i),e.enqueue(t)})}})}let W;function x(n){return n.reduce((e,t)=>e+t.length,0)}function N(n,e){if(n[0].length===e)return n.shift();const t=new Uint8Array(e);let s=0;for(let i=0;iMath.pow(2,21)-1){c.enqueue(Y);break}i=l*Math.pow(2,32)+y.getUint32(4),s=3}else{if(x(t)n){c.enqueue(Y);break}}}})}const ge=4;function u(n){if(n)return Pe(n)}function Pe(n){for(var e in u.prototype)n[e]=u.prototype[e];return n}u.prototype.on=u.prototype.addEventListener=function(n,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+n]=this._callbacks["$"+n]||[]).push(e),this};u.prototype.once=function(n,e){function t(){this.off(n,t),e.apply(this,arguments)}return t.fn=e,this.on(n,t),this};u.prototype.off=u.prototype.removeListener=u.prototype.removeAllListeners=u.prototype.removeEventListener=function(n,e){if(this._callbacks=this._callbacks||{},arguments.length==0)return this._callbacks={},this;var t=this._callbacks["$"+n];if(!t)return this;if(arguments.length==1)return delete this._callbacks["$"+n],this;for(var s,i=0;iPromise.resolve().then(e):(e,t)=>t(e,0),d=typeof self<"u"?self:typeof window<"u"?window:Function("return this")(),qe="arraybuffer";function me(n,...e){return e.reduce((t,s)=>(n.hasOwnProperty(s)&&(t[s]=n[s]),t),{})}const De=d.setTimeout,Ie=d.clearTimeout;function U(n,e){e.useNativeTimers?(n.setTimeoutFn=De.bind(d),n.clearTimeoutFn=Ie.bind(d)):(n.setTimeoutFn=d.setTimeout.bind(d),n.clearTimeoutFn=d.clearTimeout.bind(d))}const Ue=1.33;function Fe(n){return typeof n=="string"?Ve(n):Math.ceil((n.byteLength||n.size)*Ue)}function Ve(n){let e=0,t=0;for(let s=0,i=n.length;s=57344?t+=3:(s++,t+=4);return t}function _e(){return Date.now().toString(36).substring(3)+Math.random().toString(36).substring(2,5)}function Me(n){let e="";for(let t in n)n.hasOwnProperty(t)&&(e.length&&(e+="&"),e+=encodeURIComponent(t)+"="+encodeURIComponent(n[t]));return e}function He(n){let e={},t=n.split("&");for(let s=0,i=t.length;s{this.readyState="paused",e()};if(this._polling||!this.writable){let s=0;this._polling&&(s++,this.once("pollComplete",function(){--s||t()})),this.writable||(s++,this.once("drain",function(){--s||t()}))}else t()}_poll(){this._polling=!0,this.doPoll(),this.emitReserved("poll")}onData(e){const t=s=>{if(this.readyState==="opening"&&s.type==="open"&&this.onOpen(),s.type==="close")return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(s)};xe(e,this.socket.binaryType).forEach(t),this.readyState!=="closed"&&(this._polling=!1,this.emitReserved("pollComplete"),this.readyState==="open"&&this._poll())}doClose(){const e=()=>{this.write([{type:"close"}])};this.readyState==="open"?e():this.once("open",e)}write(e){this.writable=!1,Be(e,t=>{this.doWrite(t,()=>{this.writable=!0,this.emitReserved("drain")})})}uri(){const e=this.opts.secure?"https":"http",t=this.query||{};return this.opts.timestampRequests!==!1&&(t[this.opts.timestampParam]=_e()),!this.supportsBinary&&!t.sid&&(t.b64=1),this.createUri(e,t)}}let we=!1;try{we=typeof XMLHttpRequest<"u"&&"withCredentials"in new XMLHttpRequest}catch{}const $e=we;function Ye(){}class ze extends We{constructor(e){if(super(e),typeof location<"u"){const t=location.protocol==="https:";let s=location.port;s||(s=t?"443":"80"),this.xd=typeof location<"u"&&e.hostname!==location.hostname||s!==e.port}}doWrite(e,t){const s=this.request({method:"POST",data:e});s.on("success",t),s.on("error",(i,r)=>{this.onError("xhr post error",i,r)})}doPoll(){const e=this.request();e.on("data",this.onData.bind(this)),e.on("error",(t,s)=>{this.onError("xhr poll error",t,s)}),this.pollXhr=e}}class b extends u{constructor(e,t,s){super(),this.createRequest=e,U(this,s),this._opts=s,this._method=s.method||"GET",this._uri=t,this._data=s.data!==void 0?s.data:null,this._create()}_create(){var e;const t=me(this._opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");t.xdomain=!!this._opts.xd;const s=this._xhr=this.createRequest(t);try{s.open(this._method,this._uri,!0);try{if(this._opts.extraHeaders){s.setDisableHeaderCheck&&s.setDisableHeaderCheck(!0);for(let i in this._opts.extraHeaders)this._opts.extraHeaders.hasOwnProperty(i)&&s.setRequestHeader(i,this._opts.extraHeaders[i])}}catch{}if(this._method==="POST")try{s.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch{}try{s.setRequestHeader("Accept","*/*")}catch{}(e=this._opts.cookieJar)===null||e===void 0||e.addCookies(s),"withCredentials"in s&&(s.withCredentials=this._opts.withCredentials),this._opts.requestTimeout&&(s.timeout=this._opts.requestTimeout),s.onreadystatechange=()=>{var i;s.readyState===3&&((i=this._opts.cookieJar)===null||i===void 0||i.parseCookies(s.getResponseHeader("set-cookie"))),s.readyState===4&&(s.status===200||s.status===1223?this._onLoad():this.setTimeoutFn(()=>{this._onError(typeof s.status=="number"?s.status:0)},0))},s.send(this._data)}catch(i){this.setTimeoutFn(()=>{this._onError(i)},0);return}typeof document<"u"&&(this._index=b.requestsCount++,b.requests[this._index]=this)}_onError(e){this.emitReserved("error",e,this._xhr),this._cleanup(!0)}_cleanup(e){if(!(typeof this._xhr>"u"||this._xhr===null)){if(this._xhr.onreadystatechange=Ye,e)try{this._xhr.abort()}catch{}typeof document<"u"&&delete b.requests[this._index],this._xhr=null}}_onLoad(){const e=this._xhr.responseText;e!==null&&(this.emitReserved("data",e),this.emitReserved("success"),this._cleanup())}abort(){this._cleanup()}}b.requestsCount=0;b.requests={};if(typeof document<"u"){if(typeof attachEvent=="function")attachEvent("onunload",he);else if(typeof addEventListener=="function"){const n="onpagehide"in d?"pagehide":"unload";addEventListener(n,he,!1)}}function he(){for(let n in b.requests)b.requests.hasOwnProperty(n)&&b.requests[n].abort()}const Je=function(){const n=be({xdomain:!1});return n&&n.responseType!==null}();class Xe extends ze{constructor(e){super(e);const t=e&&e.forceBase64;this.supportsBinary=Je&&!t}request(e={}){return Object.assign(e,{xd:this.xd},this.opts),new b(be,this.uri(),e)}}function be(n){const e=n.xdomain;try{if(typeof XMLHttpRequest<"u"&&(!e||$e))return new XMLHttpRequest}catch{}if(!e)try{return new d[["Active"].concat("Object").join("X")]("Microsoft.XMLHTTP")}catch{}}const ve=typeof navigator<"u"&&typeof navigator.product=="string"&&navigator.product.toLowerCase()==="reactnative";class Qe extends ee{get name(){return"websocket"}doOpen(){const e=this.uri(),t=this.opts.protocols,s=ve?{}:me(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(s.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(e,t,s)}catch(i){return this.emitReserved("error",i)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=e=>this.onClose({description:"websocket connection closed",context:e}),this.ws.onmessage=e=>this.onData(e.data),this.ws.onerror=e=>this.onError("websocket error",e)}write(e){this.writable=!1;for(let t=0;t{try{this.doWrite(s,r)}catch{}i&&I(()=>{this.writable=!0,this.emitReserved("drain")},this.setTimeoutFn)})}}doClose(){typeof this.ws<"u"&&(this.ws.onerror=()=>{},this.ws.close(),this.ws=null)}uri(){const e=this.opts.secure?"wss":"ws",t=this.query||{};return this.opts.timestampRequests&&(t[this.opts.timestampParam]=_e()),this.supportsBinary||(t.b64=1),this.createUri(e,t)}}const $=d.WebSocket||d.MozWebSocket;class je extends Qe{createSocket(e,t,s){return ve?new $(e,t,s):t?new $(e,t):new $(e)}doWrite(e,t){this.ws.send(t)}}class Ge extends ee{get name(){return"webtransport"}doOpen(){try{this._transport=new WebTransport(this.createUri("https"),this.opts.transportOptions[this.name])}catch(e){return this.emitReserved("error",e)}this._transport.closed.then(()=>{this.onClose()}).catch(e=>{this.onError("webtransport error",e)}),this._transport.ready.then(()=>{this._transport.createBidirectionalStream().then(e=>{const t=Le(Number.MAX_SAFE_INTEGER,this.socket.binaryType),s=e.readable.pipeThrough(t).getReader(),i=Ne();i.readable.pipeTo(e.writable),this._writer=i.writable.getWriter();const r=()=>{s.read().then(({done:c,value:h})=>{c||(this.onPacket(h),r())}).catch(c=>{})};r();const o={type:"open"};this.query.sid&&(o.data=`{"sid":"${this.query.sid}"}`),this._writer.write(o).then(()=>this.onOpen())})})}write(e){this.writable=!1;for(let t=0;t{i&&I(()=>{this.writable=!0,this.emitReserved("drain")},this.setTimeoutFn)})}}doClose(){var e;(e=this._transport)===null||e===void 0||e.close()}}const Ze={websocket:je,webtransport:Ge,polling:Xe},et=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,tt=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];function z(n){if(n.length>8e3)throw"URI too long";const e=n,t=n.indexOf("["),s=n.indexOf("]");t!=-1&&s!=-1&&(n=n.substring(0,t)+n.substring(t,s).replace(/:/g,";")+n.substring(s,n.length));let i=et.exec(n||""),r={},o=14;for(;o--;)r[tt[o]]=i[o]||"";return t!=-1&&s!=-1&&(r.source=e,r.host=r.host.substring(1,r.host.length-1).replace(/;/g,":"),r.authority=r.authority.replace("[","").replace("]","").replace(/;/g,":"),r.ipv6uri=!0),r.pathNames=st(r,r.path),r.queryKey=nt(r,r.query),r}function st(n,e){const t=/\/{2,9}/g,s=e.replace(t,"/").split("/");return(e.slice(0,1)=="/"||e.length===0)&&s.splice(0,1),e.slice(-1)=="/"&&s.splice(s.length-1,1),s}function nt(n,e){const t={};return e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(s,i,r){i&&(t[i]=r)}),t}const J=typeof addEventListener=="function"&&typeof removeEventListener=="function",P=[];J&&addEventListener("offline",()=>{P.forEach(n=>n())},!1);class A extends u{constructor(e,t){if(super(),this.binaryType=qe,this.writeBuffer=[],this._prevBufferLen=0,this._pingInterval=-1,this._pingTimeout=-1,this._maxPayload=-1,this._pingTimeoutTime=1/0,e&&typeof e=="object"&&(t=e,e=null),e){const s=z(e);t.hostname=s.host,t.secure=s.protocol==="https"||s.protocol==="wss",t.port=s.port,s.query&&(t.query=s.query)}else t.host&&(t.hostname=z(t.host).host);U(this,t),this.secure=t.secure!=null?t.secure:typeof location<"u"&&location.protocol==="https:",t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.hostname=t.hostname||(typeof location<"u"?location.hostname:"localhost"),this.port=t.port||(typeof location<"u"&&location.port?location.port:this.secure?"443":"80"),this.transports=[],this._transportsByName={},t.transports.forEach(s=>{const i=s.prototype.name;this.transports.push(i),this._transportsByName[i]=s}),this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},t),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),typeof this.opts.query=="string"&&(this.opts.query=He(this.opts.query)),J&&(this.opts.closeOnBeforeunload&&(this._beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this._beforeunloadEventListener,!1)),this.hostname!=="localhost"&&(this._offlineEventListener=()=>{this._onClose("transport close",{description:"network connection lost"})},P.push(this._offlineEventListener))),this.opts.withCredentials&&(this._cookieJar=void 0),this._open()}createTransport(e){const t=Object.assign({},this.opts.query);t.EIO=ge,t.transport=e,this.id&&(t.sid=this.id);const s=Object.assign({},this.opts,{query:t,socket:this,hostname:this.hostname,secure:this.secure,port:this.port},this.opts.transportOptions[e]);return new this._transportsByName[e](s)}_open(){if(this.transports.length===0){this.setTimeoutFn(()=>{this.emitReserved("error","No transports available")},0);return}const e=this.opts.rememberUpgrade&&A.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1?"websocket":this.transports[0];this.readyState="opening";const t=this.createTransport(e);t.open(),this.setTransport(t)}setTransport(e){this.transport&&this.transport.removeAllListeners(),this.transport=e,e.on("drain",this._onDrain.bind(this)).on("packet",this._onPacket.bind(this)).on("error",this._onError.bind(this)).on("close",t=>this._onClose("transport close",t))}onOpen(){this.readyState="open",A.priorWebsocketSuccess=this.transport.name==="websocket",this.emitReserved("open"),this.flush()}_onPacket(e){if(this.readyState==="opening"||this.readyState==="open"||this.readyState==="closing")switch(this.emitReserved("packet",e),this.emitReserved("heartbeat"),e.type){case"open":this.onHandshake(JSON.parse(e.data));break;case"ping":this._sendPacket("pong"),this.emitReserved("ping"),this.emitReserved("pong"),this._resetPingTimeout();break;case"error":const t=new Error("server error");t.code=e.data,this._onError(t);break;case"message":this.emitReserved("data",e.data),this.emitReserved("message",e.data);break}}onHandshake(e){this.emitReserved("handshake",e),this.id=e.sid,this.transport.query.sid=e.sid,this._pingInterval=e.pingInterval,this._pingTimeout=e.pingTimeout,this._maxPayload=e.maxPayload,this.onOpen(),this.readyState!=="closed"&&this._resetPingTimeout()}_resetPingTimeout(){this.clearTimeoutFn(this._pingTimeoutTimer);const e=this._pingInterval+this._pingTimeout;this._pingTimeoutTime=Date.now()+e,this._pingTimeoutTimer=this.setTimeoutFn(()=>{this._onClose("ping timeout")},e),this.opts.autoUnref&&this._pingTimeoutTimer.unref()}_onDrain(){this.writeBuffer.splice(0,this._prevBufferLen),this._prevBufferLen=0,this.writeBuffer.length===0?this.emitReserved("drain"):this.flush()}flush(){if(this.readyState!=="closed"&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const e=this._getWritablePackets();this.transport.send(e),this._prevBufferLen=e.length,this.emitReserved("flush")}}_getWritablePackets(){if(!(this._maxPayload&&this.transport.name==="polling"&&this.writeBuffer.length>1))return this.writeBuffer;let t=1;for(let s=0;s0&&t>this._maxPayload)return this.writeBuffer.slice(0,s);t+=2}return this.writeBuffer}_hasPingExpired(){if(!this._pingTimeoutTime)return!0;const e=Date.now()>this._pingTimeoutTime;return e&&(this._pingTimeoutTime=0,I(()=>{this._onClose("ping timeout")},this.setTimeoutFn)),e}write(e,t,s){return this._sendPacket("message",e,t,s),this}send(e,t,s){return this._sendPacket("message",e,t,s),this}_sendPacket(e,t,s,i){if(typeof t=="function"&&(i=t,t=void 0),typeof s=="function"&&(i=s,s=null),this.readyState==="closing"||this.readyState==="closed")return;s=s||{},s.compress=s.compress!==!1;const r={type:e,data:t,options:s};this.emitReserved("packetCreate",r),this.writeBuffer.push(r),i&&this.once("flush",i),this.flush()}close(){const e=()=>{this._onClose("forced close"),this.transport.close()},t=()=>{this.off("upgrade",t),this.off("upgradeError",t),e()},s=()=>{this.once("upgrade",t),this.once("upgradeError",t)};return(this.readyState==="opening"||this.readyState==="open")&&(this.readyState="closing",this.writeBuffer.length?this.once("drain",()=>{this.upgrading?s():e()}):this.upgrading?s():e()),this}_onError(e){if(A.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&this.readyState==="opening")return this.transports.shift(),this._open();this.emitReserved("error",e),this._onClose("transport error",e)}_onClose(e,t){if(this.readyState==="opening"||this.readyState==="open"||this.readyState==="closing"){if(this.clearTimeoutFn(this._pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),J&&(this._beforeunloadEventListener&&removeEventListener("beforeunload",this._beforeunloadEventListener,!1),this._offlineEventListener)){const s=P.indexOf(this._offlineEventListener);s!==-1&&P.splice(s,1)}this.readyState="closed",this.id=null,this.emitReserved("close",e,t),this.writeBuffer=[],this._prevBufferLen=0}}}A.protocol=ge;class it extends A{constructor(){super(...arguments),this._upgrades=[]}onOpen(){if(super.onOpen(),this.readyState==="open"&&this.opts.upgrade)for(let e=0;e{s||(t.send([{type:"ping",data:"probe"}]),t.once("packet",m=>{if(!s)if(m.type==="pong"&&m.data==="probe"){if(this.upgrading=!0,this.emitReserved("upgrading",t),!t)return;A.priorWebsocketSuccess=t.name==="websocket",this.transport.pause(()=>{s||this.readyState!=="closed"&&(l(),this.setTransport(t),t.send([{type:"upgrade"}]),this.emitReserved("upgrade",t),t=null,this.upgrading=!1,this.flush())})}else{const p=new Error("probe error");p.transport=t.name,this.emitReserved("upgradeError",p)}}))};function r(){s||(s=!0,l(),t.close(),t=null)}const o=m=>{const p=new Error("probe error: "+m);p.transport=t.name,r(),this.emitReserved("upgradeError",p)};function c(){o("transport closed")}function h(){o("socket closed")}function y(m){t&&m.name!==t.name&&r()}const l=()=>{t.removeListener("open",i),t.removeListener("error",o),t.removeListener("close",c),this.off("close",h),this.off("upgrading",y)};t.once("open",i),t.once("error",o),t.once("close",c),this.once("close",h),this.once("upgrading",y),this._upgrades.indexOf("webtransport")!==-1&&e!=="webtransport"?this.setTimeoutFn(()=>{s||t.open()},200):t.open()}onHandshake(e){this._upgrades=this._filterUpgrades(e.upgrades),super.onHandshake(e)}_filterUpgrades(e){const t=[];for(let s=0;sZe[i]).filter(i=>!!i)),super(e,s)}};function ot(n,e="",t){let s=n;t=t||typeof location<"u"&&location,n==null&&(n=t.protocol+"//"+t.host),typeof n=="string"&&(n.charAt(0)==="/"&&(n.charAt(1)==="/"?n=t.protocol+n:n=t.host+n),/^(https?|wss?):\/\//.test(n)||(typeof t<"u"?n=t.protocol+"//"+n:n="https://"+n),s=z(n)),s.port||(/^(http|ws)$/.test(s.protocol)?s.port="80":/^(http|ws)s$/.test(s.protocol)&&(s.port="443")),s.path=s.path||"/";const r=s.host.indexOf(":")!==-1?"["+s.host+"]":s.host;return s.id=s.protocol+"://"+r+":"+s.port+e,s.href=s.protocol+"://"+r+(t&&t.port===s.port?"":":"+s.port),s}const ct=typeof ArrayBuffer=="function",at=n=>typeof ArrayBuffer.isView=="function"?ArrayBuffer.isView(n):n.buffer instanceof ArrayBuffer,Ee=Object.prototype.toString,ht=typeof Blob=="function"||typeof Blob<"u"&&Ee.call(Blob)==="[object BlobConstructor]",ut=typeof File=="function"||typeof File<"u"&&Ee.call(File)==="[object FileConstructor]";function te(n){return ct&&(n instanceof ArrayBuffer||at(n))||ht&&n instanceof Blob||ut&&n instanceof File}function q(n,e){if(!n||typeof n!="object")return!1;if(Array.isArray(n)){for(let t=0,s=n.length;t=0&&n.num{delete this.acks[e];for(let c=0;c{this.io.clearTimeoutFn(r),t.apply(this,c)};o.withError=!0,this.acks[e]=o}emitWithAck(e,...t){return new Promise((s,i)=>{const r=(o,c)=>o?i(o):s(c);r.withError=!0,t.push(r),this.emit(e,...t)})}_addToQueue(e){let t;typeof e[e.length-1]=="function"&&(t=e.pop());const s={id:this._queueSeq++,tryCount:0,pending:!1,args:e,flags:Object.assign({fromQueue:!0},this.flags)};e.push((i,...r)=>(this._queue[0],i!==null?s.tryCount>this._opts.retries&&(this._queue.shift(),t&&t(i)):(this._queue.shift(),t&&t(null,...r)),s.pending=!1,this._drainQueue())),this._queue.push(s),this._drainQueue()}_drainQueue(e=!1){if(!this.connected||this._queue.length===0)return;const t=this._queue[0];t.pending&&!e||(t.pending=!0,t.tryCount++,this.flags=t.flags,this.emit.apply(this,t.args))}packet(e){e.nsp=this.nsp,this.io._packet(e)}onopen(){typeof this.auth=="function"?this.auth(e=>{this._sendConnectPacket(e)}):this._sendConnectPacket(this.auth)}_sendConnectPacket(e){this.packet({type:a.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},e):e})}onerror(e){this.connected||this.emitReserved("connect_error",e)}onclose(e,t){this.connected=!1,delete this.id,this.emitReserved("disconnect",e,t),this._clearAcks()}_clearAcks(){Object.keys(this.acks).forEach(e=>{if(!this.sendBuffer.some(s=>String(s.id)===e)){const s=this.acks[e];delete this.acks[e],s.withError&&s.call(this,new Error("socket has been disconnected"))}})}onpacket(e){if(e.nsp===this.nsp)switch(e.type){case a.CONNECT:e.data&&e.data.sid?this.onconnect(e.data.sid,e.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case a.EVENT:case a.BINARY_EVENT:this.onevent(e);break;case a.ACK:case a.BINARY_ACK:this.onack(e);break;case a.DISCONNECT:this.ondisconnect();break;case a.CONNECT_ERROR:this.destroy();const s=new Error(e.data.message);s.data=e.data.data,this.emitReserved("connect_error",s);break}}onevent(e){const t=e.data||[];e.id!=null&&t.push(this.ack(e.id)),this.connected?this.emitEvent(t):this.receiveBuffer.push(Object.freeze(t))}emitEvent(e){if(this._anyListeners&&this._anyListeners.length){const t=this._anyListeners.slice();for(const s of t)s.apply(this,e)}super.emit.apply(this,e),this._pid&&e.length&&typeof e[e.length-1]=="string"&&(this._lastOffset=e[e.length-1])}ack(e){const t=this;let s=!1;return function(...i){s||(s=!0,t.packet({type:a.ACK,id:e,data:i}))}}onack(e){const t=this.acks[e.id];typeof t=="function"&&(delete this.acks[e.id],t.withError&&e.data.unshift(null),t.apply(this,e.data))}onconnect(e,t){this.id=e,this.recovered=t&&this._pid===t,this._pid=t,this.connected=!0,this.emitBuffered(),this._drainQueue(!0),this.emitReserved("connect")}emitBuffered(){this.receiveBuffer.forEach(e=>this.emitEvent(e)),this.receiveBuffer=[],this.sendBuffer.forEach(e=>{this.notifyOutgoingListeners(e),this.packet(e)}),this.sendBuffer=[]}ondisconnect(){this.destroy(),this.onclose("io server disconnect")}destroy(){this.subs&&(this.subs.forEach(e=>e()),this.subs=void 0),this.io._destroy(this)}disconnect(){return this.connected&&this.packet({type:a.DISCONNECT}),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(e){return this.flags.compress=e,this}get volatile(){return this.flags.volatile=!0,this}timeout(e){return this.flags.timeout=e,this}onAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(e),this}prependAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(e),this}offAny(e){if(!this._anyListeners)return this;if(e){const t=this._anyListeners;for(let s=0;s0&&n.jitter<=1?n.jitter:0,this.attempts=0}R.prototype.duration=function(){var n=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),t=Math.floor(e*this.jitter*n);n=Math.floor(e*10)&1?n+t:n-t}return Math.min(n,this.max)|0};R.prototype.reset=function(){this.attempts=0};R.prototype.setMin=function(n){this.ms=n};R.prototype.setMax=function(n){this.max=n};R.prototype.setJitter=function(n){this.jitter=n};class j extends u{constructor(e,t){var s;super(),this.nsps={},this.subs=[],e&&typeof e=="object"&&(t=e,e=void 0),t=t||{},t.path=t.path||"/socket.io",this.opts=t,U(this,t),this.reconnection(t.reconnection!==!1),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor((s=t.randomizationFactor)!==null&&s!==void 0?s:.5),this.backoff=new R({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(t.timeout==null?2e4:t.timeout),this._readyState="closed",this.uri=e;const i=t.parser||gt;this.encoder=new i.Encoder,this.decoder=new i.Decoder,this._autoConnect=t.autoConnect!==!1,this._autoConnect&&this.open()}reconnection(e){return arguments.length?(this._reconnection=!!e,e||(this.skipReconnect=!0),this):this._reconnection}reconnectionAttempts(e){return e===void 0?this._reconnectionAttempts:(this._reconnectionAttempts=e,this)}reconnectionDelay(e){var t;return e===void 0?this._reconnectionDelay:(this._reconnectionDelay=e,(t=this.backoff)===null||t===void 0||t.setMin(e),this)}randomizationFactor(e){var t;return e===void 0?this._randomizationFactor:(this._randomizationFactor=e,(t=this.backoff)===null||t===void 0||t.setJitter(e),this)}reconnectionDelayMax(e){var t;return e===void 0?this._reconnectionDelayMax:(this._reconnectionDelayMax=e,(t=this.backoff)===null||t===void 0||t.setMax(e),this)}timeout(e){return arguments.length?(this._timeout=e,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&this.backoff.attempts===0&&this.reconnect()}open(e){if(~this._readyState.indexOf("open"))return this;this.engine=new rt(this.uri,this.opts);const t=this.engine,s=this;this._readyState="opening",this.skipReconnect=!1;const i=g(t,"open",function(){s.onopen(),e&&e()}),r=c=>{this.cleanup(),this._readyState="closed",this.emitReserved("error",c),e?e(c):this.maybeReconnectOnOpen()},o=g(t,"error",r);if(this._timeout!==!1){const c=this._timeout,h=this.setTimeoutFn(()=>{i(),r(new Error("timeout")),t.close()},c);this.opts.autoUnref&&h.unref(),this.subs.push(()=>{this.clearTimeoutFn(h)})}return this.subs.push(i),this.subs.push(o),this}connect(e){return this.open(e)}onopen(){this.cleanup(),this._readyState="open",this.emitReserved("open");const e=this.engine;this.subs.push(g(e,"ping",this.onping.bind(this)),g(e,"data",this.ondata.bind(this)),g(e,"error",this.onerror.bind(this)),g(e,"close",this.onclose.bind(this)),g(this.decoder,"decoded",this.ondecoded.bind(this)))}onping(){this.emitReserved("ping")}ondata(e){try{this.decoder.add(e)}catch(t){this.onclose("parse error",t)}}ondecoded(e){I(()=>{this.emitReserved("packet",e)},this.setTimeoutFn)}onerror(e){this.emitReserved("error",e)}socket(e,t){let s=this.nsps[e];return s?this._autoConnect&&!s.active&&s.connect():(s=new ke(this,e,t),this.nsps[e]=s),s}_destroy(e){const t=Object.keys(this.nsps);for(const s of t)if(this.nsps[s].active)return;this._close()}_packet(e){const t=this.encoder.encode(e);for(let s=0;se()),this.subs.length=0,this.decoder.destroy()}_close(){this.skipReconnect=!0,this._reconnecting=!1,this.onclose("forced close")}disconnect(){return this._close()}onclose(e,t){var s;this.cleanup(),(s=this.engine)===null||s===void 0||s.close(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",e,t),this._reconnection&&!this.skipReconnect&&this.reconnect()}reconnect(){if(this._reconnecting||this.skipReconnect)return this;const e=this;if(this.backoff.attempts>=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{const t=this.backoff.duration();this._reconnecting=!0;const s=this.setTimeoutFn(()=>{e.skipReconnect||(this.emitReserved("reconnect_attempt",e.backoff.attempts),!e.skipReconnect&&e.open(i=>{i?(e._reconnecting=!1,e.reconnect(),this.emitReserved("reconnect_error",i)):e.onreconnect()}))},t);this.opts.autoUnref&&s.unref(),this.subs.push(()=>{this.clearTimeoutFn(s)})}}onreconnect(){const e=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",e)}}const O={};function D(n,e){typeof n=="object"&&(e=n,n=void 0),e=e||{};const t=ot(n,e.path||"/socket.io"),s=t.source,i=t.id,r=t.path,o=O[i]&&r in O[i].nsps,c=e.forceNew||e["force new connection"]||e.multiplex===!1||o;let h;return c?h=new j(s,e):(O[i]||(O[i]=new j(s,e)),h=O[i]),t.query&&!e.query&&(e.query=t.queryKey),h.socket(t.path,e)}Object.assign(D,{Manager:j,Socket:ke,io:D,connect:D});let E=null;function _t(){if(typeof window>"u")return"http://localhost:8000";const n=window.location.origin;return n==="http://localhost:5173"||n==="http://127.0.0.1:5173"?"http://localhost:8000":n}function Tt(){return E||(E=D(_t(),{transports:["websocket","polling"],reconnectionAttempts:5,reconnectionDelay:1500}),E.on("connect",()=>console.log("[socket] connected",E==null?void 0:E.id)),E.on("disconnect",n=>console.log("[socket] disconnected",n)),E.on("connect_error",n=>console.error("[socket] error",n.message))),E}export{_t as b,Et as d,vt as e,Tt as g,kt as u}; diff --git a/frontend/build/_app/immutable/chunks/BWggeOnc.js b/frontend/build/_app/immutable/chunks/BWggeOnc.js new file mode 100644 index 0000000000000000000000000000000000000000..d101c57bb0ba453efd6b32a8814b7ce3471d658c --- /dev/null +++ b/frontend/build/_app/immutable/chunks/BWggeOnc.js @@ -0,0 +1 @@ +function T(){}function C(t,n){for(const e in n)t[e]=n[e];return t}function P(t){return t()}function K(){return Object.create(null)}function q(t){t.forEach(P)}function Q(t){return typeof t=="function"}function V(t,n){return t!=t?n==n:t!==n||t&&typeof t=="object"||typeof t=="function"}let h;function X(t,n){return t===n?!0:(h||(h=document.createElement("a")),h.href=n,t===h.href)}function Y(t){return Object.keys(t).length===0}function H(t,...n){if(t==null){for(const i of n)i(void 0);return T}const e=t.subscribe(...n);return e.unsubscribe?()=>e.unsubscribe():e}function Z(t,n,e){t.$$.on_destroy.push(H(n,e))}function $(t,n,e,i){if(t){const c=k(t,n,e,i);return t[0](c)}}function k(t,n,e,i){return t[1]&&i?C(e.ctx.slice(),t[1](i(n))):e.ctx}function tt(t,n,e,i){return t[2],n.dirty}function nt(t,n,e,i,c,s){if(c){const r=k(n,e,i,s);t.p(r,c)}}function et(t){if(t.ctx.length>32){const n=[],e=t.ctx.length/32;for(let i=0;i>1);e(c)<=i?t=c+1:n=c}return t}function L(t){if(t.hydrate_init)return;t.hydrate_init=!0;let n=t.childNodes;if(t.nodeName==="HEAD"){const l=[];for(let u=0;u0&&n[e[c]].claim_order<=u?c+1:B(1,c,D=>n[e[D]].claim_order,u))-1;i[l]=e[a]+1;const E=a+1;e[E]=l,c=Math.max(E,c)}const s=[],r=[];let o=n.length-1;for(let l=e[c]+1;l!=0;l=i[l-1]){for(s.push(n[l-1]);o>=l;o--)r.push(n[o]);o--}for(;o>=0;o--)r.push(n[o]);s.reverse(),r.sort((l,u)=>l.claim_order-u.claim_order);for(let l=0,u=0;l=s[u].claim_order;)u++;const a=ut.removeEventListener(n,e,i)}function ft(t){return function(n){return n.stopPropagation(),t.call(this,n)}}function _t(t,n,e){e==null?t.removeAttribute(n):t.getAttribute(n)!==e&&t.setAttribute(n,e)}function dt(t){return t.dataset.svelteH}function ht(t){return Array.from(t.childNodes)}function F(t){t.claim_info===void 0&&(t.claim_info={last_index:0,total_claimed:0})}function A(t,n,e,i,c=!1){F(t);const s=(()=>{for(let r=t.claim_info.last_index;r=0;r--){const o=t[r];if(n(o)){const l=e(o);return l===void 0?t.splice(r,1):t[r]=l,c?l===void 0&&t.claim_info.last_index--:t.claim_info.last_index=r,o}}return i()})();return s.claim_order=t.claim_info.total_claimed,t.claim_info.total_claimed+=1,s}function S(t,n,e,i){return A(t,c=>c.nodeName===n,c=>{const s=[];for(let r=0;rc.removeAttribute(r))},()=>i(n))}function mt(t,n,e){return S(t,n,e,M)}function pt(t,n,e){return S(t,n,e,z)}function I(t,n){return A(t,e=>e.nodeType===3,e=>{const i=""+n;if(e.data.startsWith(i)){if(e.data.length!==i.length)return e.splitText(i.length)}else e.data=i},()=>v(n),!0)}function bt(t){return I(t," ")}function yt(t,n){n=""+n,t.data!==n&&(t.data=n)}function gt(t,n){t.value=n??""}function xt(t,n,e,i){e==null?t.style.removeProperty(n):t.style.setProperty(n,e,"")}function vt(t,n,e){t.classList.toggle(n,!!e)}function R(t,n,{bubbles:e=!1,cancelable:i=!1}={}){return new CustomEvent(t,{detail:n,bubbles:e,cancelable:i})}function Et(t,n){const e=[];let i=0;for(const c of n.childNodes)if(c.nodeType===8){const s=c.textContent.trim();s===`HEAD_${t}_END`?(i-=1,e.push(c)):s===`HEAD_${t}_START`&&(i+=1,e.push(c))}else i>0&&e.push(c);return e}function wt(t,n){return new t(n)}let m;function y(t){m=t}function b(){if(!m)throw new Error("Function called outside component initialization");return m}function Nt(t){b().$$.on_mount.push(t)}function kt(t){b().$$.after_update.push(t)}function At(t){b().$$.on_destroy.push(t)}function St(){const t=b();return(n,e,{cancelable:i=!1}={})=>{const c=t.$$.callbacks[n];if(c){const s=R(n,e,{cancelable:i});return c.slice().forEach(r=>{r.call(t,s)}),!s.defaultPrevented}return!0}}const d=[],w=[];let _=[];const N=[],j=Promise.resolve();let x=!1;function U(){x||(x=!0,j.then(G))}function jt(){return U(),j}function W(t){_.push(t)}const g=new Set;let f=0;function G(){if(f!==0)return;const t=m;do{try{for(;ft.indexOf(i)===-1?n.push(i):e.push(i)),e.forEach(i=>i()),_=n}export{vt as A,w as B,st as C,xt as D,pt as E,ut as F,z as G,ft as H,kt as I,jt as J,wt as K,gt as L,Q as M,Y as N,m as O,K as P,G as Q,y as R,Dt as S,W as T,P as U,it as V,ct as W,d as X,U as Y,St as Z,H as _,tt as a,yt as b,$ as c,lt as d,O as e,mt as f,et as g,ht as h,rt as i,I as j,bt as k,M as l,ot as m,T as n,Z as o,_t as p,Et as q,dt as r,V as s,v as t,nt as u,Nt as v,At as w,q as x,at as y,X as z}; diff --git a/frontend/build/_app/immutable/chunks/BX82rj4I.js b/frontend/build/_app/immutable/chunks/BX82rj4I.js deleted file mode 100644 index 30945cfe9fed1e1170da66b5f7c4d869162998ed..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/chunks/BX82rj4I.js +++ /dev/null @@ -1 +0,0 @@ -var E=Object.defineProperty;var I=(t,e,n)=>e in t?E(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var _=(t,e,n)=>I(t,typeof e!="symbol"?e+"":e,n);import{w as $,n as c,H as v,I as O,J as b,K as p,h as C,d as L,L as M,M as x,N,O as w,P,Q as R,R as j,S as B,T as H}from"./DQebRFVS.js";const u=new Set;let d;function A(){d={r:0,c:[],p:d}}function D(){d.r||$(d.c),d=d.p}function J(t,e){t&&t.i&&(u.delete(t),t.i(e))}function F(t,e,n,a){if(t&&t.o){if(u.has(t))return;u.add(t),d.c.push(()=>{u.delete(t),a&&(n&&t.d(1),a())}),t.o(e)}else a&&a()}function G(t){t&&t.c()}function W(t,e){t&&t.l(e)}function K(t,e,n){const{fragment:a,after_update:i}=t.$$;a&&a.m(e,n),w(()=>{const f=t.$$.on_mount.map(P).filter(v);t.$$.on_destroy?t.$$.on_destroy.push(...f):$(f),t.$$.on_mount=[]}),i.forEach(w)}function Q(t,e){const n=t.$$;n.fragment!==null&&(N(n.after_update),$(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function T(t,e){t.$$.dirty[0]===-1&&(B.push(t),H(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const y=m.length?m[0]:g;return s.ctx&&i(s.ctx[r],s.ctx[r]=y)&&(!s.skip_bound&&s.bound[r]&&s.bound[r](y),h&&T(t,r)),g}):[],s.update(),h=!0,$(s.before_update),s.fragment=a?a(s.ctx):!1,e.target){if(e.hydrate){R();const r=C(e.target);s.fragment&&s.fragment.l(r),r.forEach(L)}else s.fragment&&s.fragment.c();e.intro&&J(t.$$.fragment),K(t,e.target,e.anchor),j(),M()}x(o)}class Y{constructor(){_(this,"$$");_(this,"$$set")}$destroy(){Q(this,1),this.$destroy=c}$on(e,n){if(!v(n))return c;const a=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return a.push(n),()=>{const i=a.indexOf(n);i!==-1&&a.splice(i,1)}}$set(e){this.$$set&&!O(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}const U="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(U);export{Y as S,J as a,G as b,D as c,Q as d,W as e,A as g,X as i,K as m,F as t}; diff --git a/frontend/build/_app/immutable/chunks/J0QSAnNz.js b/frontend/build/_app/immutable/chunks/B_0L5JHM.js similarity index 83% rename from frontend/build/_app/immutable/chunks/J0QSAnNz.js rename to frontend/build/_app/immutable/chunks/B_0L5JHM.js index 755b3179b6740326ff992cd137ac0276802f79cb..a0c28a73814fb447905cc469320717517441c342 100644 --- a/frontend/build/_app/immutable/chunks/J0QSAnNz.js +++ b/frontend/build/_app/immutable/chunks/B_0L5JHM.js @@ -1 +1 @@ -var gt=Object.defineProperty;var mt=(e,t,n)=>t in e?gt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var A=(e,t,n)=>mt(e,typeof t!="symbol"?t+"":t,n);import{n as K,s as _t,X as wt,w as vt,H as yt,r as De,D as te}from"./DQebRFVS.js";class Se{constructor(t,n){this.status=t,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${t}`}}toString(){return JSON.stringify(this.body)}}class Re{constructor(t,n){this.status=t,this.location=n}}class xe extends Error{constructor(t,n,r){super(r),this.status=t,this.text=n}}new URL("sveltekit-internal://");function bt(e,t){return e==="/"||t==="ignore"?e:t==="never"?e.endsWith("/")?e.slice(0,-1):e:t==="always"&&!e.endsWith("/")?e+"/":e}function kt(e){return e.split("%25").map(decodeURI).join("%25")}function Et(e){for(const t in e)e[t]=decodeURIComponent(e[t]);return e}function ge({href:e}){return e.split("#")[0]}function St(...e){let t=5381;for(const n of e)if(typeof n=="string"){let r=n.length;for(;r;)t=t*33^n.charCodeAt(--r)}else if(ArrayBuffer.isView(n)){const r=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let a=r.length;for(;a;)t=t*33^r[--a]}else throw new TypeError("value must be a string or TypedArray");return(t>>>0).toString(36)}new TextEncoder;new TextDecoder;function Rt(e){const t=atob(e),n=new Uint8Array(t.length);for(let r=0;r((e instanceof Request?e.method:(t==null?void 0:t.method)||"GET")!=="GET"&&M.delete(Le(e)),xt(e,t));const M=new Map;function Lt(e,t){const n=Le(e,t),r=document.querySelector(n);if(r!=null&&r.textContent){r.remove();let{body:a,...i}=JSON.parse(r.textContent);const o=r.getAttribute("data-ttl");return o&&M.set(n,{body:a,init:i,ttl:1e3*Number(o)}),r.getAttribute("data-b64")!==null&&(a=Rt(a)),Promise.resolve(new Response(a,i))}return window.fetch(e,t)}function At(e,t,n){if(M.size>0){const r=Le(e,n),a=M.get(r);if(a){if(performance.now(){const a=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(r);if(a)return t.push({name:a[1],matcher:a[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const i=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(r);if(i)return t.push({name:i[1],matcher:i[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!r)return;const o=r.split(/\[(.+?)\](?!\])/);return"/"+o.map((l,c)=>{if(c%2){if(l.startsWith("x+"))return me(String.fromCharCode(parseInt(l.slice(2),16)));if(l.startsWith("u+"))return me(String.fromCharCode(...l.slice(2).split("-").map(m=>parseInt(m,16))));const h=Ut.exec(l),[,u,w,f,d]=h;return t.push({name:f,matcher:d,optional:!!u,rest:!!w,chained:w?c===1&&o[0]==="":!1}),w?"([^]*?)":u?"([^/]*)?":"([^/]+?)"}return me(l)}).join("")}).join("")}/?$`),params:t}}function $t(e){return e!==""&&!/^\([^)]+\)$/.test(e)}function It(e){return e.slice(1).split("/").filter($t)}function Pt(e,t,n){const r={},a=e.slice(1),i=a.filter(s=>s!==void 0);let o=0;for(let s=0;sh).join("/"),o=0),c===void 0)if(l.rest)c="";else continue;if(!l.matcher||n[l.matcher](c)){r[l.name]=c;const h=t[s+1],u=a[s+1];h&&!h.rest&&h.optional&&u&&l.chained&&(o=0),!h&&!u&&Object.keys(r).length===i.length&&(o=0);continue}if(l.optional&&l.chained){o++;continue}return}if(!o)return r}function me(e){return e.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}function Ot({nodes:e,server_loads:t,dictionary:n,matchers:r}){const a=new Set(t);return Object.entries(n).map(([s,[l,c,h]])=>{const{pattern:u,params:w}=Tt(s),f={id:s,exec:d=>{const m=u.exec(d);if(m)return Pt(m,w,r)},errors:[1,...h||[]].map(d=>e[d]),layouts:[0,...c||[]].map(o),leaf:i(l)};return f.errors.length=f.layouts.length=Math.max(f.errors.length,f.layouts.length),f});function i(s){const l=s<0;return l&&(s=~s),[l,e[s]]}function o(s){return s===void 0?s:[a.has(s),e[s]]}}function He(e,t=JSON.parse){try{return t(sessionStorage[e])}catch{}}function qe(e,t,n=JSON.stringify){const r=n(t);try{sessionStorage[e]=r}catch{}}const D=[];function Ct(e,t){return{subscribe:fe(e,t).subscribe}}function fe(e,t=K){let n;const r=new Set;function a(s){if(_t(e,s)&&(e=s,n)){const l=!D.length;for(const c of r)c[1](),D.push(c,e);if(l){for(let c=0;c{r.delete(c),r.size===0&&n&&(n(),n=null)}}return{set:a,update:i,subscribe:o}}function hn(e,t,n){const r=!Array.isArray(e),a=r?[e]:e;if(!a.every(Boolean))throw new Error("derived() expects stores as input, got a falsy value");const i=t.length<2;return Ct(n,(o,s)=>{let l=!1;const c=[];let h=0,u=K;const w=()=>{if(h)return;u();const d=t(r?c[0]:c,o,s);i?o(d):u=yt(d)?d:K},f=a.map((d,m)=>wt(d,p=>{c[m]=p,h&=~(1<{h|=1<o)}function a(o){n=!1,t.set(o)}function i(o){let s;return t.subscribe(l=>{(s===void 0||n&&l!==s)&&o(s=l)})}return{notify:r,set:a,subscribe:i}}const et={v:()=>{}};function qt(){const{set:e,subscribe:t}=fe(!1);let n;async function r(){clearTimeout(n);try{const a=await fetch(`${jt}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!a.ok)return!1;const o=(await a.json()).version!==Nt;return o&&(e(!0),et.v(),clearTimeout(n)),o}catch{return!1}}return{subscribe:t,check:r}}function de(e,t,n){return e.origin!==ue||!e.pathname.startsWith(t)?!0:n?e.pathname!==location.pathname:!1}function pn(e){}const tt=new Set(["load","prerender","csr","ssr","trailingSlash","config"]);[...tt];const Vt=new Set([...tt]);[...Vt];function zt(e){return e.filter(t=>t!=null)}function Ue(e){return e instanceof Se||e instanceof xe?e.status:500}function Bt(e){return e instanceof xe?e.text:"Internal Error"}let S,W,_e;const Kt=De.toString().includes("$$")||/function \w+\(\) \{\}/.test(De.toString());Kt?(S={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL("https://example.com")},W={current:null},_e={current:!1}):(S=new class{constructor(){A(this,"data",$state.raw({}));A(this,"form",$state.raw(null));A(this,"error",$state.raw(null));A(this,"params",$state.raw({}));A(this,"route",$state.raw({id:null}));A(this,"state",$state.raw({}));A(this,"status",$state.raw(-1));A(this,"url",$state.raw(new URL("https://example.com")))}},W=new class{constructor(){A(this,"current",$state.raw(null))}},_e=new class{constructor(){A(this,"current",$state.raw(!1))}},et.v=()=>_e.current=!0);function Mt(e){Object.assign(S,e)}const Ft=new Set(["icon","shortcut icon","apple-touch-icon"]),I=He(Je)??{},H=He(Ye)??{},$={url:ze({}),page:ze({}),navigating:fe(null),updated:qt()};function Te(e){I[e]=j()}function Gt(e,t){let n=e+1;for(;I[n];)delete I[n],n+=1;for(n=t+1;H[n];)delete H[n],n+=1}function Y(e,t=!1){return t?location.replace(e.href):location.href=e.href,new Promise(()=>{})}async function nt(){if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistration(L||"/");e&&await e.update()}}function Be(){}let $e,ye,ae,U,be,E;const re=[],oe=[];let v=null;function ke(){var e;(e=v==null?void 0:v.fork)==null||e.then(t=>t==null?void 0:t.discard()),v=null}const ee=new Map,at=new Set,Wt=new Set,F=new Set;let _={branch:[],error:null,url:null},rt=!1,se=!1,Ke=!0,J=!1,B=!1,ot=!1,Ie=!1,st,k,x,C;const ie=new Set,Me=new Map;async function wn(e,t,n){var i,o,s,l,c;(i=globalThis.__sveltekit_ofzaah)!=null&&i.data&&globalThis.__sveltekit_ofzaah.data,document.URL!==location.href&&(location.href=location.href),E=e,await((s=(o=e.hooks).init)==null?void 0:s.call(o)),$e=Ot(e),U=document.documentElement,be=t,ye=e.nodes[0],ae=e.nodes[1],ye(),ae(),k=(l=history.state)==null?void 0:l[V],x=(c=history.state)==null?void 0:c[G],k||(k=x=Date.now(),history.replaceState({...history.state,[V]:k,[G]:x},""));const r=I[k];function a(){r&&(history.scrollRestoration="manual",scrollTo(r.x,r.y))}n?(a(),await sn(be,n)):(await z({type:"enter",url:Ae(E.hash?fn(new URL(location.href)):location.href),replace_state:!0}),a()),on()}function Ht(){re.length=0,Ie=!1}function it(e){oe.some(t=>t==null?void 0:t.snapshot)&&(H[e]=oe.map(t=>{var n;return(n=t==null?void 0:t.snapshot)==null?void 0:n.capture()}))}function lt(e){var t;(t=H[e])==null||t.forEach((n,r)=>{var a,i;(i=(a=oe[r])==null?void 0:a.snapshot)==null||i.restore(n)})}function Fe(){Te(k),qe(Je,I),it(x),qe(Ye,H)}async function ct(e,t,n,r){let a;t.invalidateAll&&ke(),await z({type:"goto",url:Ae(e),keepfocus:t.keepFocus,noscroll:t.noScroll,replace_state:t.replaceState,state:t.state,redirect_count:n,nav_token:r,accept:()=>{t.invalidateAll&&(Ie=!0,a=[...Me.keys()]),t.invalidate&&t.invalidate.forEach(rn)}}),t.invalidateAll&&te().then(te).then(()=>{Me.forEach(({resource:i},o)=>{var s;a!=null&&a.includes(o)&&((s=i.refresh)==null||s.call(i))})})}async function Yt(e){if(e.id!==(v==null?void 0:v.id)){ke();const t={};ie.add(t),v={id:e.id,token:t,promise:ut({...e,preload:t}).then(n=>(ie.delete(t),n.type==="loaded"&&n.state.error&&ke(),n)),fork:null}}return v.promise}async function we(e){var n;const t=(n=await he(e,!1))==null?void 0:n.route;t&&await Promise.all([...t.layouts,t.leaf].filter(Boolean).map(r=>r[1]()))}async function ft(e,t,n){var a;_=e.state;const r=document.querySelector("style[data-sveltekit]");if(r&&r.remove(),Object.assign(S,e.props.page),st=new E.root({target:t,props:{...e.props,stores:$,components:oe},hydrate:n,sync:!1}),await Promise.resolve(),lt(x),n){const i={from:null,to:{params:_.params,route:{id:((a=_.route)==null?void 0:a.id)??null},url:new URL(location.href),scroll:I[k]??j()},willUnload:!1,type:"enter",complete:Promise.resolve()};F.forEach(o=>o(i))}se=!0}function le({url:e,params:t,branch:n,status:r,error:a,route:i,form:o}){let s="never";if(L&&(e.pathname===L||e.pathname===L+"/"))s="always";else for(const f of n)(f==null?void 0:f.slash)!==void 0&&(s=f.slash);e.pathname=bt(e.pathname,s),e.search=e.search;const l={type:"loaded",state:{url:e,params:t,branch:n,error:a,route:i},props:{constructors:zt(n).map(f=>f.node.component),page:Ne(S)}};o!==void 0&&(l.props.form=o);let c={},h=!S,u=0;for(let f=0;fs(new URL(o))))return!0;return!1}function Oe(e,t){return(e==null?void 0:e.type)==="data"?e:(e==null?void 0:e.type)==="skip"?t??null:null}function Qt(e,t){if(!e)return new Set(t.searchParams.keys());const n=new Set([...e.searchParams.keys(),...t.searchParams.keys()]);for(const r of n){const a=e.searchParams.getAll(r),i=t.searchParams.getAll(r);a.every(o=>i.includes(o))&&i.every(o=>a.includes(o))&&n.delete(r)}return n}function Zt({error:e,url:t,route:n,params:r}){return{type:"loaded",state:{error:e,url:t,route:n,params:r,branch:[]},props:{page:Ne(S),constructors:[]}}}async function ut({id:e,invalidating:t,url:n,params:r,route:a,preload:i}){if((v==null?void 0:v.id)===e)return ie.delete(v.token),v.promise;const{errors:o,layouts:s,leaf:l}=a,c=[...s,l];o.forEach(p=>p==null?void 0:p().catch(()=>{})),c.forEach(p=>p==null?void 0:p[1]().catch(()=>{}));const h=_.url?e!==ce(_.url):!1,u=_.route?a.id!==_.route.id:!1,w=Qt(_.url,n);let f=!1;const d=c.map(async(p,g)=>{var T;if(!p)return;const b=_.branch[g];return p[1]===(b==null?void 0:b.loader)&&!Xt(f,u,h,w,(T=b.universal)==null?void 0:T.uses,r)?b:(f=!0,Pe({loader:p[1],url:n,params:r,route:a,parent:async()=>{var Q;const P={};for(let y=0;y{});const m=[];for(let p=0;pPromise.resolve({}),server_data_node:Oe(i)}),s={node:await ae(),loader:ae,universal:null,server:null,data:null};return le({url:n,params:a,branch:[o,s],status:e,error:t,route:null})}catch(o){if(o instanceof Re)return ct(new URL(o.location,location.href),{},0);throw o}}async function tn(e){const t=e.href;if(ee.has(t))return ee.get(t);let n;try{const r=(async()=>{let a=await E.hooks.reroute({url:new URL(e),fetch:async(i,o)=>Jt(i,o,e).promise})??e;if(typeof a=="string"){const i=new URL(e);E.hash?i.hash=a:i.pathname=a,a=i}return a})();ee.set(t,r),n=await r}catch{ee.delete(t);return}return n}async function he(e,t){if(e&&!de(e,L,E.hash)){const n=await tn(e);if(!n)return;const r=nn(n);for(const a of $e){const i=a.exec(r);if(i)return{id:ce(e),invalidating:t,route:a,params:Et(i),url:e}}}}function nn(e){return kt(E.hash?e.hash.replace(/^#/,"").replace(/[?#].+/,""):e.pathname.slice(L.length))||"/"}function ce(e){return(E.hash?e.hash.replace(/^#/,""):e.pathname)+e.search}function dt({url:e,type:t,intent:n,delta:r,event:a,scroll:i}){let o=!1;const s=je(_,n,e,t,i??null);r!==void 0&&(s.navigation.delta=r),a!==void 0&&(s.navigation.event=a);const l={...s.navigation,cancel:()=>{o=!0,s.reject(new Error("navigation cancelled"))}};return J||at.forEach(c=>c(l)),o?null:s}async function z({type:e,url:t,popped:n,keepfocus:r,noscroll:a,replace_state:i,state:o={},redirect_count:s=0,nav_token:l={},accept:c=Be,block:h=Be,event:u}){const w=C;C=l;const f=await he(t,!1),d=e==="enter"?je(_,f,t,e):dt({url:t,type:e,delta:n==null?void 0:n.delta,intent:f,scroll:n==null?void 0:n.scroll,event:u});if(!d){h(),C===l&&(C=w);return}const m=k,p=x;c(),J=!0,se&&d.navigation.type!=="enter"&&$.navigating.set(W.current=d.navigation);let g=f&&await ut(f);if(!g){if(de(t,L,E.hash))return await Y(t,i);g=await ht(t,{id:null},await X(new xe(404,"Not Found",`Not found: ${t.pathname}`),{url:t,params:{},route:{id:null}}),404,i)}if(t=(f==null?void 0:f.url)||t,C!==l)return d.reject(new Error("navigation aborted")),!1;if(g.type==="redirect"){if(s<20){await z({type:e,url:new URL(g.location,t),popped:n,keepfocus:r,noscroll:a,replace_state:i,state:o,redirect_count:s+1,nav_token:l}),d.fulfil(void 0);return}g=await Ce({status:500,error:await X(new Error("Redirect loop"),{url:t,params:{},route:{id:null}}),url:t,route:{id:null}})}else g.props.page.status>=400&&await $.updated.check()&&(await nt(),await Y(t,i));if(Ht(),Te(m),it(p),g.props.page.url.pathname!==t.pathname&&(t.pathname=g.props.page.url.pathname),o=n?n.state:o,!n){const y=i?0:1,Z={[V]:k+=y,[G]:x+=y,[Xe]:o};(i?history.replaceState:history.pushState).call(history,Z,"",t),i||Gt(k,x)}const b=f&&(v==null?void 0:v.id)===f.id?v.fork:null;v=null,g.props.page.state=o;let R;if(se){const y=(await Promise.all(Array.from(Wt,N=>N(d.navigation)))).filter(N=>typeof N=="function");if(y.length>0){let N=function(){y.forEach(pe=>{F.delete(pe)})};y.push(N),y.forEach(pe=>{F.add(pe)})}_=g.state,g.props.page&&(g.props.page.url=t);const Z=b&&await b;Z?R=Z.commit():(st.$set(g.props),Mt(g.props.page),R=void 0),ot=!0}else await ft(g,be,!1);const{activeElement:T}=document;await R,await te(),await te();let P=null;if(Ke){const y=n?n.scroll:a?j():null;y?scrollTo(y.x,y.y):(P=t.hash&&document.getElementById(pt(t)))?P.scrollIntoView():scrollTo(0,0)}const Q=document.activeElement!==T&&document.activeElement!==document.body;!r&&!Q&&cn(t,!P),Ke=!0,g.props.page&&Object.assign(S,g.props.page),J=!1,e==="popstate"&<(x),d.fulfil(void 0),d.navigation.to&&(d.navigation.to.scroll=j()),F.forEach(y=>y(d.navigation)),$.navigating.set(W.current=null)}async function ht(e,t,n,r,a){return e.origin===ue&&e.pathname===location.pathname&&!rt?await Ce({status:r,error:n,url:e,route:t}):await Y(e,a)}function an(){let e,t={element:void 0,href:void 0},n;U.addEventListener("mousemove",s=>{const l=s.target;clearTimeout(e),e=setTimeout(()=>{i(l,O.hover)},20)});function r(s){s.defaultPrevented||i(s.composedPath()[0],O.tap)}U.addEventListener("mousedown",r),U.addEventListener("touchstart",r,{passive:!0});const a=new IntersectionObserver(s=>{for(const l of s)l.isIntersecting&&(we(new URL(l.target.href)),a.unobserve(l.target))},{threshold:0});async function i(s,l){const c=Ze(s,U),h=c===t.element&&(c==null?void 0:c.href)===t.href&&l>=n;if(!c||h)return;const{url:u,external:w,download:f}=ve(c,L,E.hash);if(w||f)return;const d=ne(c),m=u&&ce(_.url)===ce(u);if(!(d.reload||m))if(l<=d.preload_data){t={element:c,href:c.href},n=O.tap;const p=await he(u,!1);if(!p)return;Yt(p)}else l<=d.preload_code&&(t={element:c,href:c.href},n=l,we(u))}function o(){a.disconnect();for(const s of U.querySelectorAll("a")){const{url:l,external:c,download:h}=ve(s,L,E.hash);if(c||h)continue;const u=ne(s);u.reload||(u.preload_code===O.viewport&&a.observe(s),u.preload_code===O.eager&&we(l))}}F.add(o),o()}function X(e,t){if(e instanceof Se)return e.body;const n=Ue(e),r=Bt(e);return E.hooks.handleError({error:e,event:t,status:n,message:r})??{message:r}}function vn(e,t={}){return e=new URL(Ae(e)),e.origin!==ue?Promise.reject(new Error("goto: invalid URL")):ct(e,t,0)}function rn(e){if(typeof e=="function")re.push(e);else{const{href:t}=new URL(e,location.href);re.push(n=>n.href===t)}}function on(){var t;history.scrollRestoration="manual",addEventListener("beforeunload",n=>{let r=!1;if(Fe(),!J){const a=je(_,void 0,null,"leave"),i={...a.navigation,cancel:()=>{r=!0,a.reject(new Error("navigation cancelled"))}};at.forEach(o=>o(i))}r?(n.preventDefault(),n.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&Fe()}),(t=navigator.connection)!=null&&t.saveData||an(),U.addEventListener("click",async n=>{if(n.button||n.which!==1||n.metaKey||n.ctrlKey||n.shiftKey||n.altKey||n.defaultPrevented)return;const r=Ze(n.composedPath()[0],U);if(!r)return;const{url:a,external:i,target:o,download:s}=ve(r,L,E.hash);if(!a)return;if(o==="_parent"||o==="_top"){if(window.parent!==window)return}else if(o&&o!=="_self")return;const l=ne(r);if(!(r instanceof SVGAElement)&&a.protocol!==location.protocol&&!(a.protocol==="https:"||a.protocol==="http:")||s)return;const[h,u]=(E.hash?a.hash.replace(/^#/,""):a.href).split("#"),w=h===ge(location);if(i||l.reload&&(!w||!u)){dt({url:a,type:"link",event:n})?J=!0:n.preventDefault();return}if(u!==void 0&&w){const[,f]=_.url.href.split("#");if(f===u){if(n.preventDefault(),u===""||u==="top"&&r.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const d=r.ownerDocument.getElementById(decodeURIComponent(u));d&&(d.scrollIntoView(),d.focus())}return}if(B=!0,Te(k),e(a),!l.replace_state)return;B=!1}n.preventDefault(),await new Promise(f=>{requestAnimationFrame(()=>{setTimeout(f,0)}),setTimeout(f,100)}),await z({type:"link",url:a,keepfocus:l.keepfocus,noscroll:l.noscroll,replace_state:l.replace_state??a.href===location.href,event:n})}),U.addEventListener("submit",n=>{if(n.defaultPrevented)return;const r=HTMLFormElement.prototype.cloneNode.call(n.target),a=n.submitter;if(((a==null?void 0:a.formTarget)||r.target)==="_blank"||((a==null?void 0:a.formMethod)||r.method)!=="get")return;const s=new URL((a==null?void 0:a.hasAttribute("formaction"))&&(a==null?void 0:a.formAction)||r.action);if(de(s,L,!1))return;const l=n.target,c=ne(l);if(c.reload)return;n.preventDefault(),n.stopPropagation();const h=new FormData(l,a);s.search=new URLSearchParams(h).toString(),z({type:"form",url:s,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??s.href===location.href,event:n})}),addEventListener("popstate",async n=>{var r;if(!Ee){if((r=n.state)!=null&&r[V]){const a=n.state[V];if(C={},a===k)return;const i=I[a],o=n.state[Xe]??{},s=new URL(n.state[Dt]??location.href),l=n.state[G],c=_.url?ge(location)===ge(_.url):!1;if(l===x&&(ot||c)){o!==S.state&&(S.state=o),e(s),I[k]=j(),i&&scrollTo(i.x,i.y),k=a;return}const u=a-k;await z({type:"popstate",url:s,popped:{state:o,scroll:i,delta:u},accept:()=>{k=a,x=l},block:()=>{history.go(-u)},nav_token:C,event:n})}else if(!B){const a=new URL(location.href);e(a),E.hash&&location.reload()}}}),addEventListener("hashchange",()=>{B&&(B=!1,history.replaceState({...history.state,[V]:++k,[G]:x},"",location.href))});for(const n of document.querySelectorAll("link"))Ft.has(n.rel)&&(n.href=n.href);addEventListener("pageshow",n=>{n.persisted&&$.navigating.set(W.current=null)});function e(n){_.url=S.url=n,$.page.set(Ne(S)),$.page.notify()}}async function sn(e,{status:t=200,error:n,node_ids:r,params:a,route:i,server_route:o,data:s,form:l}){rt=!0;const c=new URL(location.href);let h;({params:a={},route:i={id:null}}=await he(c,!1)||{}),h=$e.find(({id:f})=>f===i.id);let u,w=!0;try{const f=r.map(async(m,p)=>{const g=s[p];return g!=null&&g.uses&&(g.uses=ln(g.uses)),Pe({loader:E.nodes[m],url:c,params:a,route:i,parent:async()=>{const b={};for(let R=0;R{const s=history.state;Ee=!0,location.replace(new URL(`#${r}`,location.href)),history.replaceState(s,"",e),t&&scrollTo(i,o),Ee=!1})}else{const i=document.body,o=i.getAttribute("tabindex");i.tabIndex=-1,i.focus({preventScroll:!0,focusVisible:!1}),o!==null?i.setAttribute("tabindex",o):i.removeAttribute("tabindex")}const a=getSelection();if(a&&a.type!=="None"){const i=[];for(let o=0;o{if(a.rangeCount===i.length){for(let o=0;o{i=u,o=w});return s.catch(()=>{}),{navigation:{from:{params:e.params,route:{id:((c=e.route)==null?void 0:c.id)??null},url:e.url,scroll:j()},to:n&&{params:(t==null?void 0:t.params)??null,route:{id:((h=t==null?void 0:t.route)==null?void 0:h.id)??null},url:n,scroll:a},willUnload:!t,type:r,complete:s},fulfil:i,reject:o}}function Ne(e){return{data:e.data,error:e.error,form:e.form,params:e.params,route:e.route,state:e.state,status:e.status,url:e.url}}function fn(e){const t=new URL(e);return t.hash=decodeURIComponent(e.hash),t}function pt(e){let t;if(E.hash){const[,,n]=e.hash.split("#",3);t=n??""}else t=e.hash.slice(1);return decodeURIComponent(t)}export{wn as a,hn as d,vn as g,pn as l,$ as s,fe as w}; +var gt=Object.defineProperty;var mt=(e,t,n)=>t in e?gt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var A=(e,t,n)=>mt(e,typeof t!="symbol"?t+"":t,n);import{n as M,s as _t,_ as wt,x as vt,M as yt,v as qe,J as te}from"./BWggeOnc.js";class Se{constructor(t,n){this.status=t,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${t}`}}toString(){return JSON.stringify(this.body)}}class Re{constructor(t,n){this.status=t,this.location=n}}class xe extends Error{constructor(t,n,r){super(r),this.status=t,this.text=n}}new URL("sveltekit-internal://");function bt(e,t){return e==="/"||t==="ignore"?e:t==="never"?e.endsWith("/")?e.slice(0,-1):e:t==="always"&&!e.endsWith("/")?e+"/":e}function kt(e){return e.split("%25").map(decodeURI).join("%25")}function Et(e){for(const t in e)e[t]=decodeURIComponent(e[t]);return e}function ge({href:e}){return e.split("#")[0]}function St(...e){let t=5381;for(const n of e)if(typeof n=="string"){let r=n.length;for(;r;)t=t*33^n.charCodeAt(--r)}else if(ArrayBuffer.isView(n)){const r=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let a=r.length;for(;a;)t=t*33^r[--a]}else throw new TypeError("value must be a string or TypedArray");return(t>>>0).toString(36)}new TextEncoder;new TextDecoder;function Rt(e){const t=atob(e),n=new Uint8Array(t.length);for(let r=0;r((e instanceof Request?e.method:(t==null?void 0:t.method)||"GET")!=="GET"&&z.delete(Le(e)),xt(e,t));const z=new Map;function Lt(e,t){const n=Le(e,t),r=document.querySelector(n);if(r!=null&&r.textContent){r.remove();let{body:a,...i}=JSON.parse(r.textContent);const o=r.getAttribute("data-ttl");return o&&z.set(n,{body:a,init:i,ttl:1e3*Number(o)}),r.getAttribute("data-b64")!==null&&(a=Rt(a)),Promise.resolve(new Response(a,i))}return window.fetch(e,t)}function At(e,t,n){if(z.size>0){const r=Le(e,n),a=z.get(r);if(a){if(performance.now(){const a=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(r);if(a)return t.push({name:a[1],matcher:a[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const i=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(r);if(i)return t.push({name:i[1],matcher:i[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!r)return;const o=r.split(/\[(.+?)\](?!\])/);return"/"+o.map((l,c)=>{if(c%2){if(l.startsWith("x+"))return me(String.fromCharCode(parseInt(l.slice(2),16)));if(l.startsWith("u+"))return me(String.fromCharCode(...l.slice(2).split("-").map(m=>parseInt(m,16))));const h=Ut.exec(l),[,u,w,f,d]=h;return t.push({name:f,matcher:d,optional:!!u,rest:!!w,chained:w?c===1&&o[0]==="":!1}),w?"([^]*?)":u?"([^/]*)?":"([^/]+?)"}return me(l)}).join("")}).join("")}/?$`),params:t}}function $t(e){return e!==""&&!/^\([^)]+\)$/.test(e)}function It(e){return e.slice(1).split("/").filter($t)}function Pt(e,t,n){const r={},a=e.slice(1),i=a.filter(s=>s!==void 0);let o=0;for(let s=0;sh).join("/"),o=0),c===void 0)if(l.rest)c="";else continue;if(!l.matcher||n[l.matcher](c)){r[l.name]=c;const h=t[s+1],u=a[s+1];h&&!h.rest&&h.optional&&u&&l.chained&&(o=0),!h&&!u&&Object.keys(r).length===i.length&&(o=0);continue}if(l.optional&&l.chained){o++;continue}return}if(!o)return r}function me(e){return e.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}function Ot({nodes:e,server_loads:t,dictionary:n,matchers:r}){const a=new Set(t);return Object.entries(n).map(([s,[l,c,h]])=>{const{pattern:u,params:w}=Tt(s),f={id:s,exec:d=>{const m=u.exec(d);if(m)return Pt(m,w,r)},errors:[1,...h||[]].map(d=>e[d]),layouts:[0,...c||[]].map(o),leaf:i(l)};return f.errors.length=f.layouts.length=Math.max(f.errors.length,f.layouts.length),f});function i(s){const l=s<0;return l&&(s=~s),[l,e[s]]}function o(s){return s===void 0?s:[a.has(s),e[s]]}}function Je(e,t=JSON.parse){try{return t(sessionStorage[e])}catch{}}function De(e,t,n=JSON.stringify){const r=n(t);try{sessionStorage[e]=r}catch{}}const q=[];function Ct(e,t){return{subscribe:fe(e,t).subscribe}}function fe(e,t=M){let n;const r=new Set;function a(s){if(_t(e,s)&&(e=s,n)){const l=!q.length;for(const c of r)c[1](),q.push(c,e);if(l){for(let c=0;c{r.delete(c),r.size===0&&n&&(n(),n=null)}}return{set:a,update:i,subscribe:o}}function hn(e,t,n){const r=!Array.isArray(e),a=r?[e]:e;if(!a.every(Boolean))throw new Error("derived() expects stores as input, got a falsy value");const i=t.length<2;return Ct(n,(o,s)=>{let l=!1;const c=[];let h=0,u=M;const w=()=>{if(h)return;u();const d=t(r?c[0]:c,o,s);i?o(d):u=yt(d)?d:M},f=a.map((d,m)=>wt(d,p=>{c[m]=p,h&=~(1<{h|=1<o)}function a(o){n=!1,t.set(o)}function i(o){let s;return t.subscribe(l=>{(s===void 0||n&&l!==s)&&o(s=l)})}return{notify:r,set:a,subscribe:i}}const et={v:()=>{}};function Dt(){const{set:e,subscribe:t}=fe(!1);let n;async function r(){clearTimeout(n);try{const a=await fetch(`${jt}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!a.ok)return!1;const o=(await a.json()).version!==Nt;return o&&(e(!0),et.v(),clearTimeout(n)),o}catch{return!1}}return{subscribe:t,check:r}}function de(e,t,n){return e.origin!==ue||!e.pathname.startsWith(t)?!0:n?e.pathname!==location.pathname:!1}function pn(e){}const tt=new Set(["load","prerender","csr","ssr","trailingSlash","config"]);[...tt];const Vt=new Set([...tt]);[...Vt];function Bt(e){return e.filter(t=>t!=null)}function Ue(e){return e instanceof Se||e instanceof xe?e.status:500}function Kt(e){return e instanceof xe?e.text:"Internal Error"}let S,W,_e;const Mt=qe.toString().includes("$$")||/function \w+\(\) \{\}/.test(qe.toString());Mt?(S={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL("https://example.com")},W={current:null},_e={current:!1}):(S=new class{constructor(){A(this,"data",$state.raw({}));A(this,"form",$state.raw(null));A(this,"error",$state.raw(null));A(this,"params",$state.raw({}));A(this,"route",$state.raw({id:null}));A(this,"state",$state.raw({}));A(this,"status",$state.raw(-1));A(this,"url",$state.raw(new URL("https://example.com")))}},W=new class{constructor(){A(this,"current",$state.raw(null))}},_e=new class{constructor(){A(this,"current",$state.raw(!1))}},et.v=()=>_e.current=!0);function zt(e){Object.assign(S,e)}const Ft=new Set(["icon","shortcut icon","apple-touch-icon"]),I=Je(He)??{},J=Je(Ye)??{},$={url:Be({}),page:Be({}),navigating:fe(null),updated:Dt()};function Te(e){I[e]=j()}function Gt(e,t){let n=e+1;for(;I[n];)delete I[n],n+=1;for(n=t+1;J[n];)delete J[n],n+=1}function Y(e,t=!1){return t?location.replace(e.href):location.href=e.href,new Promise(()=>{})}async function nt(){if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistration(L||"/");e&&await e.update()}}function Ke(){}let $e,ye,ae,U,be,E;const re=[],oe=[];let v=null;function ke(){var e;(e=v==null?void 0:v.fork)==null||e.then(t=>t==null?void 0:t.discard()),v=null}const ee=new Map,at=new Set,Wt=new Set,F=new Set;let _={branch:[],error:null,url:null},rt=!1,se=!1,Me=!0,H=!1,K=!1,ot=!1,Ie=!1,st,k,x,C;const ie=new Set,ze=new Map;async function wn(e,t,n){var i,o,s,l,c;(i=globalThis.__sveltekit_ifmnng)!=null&&i.data&&globalThis.__sveltekit_ifmnng.data,document.URL!==location.href&&(location.href=location.href),E=e,await((s=(o=e.hooks).init)==null?void 0:s.call(o)),$e=Ot(e),U=document.documentElement,be=t,ye=e.nodes[0],ae=e.nodes[1],ye(),ae(),k=(l=history.state)==null?void 0:l[V],x=(c=history.state)==null?void 0:c[G],k||(k=x=Date.now(),history.replaceState({...history.state,[V]:k,[G]:x},""));const r=I[k];function a(){r&&(history.scrollRestoration="manual",scrollTo(r.x,r.y))}n?(a(),await sn(be,n)):(await B({type:"enter",url:Ae(E.hash?fn(new URL(location.href)):location.href),replace_state:!0}),a()),on()}function Jt(){re.length=0,Ie=!1}function it(e){oe.some(t=>t==null?void 0:t.snapshot)&&(J[e]=oe.map(t=>{var n;return(n=t==null?void 0:t.snapshot)==null?void 0:n.capture()}))}function lt(e){var t;(t=J[e])==null||t.forEach((n,r)=>{var a,i;(i=(a=oe[r])==null?void 0:a.snapshot)==null||i.restore(n)})}function Fe(){Te(k),De(He,I),it(x),De(Ye,J)}async function ct(e,t,n,r){let a;t.invalidateAll&&ke(),await B({type:"goto",url:Ae(e),keepfocus:t.keepFocus,noscroll:t.noScroll,replace_state:t.replaceState,state:t.state,redirect_count:n,nav_token:r,accept:()=>{t.invalidateAll&&(Ie=!0,a=[...ze.keys()]),t.invalidate&&t.invalidate.forEach(rn)}}),t.invalidateAll&&te().then(te).then(()=>{ze.forEach(({resource:i},o)=>{var s;a!=null&&a.includes(o)&&((s=i.refresh)==null||s.call(i))})})}async function Yt(e){if(e.id!==(v==null?void 0:v.id)){ke();const t={};ie.add(t),v={id:e.id,token:t,promise:ut({...e,preload:t}).then(n=>(ie.delete(t),n.type==="loaded"&&n.state.error&&ke(),n)),fork:null}}return v.promise}async function we(e){var n;const t=(n=await he(e,!1))==null?void 0:n.route;t&&await Promise.all([...t.layouts,t.leaf].filter(Boolean).map(r=>r[1]()))}async function ft(e,t,n){var a;_=e.state;const r=document.querySelector("style[data-sveltekit]");if(r&&r.remove(),Object.assign(S,e.props.page),st=new E.root({target:t,props:{...e.props,stores:$,components:oe},hydrate:n,sync:!1}),await Promise.resolve(),lt(x),n){const i={from:null,to:{params:_.params,route:{id:((a=_.route)==null?void 0:a.id)??null},url:new URL(location.href),scroll:I[k]??j()},willUnload:!1,type:"enter",complete:Promise.resolve()};F.forEach(o=>o(i))}se=!0}function le({url:e,params:t,branch:n,status:r,error:a,route:i,form:o}){let s="never";if(L&&(e.pathname===L||e.pathname===L+"/"))s="always";else for(const f of n)(f==null?void 0:f.slash)!==void 0&&(s=f.slash);e.pathname=bt(e.pathname,s),e.search=e.search;const l={type:"loaded",state:{url:e,params:t,branch:n,error:a,route:i},props:{constructors:Bt(n).map(f=>f.node.component),page:Ne(S)}};o!==void 0&&(l.props.form=o);let c={},h=!S,u=0;for(let f=0;fs(new URL(o))))return!0;return!1}function Oe(e,t){return(e==null?void 0:e.type)==="data"?e:(e==null?void 0:e.type)==="skip"?t??null:null}function Qt(e,t){if(!e)return new Set(t.searchParams.keys());const n=new Set([...e.searchParams.keys(),...t.searchParams.keys()]);for(const r of n){const a=e.searchParams.getAll(r),i=t.searchParams.getAll(r);a.every(o=>i.includes(o))&&i.every(o=>a.includes(o))&&n.delete(r)}return n}function Zt({error:e,url:t,route:n,params:r}){return{type:"loaded",state:{error:e,url:t,route:n,params:r,branch:[]},props:{page:Ne(S),constructors:[]}}}async function ut({id:e,invalidating:t,url:n,params:r,route:a,preload:i}){if((v==null?void 0:v.id)===e)return ie.delete(v.token),v.promise;const{errors:o,layouts:s,leaf:l}=a,c=[...s,l];o.forEach(p=>p==null?void 0:p().catch(()=>{})),c.forEach(p=>p==null?void 0:p[1]().catch(()=>{}));const h=_.url?e!==ce(_.url):!1,u=_.route?a.id!==_.route.id:!1,w=Qt(_.url,n);let f=!1;const d=c.map(async(p,g)=>{var T;if(!p)return;const b=_.branch[g];return p[1]===(b==null?void 0:b.loader)&&!Xt(f,u,h,w,(T=b.universal)==null?void 0:T.uses,r)?b:(f=!0,Pe({loader:p[1],url:n,params:r,route:a,parent:async()=>{var Q;const P={};for(let y=0;y{});const m=[];for(let p=0;pPromise.resolve({}),server_data_node:Oe(i)}),s={node:await ae(),loader:ae,universal:null,server:null,data:null};return le({url:n,params:a,branch:[o,s],status:e,error:t,route:null})}catch(o){if(o instanceof Re)return ct(new URL(o.location,location.href),{},0);throw o}}async function tn(e){const t=e.href;if(ee.has(t))return ee.get(t);let n;try{const r=(async()=>{let a=await E.hooks.reroute({url:new URL(e),fetch:async(i,o)=>Ht(i,o,e).promise})??e;if(typeof a=="string"){const i=new URL(e);E.hash?i.hash=a:i.pathname=a,a=i}return a})();ee.set(t,r),n=await r}catch{ee.delete(t);return}return n}async function he(e,t){if(e&&!de(e,L,E.hash)){const n=await tn(e);if(!n)return;const r=nn(n);for(const a of $e){const i=a.exec(r);if(i)return{id:ce(e),invalidating:t,route:a,params:Et(i),url:e}}}}function nn(e){return kt(E.hash?e.hash.replace(/^#/,"").replace(/[?#].+/,""):e.pathname.slice(L.length))||"/"}function ce(e){return(E.hash?e.hash.replace(/^#/,""):e.pathname)+e.search}function dt({url:e,type:t,intent:n,delta:r,event:a,scroll:i}){let o=!1;const s=je(_,n,e,t,i??null);r!==void 0&&(s.navigation.delta=r),a!==void 0&&(s.navigation.event=a);const l={...s.navigation,cancel:()=>{o=!0,s.reject(new Error("navigation cancelled"))}};return H||at.forEach(c=>c(l)),o?null:s}async function B({type:e,url:t,popped:n,keepfocus:r,noscroll:a,replace_state:i,state:o={},redirect_count:s=0,nav_token:l={},accept:c=Ke,block:h=Ke,event:u}){const w=C;C=l;const f=await he(t,!1),d=e==="enter"?je(_,f,t,e):dt({url:t,type:e,delta:n==null?void 0:n.delta,intent:f,scroll:n==null?void 0:n.scroll,event:u});if(!d){h(),C===l&&(C=w);return}const m=k,p=x;c(),H=!0,se&&d.navigation.type!=="enter"&&$.navigating.set(W.current=d.navigation);let g=f&&await ut(f);if(!g){if(de(t,L,E.hash))return await Y(t,i);g=await ht(t,{id:null},await X(new xe(404,"Not Found",`Not found: ${t.pathname}`),{url:t,params:{},route:{id:null}}),404,i)}if(t=(f==null?void 0:f.url)||t,C!==l)return d.reject(new Error("navigation aborted")),!1;if(g.type==="redirect"){if(s<20){await B({type:e,url:new URL(g.location,t),popped:n,keepfocus:r,noscroll:a,replace_state:i,state:o,redirect_count:s+1,nav_token:l}),d.fulfil(void 0);return}g=await Ce({status:500,error:await X(new Error("Redirect loop"),{url:t,params:{},route:{id:null}}),url:t,route:{id:null}})}else g.props.page.status>=400&&await $.updated.check()&&(await nt(),await Y(t,i));if(Jt(),Te(m),it(p),g.props.page.url.pathname!==t.pathname&&(t.pathname=g.props.page.url.pathname),o=n?n.state:o,!n){const y=i?0:1,Z={[V]:k+=y,[G]:x+=y,[Xe]:o};(i?history.replaceState:history.pushState).call(history,Z,"",t),i||Gt(k,x)}const b=f&&(v==null?void 0:v.id)===f.id?v.fork:null;v=null,g.props.page.state=o;let R;if(se){const y=(await Promise.all(Array.from(Wt,N=>N(d.navigation)))).filter(N=>typeof N=="function");if(y.length>0){let N=function(){y.forEach(pe=>{F.delete(pe)})};y.push(N),y.forEach(pe=>{F.add(pe)})}_=g.state,g.props.page&&(g.props.page.url=t);const Z=b&&await b;Z?R=Z.commit():(st.$set(g.props),zt(g.props.page),R=void 0),ot=!0}else await ft(g,be,!1);const{activeElement:T}=document;await R,await te(),await te();let P=null;if(Me){const y=n?n.scroll:a?j():null;y?scrollTo(y.x,y.y):(P=t.hash&&document.getElementById(pt(t)))?P.scrollIntoView():scrollTo(0,0)}const Q=document.activeElement!==T&&document.activeElement!==document.body;!r&&!Q&&cn(t,!P),Me=!0,g.props.page&&Object.assign(S,g.props.page),H=!1,e==="popstate"&<(x),d.fulfil(void 0),d.navigation.to&&(d.navigation.to.scroll=j()),F.forEach(y=>y(d.navigation)),$.navigating.set(W.current=null)}async function ht(e,t,n,r,a){return e.origin===ue&&e.pathname===location.pathname&&!rt?await Ce({status:r,error:n,url:e,route:t}):await Y(e,a)}function an(){let e,t={element:void 0,href:void 0},n;U.addEventListener("mousemove",s=>{const l=s.target;clearTimeout(e),e=setTimeout(()=>{i(l,O.hover)},20)});function r(s){s.defaultPrevented||i(s.composedPath()[0],O.tap)}U.addEventListener("mousedown",r),U.addEventListener("touchstart",r,{passive:!0});const a=new IntersectionObserver(s=>{for(const l of s)l.isIntersecting&&(we(new URL(l.target.href)),a.unobserve(l.target))},{threshold:0});async function i(s,l){const c=Ze(s,U),h=c===t.element&&(c==null?void 0:c.href)===t.href&&l>=n;if(!c||h)return;const{url:u,external:w,download:f}=ve(c,L,E.hash);if(w||f)return;const d=ne(c),m=u&&ce(_.url)===ce(u);if(!(d.reload||m))if(l<=d.preload_data){t={element:c,href:c.href},n=O.tap;const p=await he(u,!1);if(!p)return;Yt(p)}else l<=d.preload_code&&(t={element:c,href:c.href},n=l,we(u))}function o(){a.disconnect();for(const s of U.querySelectorAll("a")){const{url:l,external:c,download:h}=ve(s,L,E.hash);if(c||h)continue;const u=ne(s);u.reload||(u.preload_code===O.viewport&&a.observe(s),u.preload_code===O.eager&&we(l))}}F.add(o),o()}function X(e,t){if(e instanceof Se)return e.body;const n=Ue(e),r=Kt(e);return E.hooks.handleError({error:e,event:t,status:n,message:r})??{message:r}}function vn(e,t={}){return e=new URL(Ae(e)),e.origin!==ue?Promise.reject(new Error("goto: invalid URL")):ct(e,t,0)}function rn(e){if(typeof e=="function")re.push(e);else{const{href:t}=new URL(e,location.href);re.push(n=>n.href===t)}}function on(){var t;history.scrollRestoration="manual",addEventListener("beforeunload",n=>{let r=!1;if(Fe(),!H){const a=je(_,void 0,null,"leave"),i={...a.navigation,cancel:()=>{r=!0,a.reject(new Error("navigation cancelled"))}};at.forEach(o=>o(i))}r?(n.preventDefault(),n.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&Fe()}),(t=navigator.connection)!=null&&t.saveData||an(),U.addEventListener("click",async n=>{if(n.button||n.which!==1||n.metaKey||n.ctrlKey||n.shiftKey||n.altKey||n.defaultPrevented)return;const r=Ze(n.composedPath()[0],U);if(!r)return;const{url:a,external:i,target:o,download:s}=ve(r,L,E.hash);if(!a)return;if(o==="_parent"||o==="_top"){if(window.parent!==window)return}else if(o&&o!=="_self")return;const l=ne(r);if(!(r instanceof SVGAElement)&&a.protocol!==location.protocol&&!(a.protocol==="https:"||a.protocol==="http:")||s)return;const[h,u]=(E.hash?a.hash.replace(/^#/,""):a.href).split("#"),w=h===ge(location);if(i||l.reload&&(!w||!u)){dt({url:a,type:"link",event:n})?H=!0:n.preventDefault();return}if(u!==void 0&&w){const[,f]=_.url.href.split("#");if(f===u){if(n.preventDefault(),u===""||u==="top"&&r.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const d=r.ownerDocument.getElementById(decodeURIComponent(u));d&&(d.scrollIntoView(),d.focus())}return}if(K=!0,Te(k),e(a),!l.replace_state)return;K=!1}n.preventDefault(),await new Promise(f=>{requestAnimationFrame(()=>{setTimeout(f,0)}),setTimeout(f,100)}),await B({type:"link",url:a,keepfocus:l.keepfocus,noscroll:l.noscroll,replace_state:l.replace_state??a.href===location.href,event:n})}),U.addEventListener("submit",n=>{if(n.defaultPrevented)return;const r=HTMLFormElement.prototype.cloneNode.call(n.target),a=n.submitter;if(((a==null?void 0:a.formTarget)||r.target)==="_blank"||((a==null?void 0:a.formMethod)||r.method)!=="get")return;const s=new URL((a==null?void 0:a.hasAttribute("formaction"))&&(a==null?void 0:a.formAction)||r.action);if(de(s,L,!1))return;const l=n.target,c=ne(l);if(c.reload)return;n.preventDefault(),n.stopPropagation();const h=new FormData(l,a);s.search=new URLSearchParams(h).toString(),B({type:"form",url:s,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??s.href===location.href,event:n})}),addEventListener("popstate",async n=>{var r;if(!Ee){if((r=n.state)!=null&&r[V]){const a=n.state[V];if(C={},a===k)return;const i=I[a],o=n.state[Xe]??{},s=new URL(n.state[qt]??location.href),l=n.state[G],c=_.url?ge(location)===ge(_.url):!1;if(l===x&&(ot||c)){o!==S.state&&(S.state=o),e(s),I[k]=j(),i&&scrollTo(i.x,i.y),k=a;return}const u=a-k;await B({type:"popstate",url:s,popped:{state:o,scroll:i,delta:u},accept:()=>{k=a,x=l},block:()=>{history.go(-u)},nav_token:C,event:n})}else if(!K){const a=new URL(location.href);e(a),E.hash&&location.reload()}}}),addEventListener("hashchange",()=>{K&&(K=!1,history.replaceState({...history.state,[V]:++k,[G]:x},"",location.href))});for(const n of document.querySelectorAll("link"))Ft.has(n.rel)&&(n.href=n.href);addEventListener("pageshow",n=>{n.persisted&&$.navigating.set(W.current=null)});function e(n){_.url=S.url=n,$.page.set(Ne(S)),$.page.notify()}}async function sn(e,{status:t=200,error:n,node_ids:r,params:a,route:i,server_route:o,data:s,form:l}){rt=!0;const c=new URL(location.href);let h;({params:a={},route:i={id:null}}=await he(c,!1)||{}),h=$e.find(({id:f})=>f===i.id);let u,w=!0;try{const f=r.map(async(m,p)=>{const g=s[p];return g!=null&&g.uses&&(g.uses=ln(g.uses)),Pe({loader:E.nodes[m],url:c,params:a,route:i,parent:async()=>{const b={};for(let R=0;R{const s=history.state;Ee=!0,location.replace(new URL(`#${r}`,location.href)),history.replaceState(s,"",e),t&&scrollTo(i,o),Ee=!1})}else{const i=document.body,o=i.getAttribute("tabindex");i.tabIndex=-1,i.focus({preventScroll:!0,focusVisible:!1}),o!==null?i.setAttribute("tabindex",o):i.removeAttribute("tabindex")}const a=getSelection();if(a&&a.type!=="None"){const i=[];for(let o=0;o{if(a.rangeCount===i.length){for(let o=0;o{i=u,o=w});return s.catch(()=>{}),{navigation:{from:{params:e.params,route:{id:((c=e.route)==null?void 0:c.id)??null},url:e.url,scroll:j()},to:n&&{params:(t==null?void 0:t.params)??null,route:{id:((h=t==null?void 0:t.route)==null?void 0:h.id)??null},url:n,scroll:a},willUnload:!t,type:r,complete:s},fulfil:i,reject:o}}function Ne(e){return{data:e.data,error:e.error,form:e.form,params:e.params,route:e.route,state:e.state,status:e.status,url:e.url}}function fn(e){const t=new URL(e);return t.hash=decodeURIComponent(e.hash),t}function pt(e){let t;if(E.hash){const[,,n]=e.hash.split("#",3);t=n??""}else t=e.hash.slice(1);return decodeURIComponent(t)}export{wn as a,hn as d,vn as g,pn as l,$ as s,fe as w}; diff --git a/frontend/build/_app/immutable/chunks/BxRIPBY0.js b/frontend/build/_app/immutable/chunks/BxRIPBY0.js deleted file mode 100644 index b910f12c91648592da426a57bc70c70b7cd007ab..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/chunks/BxRIPBY0.js +++ /dev/null @@ -1 +0,0 @@ -import{d as H,w as d}from"./J0QSAnNz.js";function ct(n){return(n==null?void 0:n.length)!==void 0?n:Array.from(n)}const m=Object.create(null);m.open="0";m.close="1";m.ping="2";m.pong="3";m.message="4";m.upgrade="5";m.noop="6";const O=Object.create(null);Object.keys(m).forEach(n=>{O[m[n]]=n});const D={type:"error",data:"parser error"},ee=typeof Blob=="function"||typeof Blob<"u"&&Object.prototype.toString.call(Blob)==="[object BlobConstructor]",te=typeof ArrayBuffer=="function",se=n=>typeof ArrayBuffer.isView=="function"?ArrayBuffer.isView(n):n&&n.buffer instanceof ArrayBuffer,K=({type:n,data:e},t,s)=>ee&&e instanceof Blob?t?s(e):X(e,s):te&&(e instanceof ArrayBuffer||se(e))?t?s(e):X(new Blob([e]),s):s(m[n]+(e||"")),X=(n,e)=>{const t=new FileReader;return t.onload=function(){const s=t.result.split(",")[1];e("b"+(s||""))},t.readAsDataURL(n)};function Q(n){return n instanceof Uint8Array?n:n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n.buffer,n.byteOffset,n.byteLength)}let L;function de(n,e){if(ee&&n.data instanceof Blob)return n.data.arrayBuffer().then(Q).then(e);if(te&&(n.data instanceof ArrayBuffer||se(n.data)))return e(Q(n.data));K(n,!1,t=>{L||(L=new TextEncoder),e(L.encode(t))})}const j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",A=typeof Uint8Array>"u"?[]:new Uint8Array(256);for(let n=0;n{let e=n.length*.75,t=n.length,s,i=0,r,o,c,h;n[n.length-1]==="="&&(e--,n[n.length-2]==="="&&e--);const g=new ArrayBuffer(e),f=new Uint8Array(g);for(s=0;s>4,f[i++]=(o&15)<<4|c>>2,f[i++]=(c&3)<<6|h&63;return g},me=typeof ArrayBuffer=="function",W=(n,e)=>{if(typeof n!="string")return{type:"message",data:ne(n,e)};const t=n.charAt(0);return t==="b"?{type:"message",data:ge(n.substring(1),e)}:O[t]?n.length>1?{type:O[t],data:n.substring(1)}:{type:O[t]}:D},ge=(n,e)=>{if(me){const t=ye(n);return ne(t,e)}else return{base64:!0,data:n}},ne=(n,e)=>{switch(e){case"blob":return n instanceof Blob?n:new Blob([n]);case"arraybuffer":default:return n instanceof ArrayBuffer?n:n.buffer}},ie="",_e=(n,e)=>{const t=n.length,s=new Array(t);let i=0;n.forEach((r,o)=>{K(r,!1,c=>{s[o]=c,++i===t&&e(s.join(ie))})})},be=(n,e)=>{const t=n.split(ie),s=[];for(let i=0;i{const s=t.length;let i;if(s<126)i=new Uint8Array(1),new DataView(i.buffer).setUint8(0,s);else if(s<65536){i=new Uint8Array(3);const r=new DataView(i.buffer);r.setUint8(0,126),r.setUint16(1,s)}else{i=new Uint8Array(9);const r=new DataView(i.buffer);r.setUint8(0,127),r.setBigUint64(1,BigInt(s))}n.data&&typeof n.data!="string"&&(i[0]|=128),e.enqueue(i),e.enqueue(t)})}})}let P;function T(n){return n.reduce((e,t)=>e+t.length,0)}function R(n,e){if(n[0].length===e)return n.shift();const t=new Uint8Array(e);let s=0;for(let i=0;iMath.pow(2,21)-1){c.enqueue(D);break}i=f*Math.pow(2,32)+g.getUint32(4),s=3}else{if(T(t)n){c.enqueue(D);break}}}})}const re=4;function u(n){if(n)return Ee(n)}function Ee(n){for(var e in u.prototype)n[e]=u.prototype[e];return n}u.prototype.on=u.prototype.addEventListener=function(n,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+n]=this._callbacks["$"+n]||[]).push(e),this};u.prototype.once=function(n,e){function t(){this.off(n,t),e.apply(this,arguments)}return t.fn=e,this.on(n,t),this};u.prototype.off=u.prototype.removeListener=u.prototype.removeAllListeners=u.prototype.removeEventListener=function(n,e){if(this._callbacks=this._callbacks||{},arguments.length==0)return this._callbacks={},this;var t=this._callbacks["$"+n];if(!t)return this;if(arguments.length==1)return delete this._callbacks["$"+n],this;for(var s,i=0;iPromise.resolve().then(e):(e,t)=>t(e,0),l=typeof self<"u"?self:typeof window<"u"?window:Function("return this")(),ke="arraybuffer";function oe(n,...e){return e.reduce((t,s)=>(n.hasOwnProperty(s)&&(t[s]=n[s]),t),{})}const Ae=l.setTimeout,Te=l.clearTimeout;function N(n,e){e.useNativeTimers?(n.setTimeoutFn=Ae.bind(l),n.clearTimeoutFn=Te.bind(l)):(n.setTimeoutFn=l.setTimeout.bind(l),n.clearTimeoutFn=l.clearTimeout.bind(l))}const Re=1.33;function Oe(n){return typeof n=="string"?Se(n):Math.ceil((n.byteLength||n.size)*Re)}function Se(n){let e=0,t=0;for(let s=0,i=n.length;s=57344?t+=3:(s++,t+=4);return t}function ae(){return Date.now().toString(36).substring(3)+Math.random().toString(36).substring(2,5)}function Be(n){let e="";for(let t in n)n.hasOwnProperty(t)&&(e.length&&(e+="&"),e+=encodeURIComponent(t)+"="+encodeURIComponent(n[t]));return e}function Ce(n){let e={},t=n.split("&");for(let s=0,i=t.length;s{this.readyState="paused",e()};if(this._polling||!this.writable){let s=0;this._polling&&(s++,this.once("pollComplete",function(){--s||t()})),this.writable||(s++,this.once("drain",function(){--s||t()}))}else t()}_poll(){this._polling=!0,this.doPoll(),this.emitReserved("poll")}onData(e){const t=s=>{if(this.readyState==="opening"&&s.type==="open"&&this.onOpen(),s.type==="close")return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(s)};be(e,this.socket.binaryType).forEach(t),this.readyState!=="closed"&&(this._polling=!1,this.emitReserved("pollComplete"),this.readyState==="open"&&this._poll())}doClose(){const e=()=>{this.write([{type:"close"}])};this.readyState==="open"?e():this.once("open",e)}write(e){this.writable=!1,_e(e,t=>{this.doWrite(t,()=>{this.writable=!0,this.emitReserved("drain")})})}uri(){const e=this.opts.secure?"https":"http",t=this.query||{};return this.opts.timestampRequests!==!1&&(t[this.opts.timestampParam]=ae()),!this.supportsBinary&&!t.sid&&(t.b64=1),this.createUri(e,t)}}let ce=!1;try{ce=typeof XMLHttpRequest<"u"&&"withCredentials"in new XMLHttpRequest}catch{}const Le=ce;function Pe(){}class qe extends Ne{constructor(e){if(super(e),typeof location<"u"){const t=location.protocol==="https:";let s=location.port;s||(s=t?"443":"80"),this.xd=typeof location<"u"&&e.hostname!==location.hostname||s!==e.port}}doWrite(e,t){const s=this.request({method:"POST",data:e});s.on("success",t),s.on("error",(i,r)=>{this.onError("xhr post error",i,r)})}doPoll(){const e=this.request();e.on("data",this.onData.bind(this)),e.on("error",(t,s)=>{this.onError("xhr poll error",t,s)}),this.pollXhr=e}}class y extends u{constructor(e,t,s){super(),this.createRequest=e,N(this,s),this._opts=s,this._method=s.method||"GET",this._uri=t,this._data=s.data!==void 0?s.data:null,this._create()}_create(){var e;const t=oe(this._opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");t.xdomain=!!this._opts.xd;const s=this._xhr=this.createRequest(t);try{s.open(this._method,this._uri,!0);try{if(this._opts.extraHeaders){s.setDisableHeaderCheck&&s.setDisableHeaderCheck(!0);for(let i in this._opts.extraHeaders)this._opts.extraHeaders.hasOwnProperty(i)&&s.setRequestHeader(i,this._opts.extraHeaders[i])}}catch{}if(this._method==="POST")try{s.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch{}try{s.setRequestHeader("Accept","*/*")}catch{}(e=this._opts.cookieJar)===null||e===void 0||e.addCookies(s),"withCredentials"in s&&(s.withCredentials=this._opts.withCredentials),this._opts.requestTimeout&&(s.timeout=this._opts.requestTimeout),s.onreadystatechange=()=>{var i;s.readyState===3&&((i=this._opts.cookieJar)===null||i===void 0||i.parseCookies(s.getResponseHeader("set-cookie"))),s.readyState===4&&(s.status===200||s.status===1223?this._onLoad():this.setTimeoutFn(()=>{this._onError(typeof s.status=="number"?s.status:0)},0))},s.send(this._data)}catch(i){this.setTimeoutFn(()=>{this._onError(i)},0);return}typeof document<"u"&&(this._index=y.requestsCount++,y.requests[this._index]=this)}_onError(e){this.emitReserved("error",e,this._xhr),this._cleanup(!0)}_cleanup(e){if(!(typeof this._xhr>"u"||this._xhr===null)){if(this._xhr.onreadystatechange=Pe,e)try{this._xhr.abort()}catch{}typeof document<"u"&&delete y.requests[this._index],this._xhr=null}}_onLoad(){const e=this._xhr.responseText;e!==null&&(this.emitReserved("data",e),this.emitReserved("success"),this._cleanup())}abort(){this._cleanup()}}y.requestsCount=0;y.requests={};if(typeof document<"u"){if(typeof attachEvent=="function")attachEvent("onunload",G);else if(typeof addEventListener=="function"){const n="onpagehide"in l?"pagehide":"unload";addEventListener(n,G,!1)}}function G(){for(let n in y.requests)y.requests.hasOwnProperty(n)&&y.requests[n].abort()}const De=function(){const n=he({xdomain:!1});return n&&n.responseType!==null}();class Ie extends qe{constructor(e){super(e);const t=e&&e.forceBase64;this.supportsBinary=De&&!t}request(e={}){return Object.assign(e,{xd:this.xd},this.opts),new y(he,this.uri(),e)}}function he(n){const e=n.xdomain;try{if(typeof XMLHttpRequest<"u"&&(!e||Le))return new XMLHttpRequest}catch{}if(!e)try{return new l[["Active"].concat("Object").join("X")]("Microsoft.XMLHTTP")}catch{}}const ue=typeof navigator<"u"&&typeof navigator.product=="string"&&navigator.product.toLowerCase()==="reactnative";class Ue extends Y{get name(){return"websocket"}doOpen(){const e=this.uri(),t=this.opts.protocols,s=ue?{}:oe(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(s.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(e,t,s)}catch(i){return this.emitReserved("error",i)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=e=>this.onClose({description:"websocket connection closed",context:e}),this.ws.onmessage=e=>this.onData(e.data),this.ws.onerror=e=>this.onError("websocket error",e)}write(e){this.writable=!1;for(let t=0;t{try{this.doWrite(s,r)}catch{}i&&x(()=>{this.writable=!0,this.emitReserved("drain")},this.setTimeoutFn)})}}doClose(){typeof this.ws<"u"&&(this.ws.onerror=()=>{},this.ws.close(),this.ws=null)}uri(){const e=this.opts.secure?"wss":"ws",t=this.query||{};return this.opts.timestampRequests&&(t[this.opts.timestampParam]=ae()),this.supportsBinary||(t.b64=1),this.createUri(e,t)}}const q=l.WebSocket||l.MozWebSocket;class Fe extends Ue{createSocket(e,t,s){return ue?new q(e,t,s):t?new q(e,t):new q(e)}doWrite(e,t){this.ws.send(t)}}class Ve extends Y{get name(){return"webtransport"}doOpen(){try{this._transport=new WebTransport(this.createUri("https"),this.opts.transportOptions[this.name])}catch(e){return this.emitReserved("error",e)}this._transport.closed.then(()=>{this.onClose()}).catch(e=>{this.onError("webtransport error",e)}),this._transport.ready.then(()=>{this._transport.createBidirectionalStream().then(e=>{const t=ve(Number.MAX_SAFE_INTEGER,this.socket.binaryType),s=e.readable.pipeThrough(t).getReader(),i=we();i.readable.pipeTo(e.writable),this._writer=i.writable.getWriter();const r=()=>{s.read().then(({done:c,value:h})=>{c||(this.onPacket(h),r())}).catch(c=>{})};r();const o={type:"open"};this.query.sid&&(o.data=`{"sid":"${this.query.sid}"}`),this._writer.write(o).then(()=>this.onOpen())})})}write(e){this.writable=!1;for(let t=0;t{i&&x(()=>{this.writable=!0,this.emitReserved("drain")},this.setTimeoutFn)})}}doClose(){var e;(e=this._transport)===null||e===void 0||e.close()}}const Me={websocket:Fe,webtransport:Ve,polling:Ie},He=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,Ke=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];function I(n){if(n.length>8e3)throw"URI too long";const e=n,t=n.indexOf("["),s=n.indexOf("]");t!=-1&&s!=-1&&(n=n.substring(0,t)+n.substring(t,s).replace(/:/g,";")+n.substring(s,n.length));let i=He.exec(n||""),r={},o=14;for(;o--;)r[Ke[o]]=i[o]||"";return t!=-1&&s!=-1&&(r.source=e,r.host=r.host.substring(1,r.host.length-1).replace(/;/g,":"),r.authority=r.authority.replace("[","").replace("]","").replace(/;/g,":"),r.ipv6uri=!0),r.pathNames=We(r,r.path),r.queryKey=Ye(r,r.query),r}function We(n,e){const t=/\/{2,9}/g,s=e.replace(t,"/").split("/");return(e.slice(0,1)=="/"||e.length===0)&&s.splice(0,1),e.slice(-1)=="/"&&s.splice(s.length-1,1),s}function Ye(n,e){const t={};return e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(s,i,r){i&&(t[i]=r)}),t}const U=typeof addEventListener=="function"&&typeof removeEventListener=="function",S=[];U&&addEventListener("offline",()=>{S.forEach(n=>n())},!1);class w extends u{constructor(e,t){if(super(),this.binaryType=ke,this.writeBuffer=[],this._prevBufferLen=0,this._pingInterval=-1,this._pingTimeout=-1,this._maxPayload=-1,this._pingTimeoutTime=1/0,e&&typeof e=="object"&&(t=e,e=null),e){const s=I(e);t.hostname=s.host,t.secure=s.protocol==="https"||s.protocol==="wss",t.port=s.port,s.query&&(t.query=s.query)}else t.host&&(t.hostname=I(t.host).host);N(this,t),this.secure=t.secure!=null?t.secure:typeof location<"u"&&location.protocol==="https:",t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.hostname=t.hostname||(typeof location<"u"?location.hostname:"localhost"),this.port=t.port||(typeof location<"u"&&location.port?location.port:this.secure?"443":"80"),this.transports=[],this._transportsByName={},t.transports.forEach(s=>{const i=s.prototype.name;this.transports.push(i),this._transportsByName[i]=s}),this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},t),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),typeof this.opts.query=="string"&&(this.opts.query=Ce(this.opts.query)),U&&(this.opts.closeOnBeforeunload&&(this._beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this._beforeunloadEventListener,!1)),this.hostname!=="localhost"&&(this._offlineEventListener=()=>{this._onClose("transport close",{description:"network connection lost"})},S.push(this._offlineEventListener))),this.opts.withCredentials&&(this._cookieJar=void 0),this._open()}createTransport(e){const t=Object.assign({},this.opts.query);t.EIO=re,t.transport=e,this.id&&(t.sid=this.id);const s=Object.assign({},this.opts,{query:t,socket:this,hostname:this.hostname,secure:this.secure,port:this.port},this.opts.transportOptions[e]);return new this._transportsByName[e](s)}_open(){if(this.transports.length===0){this.setTimeoutFn(()=>{this.emitReserved("error","No transports available")},0);return}const e=this.opts.rememberUpgrade&&w.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1?"websocket":this.transports[0];this.readyState="opening";const t=this.createTransport(e);t.open(),this.setTransport(t)}setTransport(e){this.transport&&this.transport.removeAllListeners(),this.transport=e,e.on("drain",this._onDrain.bind(this)).on("packet",this._onPacket.bind(this)).on("error",this._onError.bind(this)).on("close",t=>this._onClose("transport close",t))}onOpen(){this.readyState="open",w.priorWebsocketSuccess=this.transport.name==="websocket",this.emitReserved("open"),this.flush()}_onPacket(e){if(this.readyState==="opening"||this.readyState==="open"||this.readyState==="closing")switch(this.emitReserved("packet",e),this.emitReserved("heartbeat"),e.type){case"open":this.onHandshake(JSON.parse(e.data));break;case"ping":this._sendPacket("pong"),this.emitReserved("ping"),this.emitReserved("pong"),this._resetPingTimeout();break;case"error":const t=new Error("server error");t.code=e.data,this._onError(t);break;case"message":this.emitReserved("data",e.data),this.emitReserved("message",e.data);break}}onHandshake(e){this.emitReserved("handshake",e),this.id=e.sid,this.transport.query.sid=e.sid,this._pingInterval=e.pingInterval,this._pingTimeout=e.pingTimeout,this._maxPayload=e.maxPayload,this.onOpen(),this.readyState!=="closed"&&this._resetPingTimeout()}_resetPingTimeout(){this.clearTimeoutFn(this._pingTimeoutTimer);const e=this._pingInterval+this._pingTimeout;this._pingTimeoutTime=Date.now()+e,this._pingTimeoutTimer=this.setTimeoutFn(()=>{this._onClose("ping timeout")},e),this.opts.autoUnref&&this._pingTimeoutTimer.unref()}_onDrain(){this.writeBuffer.splice(0,this._prevBufferLen),this._prevBufferLen=0,this.writeBuffer.length===0?this.emitReserved("drain"):this.flush()}flush(){if(this.readyState!=="closed"&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const e=this._getWritablePackets();this.transport.send(e),this._prevBufferLen=e.length,this.emitReserved("flush")}}_getWritablePackets(){if(!(this._maxPayload&&this.transport.name==="polling"&&this.writeBuffer.length>1))return this.writeBuffer;let t=1;for(let s=0;s0&&t>this._maxPayload)return this.writeBuffer.slice(0,s);t+=2}return this.writeBuffer}_hasPingExpired(){if(!this._pingTimeoutTime)return!0;const e=Date.now()>this._pingTimeoutTime;return e&&(this._pingTimeoutTime=0,x(()=>{this._onClose("ping timeout")},this.setTimeoutFn)),e}write(e,t,s){return this._sendPacket("message",e,t,s),this}send(e,t,s){return this._sendPacket("message",e,t,s),this}_sendPacket(e,t,s,i){if(typeof t=="function"&&(i=t,t=void 0),typeof s=="function"&&(i=s,s=null),this.readyState==="closing"||this.readyState==="closed")return;s=s||{},s.compress=s.compress!==!1;const r={type:e,data:t,options:s};this.emitReserved("packetCreate",r),this.writeBuffer.push(r),i&&this.once("flush",i),this.flush()}close(){const e=()=>{this._onClose("forced close"),this.transport.close()},t=()=>{this.off("upgrade",t),this.off("upgradeError",t),e()},s=()=>{this.once("upgrade",t),this.once("upgradeError",t)};return(this.readyState==="opening"||this.readyState==="open")&&(this.readyState="closing",this.writeBuffer.length?this.once("drain",()=>{this.upgrading?s():e()}):this.upgrading?s():e()),this}_onError(e){if(w.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&this.readyState==="opening")return this.transports.shift(),this._open();this.emitReserved("error",e),this._onClose("transport error",e)}_onClose(e,t){if(this.readyState==="opening"||this.readyState==="open"||this.readyState==="closing"){if(this.clearTimeoutFn(this._pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),U&&(this._beforeunloadEventListener&&removeEventListener("beforeunload",this._beforeunloadEventListener,!1),this._offlineEventListener)){const s=S.indexOf(this._offlineEventListener);s!==-1&&S.splice(s,1)}this.readyState="closed",this.id=null,this.emitReserved("close",e,t),this.writeBuffer=[],this._prevBufferLen=0}}}w.protocol=re;class ze extends w{constructor(){super(...arguments),this._upgrades=[]}onOpen(){if(super.onOpen(),this.readyState==="open"&&this.opts.upgrade)for(let e=0;e{s||(t.send([{type:"ping",data:"probe"}]),t.once("packet",b=>{if(!s)if(b.type==="pong"&&b.data==="probe"){if(this.upgrading=!0,this.emitReserved("upgrading",t),!t)return;w.priorWebsocketSuccess=t.name==="websocket",this.transport.pause(()=>{s||this.readyState!=="closed"&&(f(),this.setTransport(t),t.send([{type:"upgrade"}]),this.emitReserved("upgrade",t),t=null,this.upgrading=!1,this.flush())})}else{const E=new Error("probe error");E.transport=t.name,this.emitReserved("upgradeError",E)}}))};function r(){s||(s=!0,f(),t.close(),t=null)}const o=b=>{const E=new Error("probe error: "+b);E.transport=t.name,r(),this.emitReserved("upgradeError",E)};function c(){o("transport closed")}function h(){o("socket closed")}function g(b){t&&b.name!==t.name&&r()}const f=()=>{t.removeListener("open",i),t.removeListener("error",o),t.removeListener("close",c),this.off("close",h),this.off("upgrading",g)};t.once("open",i),t.once("error",o),t.once("close",c),this.once("close",h),this.once("upgrading",g),this._upgrades.indexOf("webtransport")!==-1&&e!=="webtransport"?this.setTimeoutFn(()=>{s||t.open()},200):t.open()}onHandshake(e){this._upgrades=this._filterUpgrades(e.upgrades),super.onHandshake(e)}_filterUpgrades(e){const t=[];for(let s=0;sMe[i]).filter(i=>!!i)),super(e,s)}};function Je(n,e="",t){let s=n;t=t||typeof location<"u"&&location,n==null&&(n=t.protocol+"//"+t.host),typeof n=="string"&&(n.charAt(0)==="/"&&(n.charAt(1)==="/"?n=t.protocol+n:n=t.host+n),/^(https?|wss?):\/\//.test(n)||(typeof t<"u"?n=t.protocol+"//"+n:n="https://"+n),s=I(n)),s.port||(/^(http|ws)$/.test(s.protocol)?s.port="80":/^(http|ws)s$/.test(s.protocol)&&(s.port="443")),s.path=s.path||"/";const r=s.host.indexOf(":")!==-1?"["+s.host+"]":s.host;return s.id=s.protocol+"://"+r+":"+s.port+e,s.href=s.protocol+"://"+r+(t&&t.port===s.port?"":":"+s.port),s}const Xe=typeof ArrayBuffer=="function",Qe=n=>typeof ArrayBuffer.isView=="function"?ArrayBuffer.isView(n):n.buffer instanceof ArrayBuffer,fe=Object.prototype.toString,je=typeof Blob=="function"||typeof Blob<"u"&&fe.call(Blob)==="[object BlobConstructor]",Ge=typeof File=="function"||typeof File<"u"&&fe.call(File)==="[object FileConstructor]";function z(n){return Xe&&(n instanceof ArrayBuffer||Qe(n))||je&&n instanceof Blob||Ge&&n instanceof File}function B(n,e){if(!n||typeof n!="object")return!1;if(Array.isArray(n)){for(let t=0,s=n.length;t=0&&n.num{delete this.acks[e];for(let c=0;c{this.io.clearTimeoutFn(r),t.apply(this,c)};o.withError=!0,this.acks[e]=o}emitWithAck(e,...t){return new Promise((s,i)=>{const r=(o,c)=>o?i(o):s(c);r.withError=!0,t.push(r),this.emit(e,...t)})}_addToQueue(e){let t;typeof e[e.length-1]=="function"&&(t=e.pop());const s={id:this._queueSeq++,tryCount:0,pending:!1,args:e,flags:Object.assign({fromQueue:!0},this.flags)};e.push((i,...r)=>(this._queue[0],i!==null?s.tryCount>this._opts.retries&&(this._queue.shift(),t&&t(i)):(this._queue.shift(),t&&t(null,...r)),s.pending=!1,this._drainQueue())),this._queue.push(s),this._drainQueue()}_drainQueue(e=!1){if(!this.connected||this._queue.length===0)return;const t=this._queue[0];t.pending&&!e||(t.pending=!0,t.tryCount++,this.flags=t.flags,this.emit.apply(this,t.args))}packet(e){e.nsp=this.nsp,this.io._packet(e)}onopen(){typeof this.auth=="function"?this.auth(e=>{this._sendConnectPacket(e)}):this._sendConnectPacket(this.auth)}_sendConnectPacket(e){this.packet({type:a.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},e):e})}onerror(e){this.connected||this.emitReserved("connect_error",e)}onclose(e,t){this.connected=!1,delete this.id,this.emitReserved("disconnect",e,t),this._clearAcks()}_clearAcks(){Object.keys(this.acks).forEach(e=>{if(!this.sendBuffer.some(s=>String(s.id)===e)){const s=this.acks[e];delete this.acks[e],s.withError&&s.call(this,new Error("socket has been disconnected"))}})}onpacket(e){if(e.nsp===this.nsp)switch(e.type){case a.CONNECT:e.data&&e.data.sid?this.onconnect(e.data.sid,e.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case a.EVENT:case a.BINARY_EVENT:this.onevent(e);break;case a.ACK:case a.BINARY_ACK:this.onack(e);break;case a.DISCONNECT:this.ondisconnect();break;case a.CONNECT_ERROR:this.destroy();const s=new Error(e.data.message);s.data=e.data.data,this.emitReserved("connect_error",s);break}}onevent(e){const t=e.data||[];e.id!=null&&t.push(this.ack(e.id)),this.connected?this.emitEvent(t):this.receiveBuffer.push(Object.freeze(t))}emitEvent(e){if(this._anyListeners&&this._anyListeners.length){const t=this._anyListeners.slice();for(const s of t)s.apply(this,e)}super.emit.apply(this,e),this._pid&&e.length&&typeof e[e.length-1]=="string"&&(this._lastOffset=e[e.length-1])}ack(e){const t=this;let s=!1;return function(...i){s||(s=!0,t.packet({type:a.ACK,id:e,data:i}))}}onack(e){const t=this.acks[e.id];typeof t=="function"&&(delete this.acks[e.id],t.withError&&e.data.unshift(null),t.apply(this,e.data))}onconnect(e,t){this.id=e,this.recovered=t&&this._pid===t,this._pid=t,this.connected=!0,this.emitBuffered(),this._drainQueue(!0),this.emitReserved("connect")}emitBuffered(){this.receiveBuffer.forEach(e=>this.emitEvent(e)),this.receiveBuffer=[],this.sendBuffer.forEach(e=>{this.notifyOutgoingListeners(e),this.packet(e)}),this.sendBuffer=[]}ondisconnect(){this.destroy(),this.onclose("io server disconnect")}destroy(){this.subs&&(this.subs.forEach(e=>e()),this.subs=void 0),this.io._destroy(this)}disconnect(){return this.connected&&this.packet({type:a.DISCONNECT}),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(e){return this.flags.compress=e,this}get volatile(){return this.flags.volatile=!0,this}timeout(e){return this.flags.timeout=e,this}onAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(e),this}prependAny(e){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(e),this}offAny(e){if(!this._anyListeners)return this;if(e){const t=this._anyListeners;for(let s=0;s0&&n.jitter<=1?n.jitter:0,this.attempts=0}v.prototype.duration=function(){var n=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),t=Math.floor(e*this.jitter*n);n=Math.floor(e*10)&1?n+t:n-t}return Math.min(n,this.max)|0};v.prototype.reset=function(){this.attempts=0};v.prototype.setMin=function(n){this.ms=n};v.prototype.setMax=function(n){this.max=n};v.prototype.setJitter=function(n){this.jitter=n};class M extends u{constructor(e,t){var s;super(),this.nsps={},this.subs=[],e&&typeof e=="object"&&(t=e,e=void 0),t=t||{},t.path=t.path||"/socket.io",this.opts=t,N(this,t),this.reconnection(t.reconnection!==!1),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor((s=t.randomizationFactor)!==null&&s!==void 0?s:.5),this.backoff=new v({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(t.timeout==null?2e4:t.timeout),this._readyState="closed",this.uri=e;const i=t.parser||it;this.encoder=new i.Encoder,this.decoder=new i.Decoder,this._autoConnect=t.autoConnect!==!1,this._autoConnect&&this.open()}reconnection(e){return arguments.length?(this._reconnection=!!e,e||(this.skipReconnect=!0),this):this._reconnection}reconnectionAttempts(e){return e===void 0?this._reconnectionAttempts:(this._reconnectionAttempts=e,this)}reconnectionDelay(e){var t;return e===void 0?this._reconnectionDelay:(this._reconnectionDelay=e,(t=this.backoff)===null||t===void 0||t.setMin(e),this)}randomizationFactor(e){var t;return e===void 0?this._randomizationFactor:(this._randomizationFactor=e,(t=this.backoff)===null||t===void 0||t.setJitter(e),this)}reconnectionDelayMax(e){var t;return e===void 0?this._reconnectionDelayMax:(this._reconnectionDelayMax=e,(t=this.backoff)===null||t===void 0||t.setMax(e),this)}timeout(e){return arguments.length?(this._timeout=e,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&this.backoff.attempts===0&&this.reconnect()}open(e){if(~this._readyState.indexOf("open"))return this;this.engine=new $e(this.uri,this.opts);const t=this.engine,s=this;this._readyState="opening",this.skipReconnect=!1;const i=p(t,"open",function(){s.onopen(),e&&e()}),r=c=>{this.cleanup(),this._readyState="closed",this.emitReserved("error",c),e?e(c):this.maybeReconnectOnOpen()},o=p(t,"error",r);if(this._timeout!==!1){const c=this._timeout,h=this.setTimeoutFn(()=>{i(),r(new Error("timeout")),t.close()},c);this.opts.autoUnref&&h.unref(),this.subs.push(()=>{this.clearTimeoutFn(h)})}return this.subs.push(i),this.subs.push(o),this}connect(e){return this.open(e)}onopen(){this.cleanup(),this._readyState="open",this.emitReserved("open");const e=this.engine;this.subs.push(p(e,"ping",this.onping.bind(this)),p(e,"data",this.ondata.bind(this)),p(e,"error",this.onerror.bind(this)),p(e,"close",this.onclose.bind(this)),p(this.decoder,"decoded",this.ondecoded.bind(this)))}onping(){this.emitReserved("ping")}ondata(e){try{this.decoder.add(e)}catch(t){this.onclose("parse error",t)}}ondecoded(e){x(()=>{this.emitReserved("packet",e)},this.setTimeoutFn)}onerror(e){this.emitReserved("error",e)}socket(e,t){let s=this.nsps[e];return s?this._autoConnect&&!s.active&&s.connect():(s=new le(this,e,t),this.nsps[e]=s),s}_destroy(e){const t=Object.keys(this.nsps);for(const s of t)if(this.nsps[s].active)return;this._close()}_packet(e){const t=this.encoder.encode(e);for(let s=0;se()),this.subs.length=0,this.decoder.destroy()}_close(){this.skipReconnect=!0,this._reconnecting=!1,this.onclose("forced close")}disconnect(){return this._close()}onclose(e,t){var s;this.cleanup(),(s=this.engine)===null||s===void 0||s.close(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",e,t),this._reconnection&&!this.skipReconnect&&this.reconnect()}reconnect(){if(this._reconnecting||this.skipReconnect)return this;const e=this;if(this.backoff.attempts>=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{const t=this.backoff.duration();this._reconnecting=!0;const s=this.setTimeoutFn(()=>{e.skipReconnect||(this.emitReserved("reconnect_attempt",e.backoff.attempts),!e.skipReconnect&&e.open(i=>{i?(e._reconnecting=!1,e.reconnect(),this.emitReserved("reconnect_error",i)):e.onreconnect()}))},t);this.opts.autoUnref&&s.unref(),this.subs.push(()=>{this.clearTimeoutFn(s)})}}onreconnect(){const e=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",e)}}const k={};function C(n,e){typeof n=="object"&&(e=n,n=void 0),e=e||{};const t=Je(n,e.path||"/socket.io"),s=t.source,i=t.id,r=t.path,o=k[i]&&r in k[i].nsps,c=e.forceNew||e["force new connection"]||e.multiplex===!1||o;let h;return c?h=new M(s,e):(k[i]||(k[i]=new M(s,e)),h=k[i]),t.query&&!e.query&&(e.query=t.queryKey),h.socket(t.path,e)}Object.assign(C,{Manager:M,Socket:le,io:C,connect:C});let _=null;function ot(){return typeof window>"u"?"http://localhost:8000":window.location.origin}function ut(){return _||(_=C(ot(),{transports:["websocket","polling"],reconnectionAttempts:5,reconnectionDelay:1500}),_.on("connect",()=>console.log("[socket] connected",_==null?void 0:_.id)),_.on("disconnect",n=>console.log("[socket] disconnected",n)),_.on("connect_error",n=>console.error("[socket] error",n.message))),_}const pe=d(null),ft=d(""),lt=d(null),pt=d(null),J=d(null),dt=d(null),yt=d(null),mt=d(null),gt=d("idle"),_t=d(""),bt=d(""),wt=H([J,pe],([n,e])=>!n||!e?null:n.players[e]??null),vt=H([J,pe],([n,e])=>!n||!e?null:Object.values(n.players).find(t=>t.player_id!==e)??null);H(J,n=>n?Object.values(n.players).flatMap(e=>Object.values(e.units).map(t=>({unit:t,isOwn:!1}))):[]);export{lt as a,J as b,yt as c,vt as d,ct as e,wt as f,ut as g,_t as h,bt as l,pe as m,ft as p,pt as r,mt as s,gt as v,dt as w}; diff --git a/frontend/build/_app/immutable/chunks/CbPkTnjI.js b/frontend/build/_app/immutable/chunks/CbPkTnjI.js new file mode 100644 index 0000000000000000000000000000000000000000..0b8327fe2fdc9dbc473ade0d6ea4a69b60f3ff4e --- /dev/null +++ b/frontend/build/_app/immutable/chunks/CbPkTnjI.js @@ -0,0 +1 @@ +var E=Object.defineProperty;var O=(t,e,n)=>e in t?E(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var _=(t,e,n)=>O(t,typeof e!="symbol"?e+"":e,n);import{x as $,n as c,M as v,N as b,O as C,P as p,h as I,d as M,Q as N,R as x,S as P,T as w,U as R,V as U,W as V,X as j,Y as B}from"./BWggeOnc.js";const u=new Set;let d;function A(){d={r:0,c:[],p:d}}function D(){d.r||$(d.c),d=d.p}function L(t,e){t&&t.i&&(u.delete(t),t.i(e))}function F(t,e,n,a){if(t&&t.o){if(u.has(t))return;u.add(t),d.c.push(()=>{u.delete(t),a&&(n&&t.d(1),a())}),t.o(e)}else a&&a()}function G(t){t&&t.c()}function H(t,e){t&&t.l(e)}function Q(t,e,n){const{fragment:a,after_update:i}=t.$$;a&&a.m(e,n),w(()=>{const f=t.$$.on_mount.map(R).filter(v);t.$$.on_destroy?t.$$.on_destroy.push(...f):$(f),t.$$.on_mount=[]}),i.forEach(w)}function T(t,e){const n=t.$$;n.fragment!==null&&(P(n.after_update),$(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function W(t,e){t.$$.dirty[0]===-1&&(j.push(t),B(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const y=m.length?m[0]:g;return s.ctx&&i(s.ctx[r],s.ctx[r]=y)&&(!s.skip_bound&&s.bound[r]&&s.bound[r](y),h&&W(t,r)),g}):[],s.update(),h=!0,$(s.before_update),s.fragment=a?a(s.ctx):!1,e.target){if(e.hydrate){U();const r=I(e.target);s.fragment&&s.fragment.l(r),r.forEach(M)}else s.fragment&&s.fragment.c();e.intro&&L(t.$$.fragment),Q(t,e.target,e.anchor),V(),N()}x(o)}class K{constructor(){_(this,"$$");_(this,"$$set")}$destroy(){T(this,1),this.$destroy=c}$on(e,n){if(!v(n))return c;const a=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return a.push(n),()=>{const i=a.indexOf(n);i!==-1&&a.splice(i,1)}}$set(e){this.$$set&&!b(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}const X="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(X);export{K as S,L as a,G as b,D as c,T as d,H as e,A as g,J as i,Q as m,F as t}; diff --git a/frontend/build/_app/immutable/chunks/DQebRFVS.js b/frontend/build/_app/immutable/chunks/DQebRFVS.js deleted file mode 100644 index cb76f902b799c50a1a5fda2fbbd9afcfe09daa6a..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/chunks/DQebRFVS.js +++ /dev/null @@ -1 +0,0 @@ -function C(){}function P(n,t){for(const e in t)n[e]=t[e];return n}function B(n){return n()}function K(){return Object.create(null)}function D(n){n.forEach(B)}function Q(n){return typeof n=="function"}function R(n,t){return n!=n?t==t:n!==t||n&&typeof n=="object"||typeof n=="function"}function V(n){return Object.keys(n).length===0}function L(n,...t){if(n==null){for(const i of t)i(void 0);return C}const e=n.subscribe(...t);return e.unsubscribe?()=>e.unsubscribe():e}function X(n,t,e){n.$$.on_destroy.push(L(t,e))}function Y(n,t,e,i){if(n){const l=k(n,t,e,i);return n[0](l)}}function k(n,t,e,i){return n[1]&&i?P(e.ctx.slice(),n[1](i(t))):e.ctx}function Z(n,t,e,i){return n[2],t.dirty}function $(n,t,e,i,l,u){if(l){const c=k(t,e,i,u);n.p(c,l)}}function nn(n){if(n.ctx.length>32){const t=[],e=n.ctx.length/32;for(let i=0;i>1);e(l)<=i?n=l+1:t=l}return n}function T(n){if(n.hydrate_init)return;n.hydrate_init=!0;let t=n.childNodes;if(n.nodeName==="HEAD"){const r=[];for(let o=0;o0&&t[e[l]].claim_order<=o?l+1:O(1,l,S=>t[e[S]].claim_order,o))-1;i[r]=e[a]+1;const v=a+1;e[v]=r,l=Math.max(v,l)}const u=[],c=[];let s=t.length-1;for(let r=e[l]+1;r!=0;r=i[r-1]){for(u.push(t[r-1]);s>=r;s--)c.push(t[s]);s--}for(;s>=0;s--)c.push(t[s]);u.reverse(),c.sort((r,o)=>r.claim_order-o.claim_order);for(let r=0,o=0;r=u[o].claim_order;)o++;const a=on.removeEventListener(t,e,i)}function an(n,t,e){e==null?n.removeAttribute(t):n.getAttribute(t)!==e&&n.setAttribute(t,e)}function fn(n){return n.dataset.svelteH}function _n(n){return Array.from(n.childNodes)}function M(n){n.claim_info===void 0&&(n.claim_info={last_index:0,total_claimed:0})}function N(n,t,e,i,l=!1){M(n);const u=(()=>{for(let c=n.claim_info.last_index;c=0;c--){const s=n[c];if(t(s)){const r=e(s);return r===void 0?n.splice(c,1):n[c]=r,l?r===void 0&&n.claim_info.last_index--:n.claim_info.last_index=c,s}}return i()})();return u.claim_order=n.claim_info.total_claimed,n.claim_info.total_claimed+=1,u}function A(n,t,e,i){return N(n,l=>l.nodeName===t,l=>{const u=[];for(let c=0;cl.removeAttribute(c))},()=>i(t))}function dn(n,t,e){return A(n,t,e,H)}function hn(n,t,e){return A(n,t,e,I)}function z(n,t){return N(n,e=>e.nodeType===3,e=>{const i=""+t;if(e.data.startsWith(i)){if(e.data.length!==i.length)return e.splitText(i.length)}else e.data=i},()=>x(t),!0)}function mn(n){return z(n," ")}function pn(n,t){t=""+t,n.data!==t&&(n.data=t)}function bn(n,t){n.value=t??""}function yn(n,t,e,i){e==null?n.style.removeProperty(t):n.style.setProperty(t,e,"")}function gn(n,t,e){n.classList.toggle(t,!!e)}function F(n,t,{bubbles:e=!1,cancelable:i=!1}={}){return new CustomEvent(n,{detail:t,bubbles:e,cancelable:i})}function xn(n,t){return new n(t)}let h;function b(n){h=n}function p(){if(!h)throw new Error("Function called outside component initialization");return h}function vn(n){p().$$.on_mount.push(n)}function wn(n){p().$$.after_update.push(n)}function En(n){p().$$.on_destroy.push(n)}function kn(){const n=p();return(t,e,{cancelable:i=!1}={})=>{const l=n.$$.callbacks[t];if(l){const u=F(t,e,{cancelable:i});return l.slice().forEach(c=>{c.call(n,u)}),!u.defaultPrevented}return!0}}const d=[],w=[];let _=[];const E=[],j=Promise.resolve();let g=!1;function U(){g||(g=!0,j.then(G))}function Nn(){return U(),j}function W(n){_.push(n)}const y=new Set;let f=0;function G(){if(f!==0)return;const n=h;do{try{for(;fn.indexOf(i)===-1?t.push(i):e.push(i)),e.forEach(i=>i()),_=t}export{un as A,gn as B,wn as C,Nn as D,xn as E,yn as F,w as G,Q as H,V as I,h as J,K,G as L,b as M,An as N,W as O,B as P,tn as Q,en as R,d as S,U as T,hn as U,I as V,kn as W,L as X,Z as a,pn as b,Y as c,ln as d,q as e,dn as f,nn as g,_n as h,cn as i,z as j,mn as k,H as l,sn as m,C as n,X as o,an as p,fn as q,vn as r,R as s,x as t,$ as u,En as v,D as w,bn as x,on as y,rn as z}; diff --git a/frontend/build/_app/immutable/chunks/JF702EPM.js b/frontend/build/_app/immutable/chunks/JF702EPM.js new file mode 100644 index 0000000000000000000000000000000000000000..36fe499bb023e075106a6768bbf4e2049dcf9d92 --- /dev/null +++ b/frontend/build/_app/immutable/chunks/JF702EPM.js @@ -0,0 +1 @@ +import{d as i,w as e}from"./B_0L5JHM.js";const p={command_center:{w:4,h:3},barracks:{w:4,h:3},factory:{w:4,h:3},starport:{w:4,h:2},supply_depot:{w:3,h:2},engineering_bay:{w:3,h:2},armory:{w:3,h:2},refinery:{w:2,h:2}},_={command_center:"🏛️",supply_depot:"📦",barracks:"⚔️",engineering_bay:"🔬",refinery:"🛢️",factory:"🏭",armory:"🛡️",starport:"🚀"},h={command_center:"CC",supply_depot:"SD",barracks:"BAR",engineering_bay:"EB",refinery:"REF",factory:"FAC",armory:"ARM",starport:"SP"},S={scv:"S",marine:"M",medic:"+",goliath:"G",tank:"T",wraith:"W"},y={scv:1,marine:1,medic:1,goliath:2,tank:2,wraith:2},d={command_center:10,supply_depot:8,barracks:0,engineering_bay:0,refinery:0,factory:0,armory:0,starport:0},g={scv:"⚒️",marine:"🔫",medic:"⚕️",goliath:"🤖",tank:"💥",wraith:"✈️"},w={scv:"SCV — Worker. Gathers resources, constructs buildings.",marine:"Marine — Basic infantry. Anti-ground and anti-air.",medic:"Medic — Heals adjacent infantry.",goliath:"Goliath — Heavy vehicle. Anti-ground and anti-air.",tank:"Siege Tank — Artillery vehicle. Siege mode: +5 range, +20 damage.",wraith:"Wraith — Aerial vessel. Can cloak."},l=e(null),I=e(""),f=e(null),v=e(null),c=e(null),b=e(null),k=e(null),C=e(null),N=e({vx:0,vy:0,vw:40,vh:40}),U=e(new Set),B=e(new Set),O=e(null),L=e("idle"),P=e(""),A=e(""),T=i([c,l],([a,t])=>!a||!t?null:a.players[t]??null),D=i([c,l],([a,t])=>!a||!t?null:Object.values(a.players).find(n=>n.player_id!==t)??null),E=i([c,l],([a,t])=>{if(!a||!t)return{used:0,max:0};const n=a.players[t];if(!n)return{used:0,max:0};const o=Object.values(n.units).reduce((s,r)=>s+(y[r.unit_type]??0),0),u=Object.values(n.buildings).reduce((s,r)=>r.status==="constructing"||r.status==="destroyed"?s:s+(d[r.building_type]??0),0);return{used:o,max:u}});i(c,a=>a?Object.values(a.players).flatMap(t=>Object.values(t.units).map(n=>({unit:n,isOwn:!1}))):[]);export{p as B,S as U,f as a,O as b,N as c,U as d,B as e,h as f,c as g,k as h,g as i,_ as j,E as k,D as l,l as m,T as n,w as o,I as p,A as q,v as r,C as s,P as t,L as v,b as w}; diff --git a/frontend/build/_app/immutable/entry/app.KWTSSp-V.js b/frontend/build/_app/immutable/entry/app.KWTSSp-V.js new file mode 100644 index 0000000000000000000000000000000000000000..26d929a016c34ab544b7dccd29e8948639e924b7 --- /dev/null +++ b/frontend/build/_app/immutable/entry/app.KWTSSp-V.js @@ -0,0 +1,2 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["../nodes/0.CERW03VC.js","../chunks/BWggeOnc.js","../chunks/CbPkTnjI.js","../assets/0.XtmqqaVM.css","../nodes/1.Dc5H-1Vw.js","../chunks/B_0L5JHM.js","../nodes/2.BGMK1cUJ.js","../chunks/B2mXDaCf.js","../chunks/JF702EPM.js","../assets/2.BFuSHcwB.css","../nodes/3.BjvrdcVH.js","../assets/3.D3DQ9iMP.css","../nodes/4.Jx4UbTnq.js","../assets/4.jNh2kMze.css","../nodes/5.CRReP7Rn.js","../assets/5.CABnF8ZW.css","../nodes/6.CN5y69hd.js","../assets/6.A5_P0Qff.css"])))=>i.map(i=>d[i]); +import{s as N,d,i as v,k as q,F as h,m as U,I as F,v as J,J as K,K as R,p as I,D as p,f as W,h as z,l as G,B as S,b as H,j as Q,t as X}from"../chunks/BWggeOnc.js";import{S as Y,i as Z,t as g,a as w,g as A,c as D,d as O,b as L,m as y,e as T}from"../chunks/CbPkTnjI.js";const M="modulepreload",$=function(o,e){return new URL(o,e).href},V={},k=function(e,n,r){let i=Promise.resolve();if(n&&n.length>0){const t=document.getElementsByTagName("link"),s=document.querySelector("meta[property=csp-nonce]"),a=(s==null?void 0:s.nonce)||(s==null?void 0:s.getAttribute("nonce"));i=Promise.allSettled(n.map(f=>{if(f=$(f,r),f in V)return;V[f]=!0;const l=f.endsWith(".css"),_=l?'[rel="stylesheet"]':"";if(!!r)for(let E=t.length-1;E>=0;E--){const P=t[E];if(P.href===f&&(!l||P.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${f}"]${_}`))return;const m=document.createElement("link");if(m.rel=l?"stylesheet":M,l||(m.as="script"),m.crossOrigin="",m.href=f,a&&m.setAttribute("nonce",a),document.head.appendChild(m),l)return new Promise((E,P)=>{m.addEventListener("load",E),m.addEventListener("error",()=>P(new Error(`Unable to preload CSS for ${f}`)))})}))}function u(t){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=t,window.dispatchEvent(s),!s.defaultPrevented)throw t}return i.then(t=>{for(const s of t||[])s.status==="rejected"&&u(s.reason);return e().catch(u)})},ae={};function x(o){let e,n,r;var i=o[2][0];function u(t,s){return{props:{data:t[4],form:t[3],params:t[1].params}}}return i&&(e=R(i,u(o)),o[12](e)),{c(){e&&L(e.$$.fragment),n=h()},l(t){e&&T(e.$$.fragment,t),n=h()},m(t,s){e&&y(e,t,s),v(t,n,s),r=!0},p(t,s){if(s&4&&i!==(i=t[2][0])){if(e){A();const a=e;g(a.$$.fragment,1,0,()=>{O(a,1)}),D()}i?(e=R(i,u(t)),t[12](e),L(e.$$.fragment),w(e.$$.fragment,1),y(e,n.parentNode,n)):e=null}else if(i){const a={};s&16&&(a.data=t[4]),s&8&&(a.form=t[3]),s&2&&(a.params=t[1].params),e.$set(a)}},i(t){r||(e&&w(e.$$.fragment,t),r=!0)},o(t){e&&g(e.$$.fragment,t),r=!1},d(t){t&&d(n),o[12](null),e&&O(e,t)}}}function ee(o){let e,n,r;var i=o[2][0];function u(t,s){return{props:{data:t[4],params:t[1].params,$$slots:{default:[te]},$$scope:{ctx:t}}}}return i&&(e=R(i,u(o)),o[11](e)),{c(){e&&L(e.$$.fragment),n=h()},l(t){e&&T(e.$$.fragment,t),n=h()},m(t,s){e&&y(e,t,s),v(t,n,s),r=!0},p(t,s){if(s&4&&i!==(i=t[2][0])){if(e){A();const a=e;g(a.$$.fragment,1,0,()=>{O(a,1)}),D()}i?(e=R(i,u(t)),t[11](e),L(e.$$.fragment),w(e.$$.fragment,1),y(e,n.parentNode,n)):e=null}else if(i){const a={};s&16&&(a.data=t[4]),s&2&&(a.params=t[1].params),s&8239&&(a.$$scope={dirty:s,ctx:t}),e.$set(a)}},i(t){r||(e&&w(e.$$.fragment,t),r=!0)},o(t){e&&g(e.$$.fragment,t),r=!1},d(t){t&&d(n),o[11](null),e&&O(e,t)}}}function te(o){let e,n,r;var i=o[2][1];function u(t,s){return{props:{data:t[5],form:t[3],params:t[1].params}}}return i&&(e=R(i,u(o)),o[10](e)),{c(){e&&L(e.$$.fragment),n=h()},l(t){e&&T(e.$$.fragment,t),n=h()},m(t,s){e&&y(e,t,s),v(t,n,s),r=!0},p(t,s){if(s&4&&i!==(i=t[2][1])){if(e){A();const a=e;g(a.$$.fragment,1,0,()=>{O(a,1)}),D()}i?(e=R(i,u(t)),t[10](e),L(e.$$.fragment),w(e.$$.fragment,1),y(e,n.parentNode,n)):e=null}else if(i){const a={};s&32&&(a.data=t[5]),s&8&&(a.form=t[3]),s&2&&(a.params=t[1].params),e.$set(a)}},i(t){r||(e&&w(e.$$.fragment,t),r=!0)},o(t){e&&g(e.$$.fragment,t),r=!1},d(t){t&&d(n),o[10](null),e&&O(e,t)}}}function j(o){let e,n=o[7]&&B(o);return{c(){e=G("div"),n&&n.c(),this.h()},l(r){e=W(r,"DIV",{id:!0,"aria-live":!0,"aria-atomic":!0,style:!0});var i=z(e);n&&n.l(i),i.forEach(d),this.h()},h(){I(e,"id","svelte-announcer"),I(e,"aria-live","assertive"),I(e,"aria-atomic","true"),p(e,"position","absolute"),p(e,"left","0"),p(e,"top","0"),p(e,"clip","rect(0 0 0 0)"),p(e,"clip-path","inset(50%)"),p(e,"overflow","hidden"),p(e,"white-space","nowrap"),p(e,"width","1px"),p(e,"height","1px")},m(r,i){v(r,e,i),n&&n.m(e,null)},p(r,i){r[7]?n?n.p(r,i):(n=B(r),n.c(),n.m(e,null)):n&&(n.d(1),n=null)},d(r){r&&d(e),n&&n.d()}}}function B(o){let e;return{c(){e=X(o[8])},l(n){e=Q(n,o[8])},m(n,r){v(n,e,r)},p(n,r){r&256&&H(e,n[8])},d(n){n&&d(e)}}}function ne(o){let e,n,r,i,u;const t=[ee,x],s=[];function a(l,_){return l[2][1]?0:1}e=a(o),n=s[e]=t[e](o);let f=o[6]&&j(o);return{c(){n.c(),r=U(),f&&f.c(),i=h()},l(l){n.l(l),r=q(l),f&&f.l(l),i=h()},m(l,_){s[e].m(l,_),v(l,r,_),f&&f.m(l,_),v(l,i,_),u=!0},p(l,[_]){let b=e;e=a(l),e===b?s[e].p(l,_):(A(),g(s[b],1,1,()=>{s[b]=null}),D(),n=s[e],n?n.p(l,_):(n=s[e]=t[e](l),n.c()),w(n,1),n.m(r.parentNode,r)),l[6]?f?f.p(l,_):(f=j(l),f.c(),f.m(i.parentNode,i)):f&&(f.d(1),f=null)},i(l){u||(w(n),u=!0)},o(l){g(n),u=!1},d(l){l&&(d(r),d(i)),s[e].d(l),f&&f.d(l)}}}function se(o,e,n){let{stores:r}=e,{page:i}=e,{constructors:u}=e,{components:t=[]}=e,{form:s}=e,{data_0:a=null}=e,{data_1:f=null}=e;F(r.page.notify);let l=!1,_=!1,b=null;J(()=>{const c=r.page.subscribe(()=>{l&&(n(7,_=!0),K().then(()=>{n(8,b=document.title||"untitled page")}))});return n(6,l=!0),c});function m(c){S[c?"unshift":"push"](()=>{t[1]=c,n(0,t)})}function E(c){S[c?"unshift":"push"](()=>{t[0]=c,n(0,t)})}function P(c){S[c?"unshift":"push"](()=>{t[0]=c,n(0,t)})}return o.$$set=c=>{"stores"in c&&n(9,r=c.stores),"page"in c&&n(1,i=c.page),"constructors"in c&&n(2,u=c.constructors),"components"in c&&n(0,t=c.components),"form"in c&&n(3,s=c.form),"data_0"in c&&n(4,a=c.data_0),"data_1"in c&&n(5,f=c.data_1)},o.$$.update=()=>{o.$$.dirty&514&&r.page.set(i)},[t,i,u,s,a,f,l,_,b,r,m,E,P]}class le extends Y{constructor(e){super(),Z(this,e,se,ne,N,{stores:9,page:1,constructors:2,components:0,form:3,data_0:4,data_1:5})}}const fe=[()=>k(()=>import("../nodes/0.CERW03VC.js"),__vite__mapDeps([0,1,2,3]),import.meta.url),()=>k(()=>import("../nodes/1.Dc5H-1Vw.js"),__vite__mapDeps([4,1,2,5]),import.meta.url),()=>k(()=>import("../nodes/2.BGMK1cUJ.js"),__vite__mapDeps([6,1,7,2,5,8,9]),import.meta.url),()=>k(()=>import("../nodes/3.BjvrdcVH.js"),__vite__mapDeps([10,1,7,2,11]),import.meta.url),()=>k(()=>import("../nodes/4.Jx4UbTnq.js"),__vite__mapDeps([12,1,7,2,13]),import.meta.url),()=>k(()=>import("../nodes/5.CRReP7Rn.js"),__vite__mapDeps([14,1,7,2,15]),import.meta.url),()=>k(()=>import("../nodes/6.CN5y69hd.js"),__vite__mapDeps([16,1,2,5,7,8,17]),import.meta.url)],ce=[],ue={"/":[2],"/admin/map":[3],"/admin/sounds":[4],"/admin/sprites":[5],"/game":[6]},C={handleError:({error:o})=>{console.error(o)},reroute:()=>{},transport:{}},re=Object.fromEntries(Object.entries(C.transport).map(([o,e])=>[o,e.decode])),_e=Object.fromEntries(Object.entries(C.transport).map(([o,e])=>[o,e.encode])),me=!1,pe=(o,e)=>re[o](e);export{pe as decode,re as decoders,ue as dictionary,_e as encoders,me as hash,C as hooks,ae as matchers,fe as nodes,le as root,ce as server_loads}; diff --git a/frontend/build/_app/immutable/entry/app.mfL3Z2b5.js b/frontend/build/_app/immutable/entry/app.mfL3Z2b5.js deleted file mode 100644 index 91be310b5643f667707e13929f1847cb9474c9b6..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/entry/app.mfL3Z2b5.js +++ /dev/null @@ -1,2 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["../nodes/0.B4Km_xye.js","../chunks/DQebRFVS.js","../chunks/BX82rj4I.js","../assets/0.XtmqqaVM.css","../nodes/1.CgjumiZZ.js","../chunks/J0QSAnNz.js","../nodes/2.CwsLFqkm.js","../chunks/BxRIPBY0.js","../assets/2.BpyLQ8v8.css","../nodes/3.xYrSFp1n.js","../assets/3.CRf0cTrE.css"])))=>i.map(i=>d[i]); -import{s as q,d,i as E,k as B,A as h,m as U,C as F,r as G,D as W,E as y,p as j,F as p,f as z,h as H,l as J,G as D,b as K,j as Q,t as X}from"../chunks/DQebRFVS.js";import{S as Y,i as Z,t as g,a as w,g as S,c as A,d as P,b as R,m as O,e as C}from"../chunks/BX82rj4I.js";const M="modulepreload",$=function(o,e){return new URL(o,e).href},I={},L=function(e,n,r){let i=Promise.resolve();if(n&&n.length>0){const t=document.getElementsByTagName("link"),s=document.querySelector("meta[property=csp-nonce]"),a=(s==null?void 0:s.nonce)||(s==null?void 0:s.getAttribute("nonce"));i=Promise.allSettled(n.map(f=>{if(f=$(f,r),f in I)return;I[f]=!0;const l=f.endsWith(".css"),_=l?'[rel="stylesheet"]':"";if(!!r)for(let k=t.length-1;k>=0;k--){const v=t[k];if(v.href===f&&(!l||v.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${f}"]${_}`))return;const m=document.createElement("link");if(m.rel=l?"stylesheet":M,l||(m.as="script"),m.crossOrigin="",m.href=f,a&&m.setAttribute("nonce",a),document.head.appendChild(m),l)return new Promise((k,v)=>{m.addEventListener("load",k),m.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${f}`)))})}))}function u(t){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=t,window.dispatchEvent(s),!s.defaultPrevented)throw t}return i.then(t=>{for(const s of t||[])s.status==="rejected"&&u(s.reason);return e().catch(u)})},ae={};function x(o){let e,n,r;var i=o[2][0];function u(t,s){return{props:{data:t[4],form:t[3],params:t[1].params}}}return i&&(e=y(i,u(o)),o[12](e)),{c(){e&&R(e.$$.fragment),n=h()},l(t){e&&C(e.$$.fragment,t),n=h()},m(t,s){e&&O(e,t,s),E(t,n,s),r=!0},p(t,s){if(s&4&&i!==(i=t[2][0])){if(e){S();const a=e;g(a.$$.fragment,1,0,()=>{P(a,1)}),A()}i?(e=y(i,u(t)),t[12](e),R(e.$$.fragment),w(e.$$.fragment,1),O(e,n.parentNode,n)):e=null}else if(i){const a={};s&16&&(a.data=t[4]),s&8&&(a.form=t[3]),s&2&&(a.params=t[1].params),e.$set(a)}},i(t){r||(e&&w(e.$$.fragment,t),r=!0)},o(t){e&&g(e.$$.fragment,t),r=!1},d(t){t&&d(n),o[12](null),e&&P(e,t)}}}function ee(o){let e,n,r;var i=o[2][0];function u(t,s){return{props:{data:t[4],params:t[1].params,$$slots:{default:[te]},$$scope:{ctx:t}}}}return i&&(e=y(i,u(o)),o[11](e)),{c(){e&&R(e.$$.fragment),n=h()},l(t){e&&C(e.$$.fragment,t),n=h()},m(t,s){e&&O(e,t,s),E(t,n,s),r=!0},p(t,s){if(s&4&&i!==(i=t[2][0])){if(e){S();const a=e;g(a.$$.fragment,1,0,()=>{P(a,1)}),A()}i?(e=y(i,u(t)),t[11](e),R(e.$$.fragment),w(e.$$.fragment,1),O(e,n.parentNode,n)):e=null}else if(i){const a={};s&16&&(a.data=t[4]),s&2&&(a.params=t[1].params),s&8239&&(a.$$scope={dirty:s,ctx:t}),e.$set(a)}},i(t){r||(e&&w(e.$$.fragment,t),r=!0)},o(t){e&&g(e.$$.fragment,t),r=!1},d(t){t&&d(n),o[11](null),e&&P(e,t)}}}function te(o){let e,n,r;var i=o[2][1];function u(t,s){return{props:{data:t[5],form:t[3],params:t[1].params}}}return i&&(e=y(i,u(o)),o[10](e)),{c(){e&&R(e.$$.fragment),n=h()},l(t){e&&C(e.$$.fragment,t),n=h()},m(t,s){e&&O(e,t,s),E(t,n,s),r=!0},p(t,s){if(s&4&&i!==(i=t[2][1])){if(e){S();const a=e;g(a.$$.fragment,1,0,()=>{P(a,1)}),A()}i?(e=y(i,u(t)),t[10](e),R(e.$$.fragment),w(e.$$.fragment,1),O(e,n.parentNode,n)):e=null}else if(i){const a={};s&32&&(a.data=t[5]),s&8&&(a.form=t[3]),s&2&&(a.params=t[1].params),e.$set(a)}},i(t){r||(e&&w(e.$$.fragment,t),r=!0)},o(t){e&&g(e.$$.fragment,t),r=!1},d(t){t&&d(n),o[10](null),e&&P(e,t)}}}function T(o){let e,n=o[7]&&V(o);return{c(){e=J("div"),n&&n.c(),this.h()},l(r){e=z(r,"DIV",{id:!0,"aria-live":!0,"aria-atomic":!0,style:!0});var i=H(e);n&&n.l(i),i.forEach(d),this.h()},h(){j(e,"id","svelte-announcer"),j(e,"aria-live","assertive"),j(e,"aria-atomic","true"),p(e,"position","absolute"),p(e,"left","0"),p(e,"top","0"),p(e,"clip","rect(0 0 0 0)"),p(e,"clip-path","inset(50%)"),p(e,"overflow","hidden"),p(e,"white-space","nowrap"),p(e,"width","1px"),p(e,"height","1px")},m(r,i){E(r,e,i),n&&n.m(e,null)},p(r,i){r[7]?n?n.p(r,i):(n=V(r),n.c(),n.m(e,null)):n&&(n.d(1),n=null)},d(r){r&&d(e),n&&n.d()}}}function V(o){let e;return{c(){e=X(o[8])},l(n){e=Q(n,o[8])},m(n,r){E(n,e,r)},p(n,r){r&256&&K(e,n[8])},d(n){n&&d(e)}}}function ne(o){let e,n,r,i,u;const t=[ee,x],s=[];function a(l,_){return l[2][1]?0:1}e=a(o),n=s[e]=t[e](o);let f=o[6]&&T(o);return{c(){n.c(),r=U(),f&&f.c(),i=h()},l(l){n.l(l),r=B(l),f&&f.l(l),i=h()},m(l,_){s[e].m(l,_),E(l,r,_),f&&f.m(l,_),E(l,i,_),u=!0},p(l,[_]){let b=e;e=a(l),e===b?s[e].p(l,_):(S(),g(s[b],1,1,()=>{s[b]=null}),A(),n=s[e],n?n.p(l,_):(n=s[e]=t[e](l),n.c()),w(n,1),n.m(r.parentNode,r)),l[6]?f?f.p(l,_):(f=T(l),f.c(),f.m(i.parentNode,i)):f&&(f.d(1),f=null)},i(l){u||(w(n),u=!0)},o(l){g(n),u=!1},d(l){l&&(d(r),d(i)),s[e].d(l),f&&f.d(l)}}}function se(o,e,n){let{stores:r}=e,{page:i}=e,{constructors:u}=e,{components:t=[]}=e,{form:s}=e,{data_0:a=null}=e,{data_1:f=null}=e;F(r.page.notify);let l=!1,_=!1,b=null;G(()=>{const c=r.page.subscribe(()=>{l&&(n(7,_=!0),W().then(()=>{n(8,b=document.title||"untitled page")}))});return n(6,l=!0),c});function m(c){D[c?"unshift":"push"](()=>{t[1]=c,n(0,t)})}function k(c){D[c?"unshift":"push"](()=>{t[0]=c,n(0,t)})}function v(c){D[c?"unshift":"push"](()=>{t[0]=c,n(0,t)})}return o.$$set=c=>{"stores"in c&&n(9,r=c.stores),"page"in c&&n(1,i=c.page),"constructors"in c&&n(2,u=c.constructors),"components"in c&&n(0,t=c.components),"form"in c&&n(3,s=c.form),"data_0"in c&&n(4,a=c.data_0),"data_1"in c&&n(5,f=c.data_1)},o.$$.update=()=>{o.$$.dirty&514&&r.page.set(i)},[t,i,u,s,a,f,l,_,b,r,m,k,v]}class le extends Y{constructor(e){super(),Z(this,e,se,ne,q,{stores:9,page:1,constructors:2,components:0,form:3,data_0:4,data_1:5})}}const fe=[()=>L(()=>import("../nodes/0.B4Km_xye.js"),__vite__mapDeps([0,1,2,3]),import.meta.url),()=>L(()=>import("../nodes/1.CgjumiZZ.js"),__vite__mapDeps([4,1,2,5]),import.meta.url),()=>L(()=>import("../nodes/2.CwsLFqkm.js"),__vite__mapDeps([6,1,7,5,2,8]),import.meta.url),()=>L(()=>import("../nodes/3.xYrSFp1n.js"),__vite__mapDeps([9,1,2,5,7,10]),import.meta.url)],ce=[],ue={"/":[2],"/game":[3]},N={handleError:({error:o})=>{console.error(o)},reroute:()=>{},transport:{}},re=Object.fromEntries(Object.entries(N.transport).map(([o,e])=>[o,e.decode])),_e=Object.fromEntries(Object.entries(N.transport).map(([o,e])=>[o,e.encode])),me=!1,pe=(o,e)=>re[o](e);export{pe as decode,re as decoders,ue as dictionary,_e as encoders,me as hash,N as hooks,ae as matchers,fe as nodes,le as root,ce as server_loads}; diff --git a/frontend/build/_app/immutable/entry/start.B6yuEF1E.js b/frontend/build/_app/immutable/entry/start.B6yuEF1E.js new file mode 100644 index 0000000000000000000000000000000000000000..8f2cfd19fa142b603401b32ff7039b2ab5e3bc55 --- /dev/null +++ b/frontend/build/_app/immutable/entry/start.B6yuEF1E.js @@ -0,0 +1 @@ +import{l as o,a as r}from"../chunks/B_0L5JHM.js";export{o as load_css,r as start}; diff --git a/frontend/build/_app/immutable/entry/start.BPj5i5_S.js b/frontend/build/_app/immutable/entry/start.BPj5i5_S.js deleted file mode 100644 index 6c40835f6399dac7ac962788b51ccd0bf8c419d8..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/entry/start.BPj5i5_S.js +++ /dev/null @@ -1 +0,0 @@ -import{l as o,a as r}from"../chunks/J0QSAnNz.js";export{o as load_css,r as start}; diff --git a/frontend/build/_app/immutable/nodes/0.B4Km_xye.js b/frontend/build/_app/immutable/nodes/0.CERW03VC.js similarity index 77% rename from frontend/build/_app/immutable/nodes/0.B4Km_xye.js rename to frontend/build/_app/immutable/nodes/0.CERW03VC.js index 8dd53a81d3721b28d8b58fe1901d8ef253e82d5d..e2aedd874660282cc31b3cebbcbb23ae07ed686b 100644 --- a/frontend/build/_app/immutable/nodes/0.B4Km_xye.js +++ b/frontend/build/_app/immutable/nodes/0.CERW03VC.js @@ -1 +1 @@ -import{s as l,c as i,u as r,g as u,a as f}from"../chunks/DQebRFVS.js";import{S as _,i as c,t as p,a as m}from"../chunks/BX82rj4I.js";function $(a){let s;const n=a[1].default,e=i(n,a,a[0],null);return{c(){e&&e.c()},l(t){e&&e.l(t)},m(t,o){e&&e.m(t,o),s=!0},p(t,[o]){e&&e.p&&(!s||o&1)&&r(e,n,t,t[0],s?f(n,t[0],o,null):u(t[0]),null)},i(t){s||(m(e,t),s=!0)},o(t){p(e,t),s=!1},d(t){e&&e.d(t)}}}function d(a,s,n){let{$$slots:e={},$$scope:t}=s;return a.$$set=o=>{"$$scope"in o&&n(0,t=o.$$scope)},[t,e]}class S extends _{constructor(s){super(),c(this,s,d,$,l,{})}}export{S as component}; +import{s as l,c as i,u as r,g as u,a as f}from"../chunks/BWggeOnc.js";import{S as _,i as c,t as p,a as m}from"../chunks/CbPkTnjI.js";function $(a){let s;const n=a[1].default,e=i(n,a,a[0],null);return{c(){e&&e.c()},l(t){e&&e.l(t)},m(t,o){e&&e.m(t,o),s=!0},p(t,[o]){e&&e.p&&(!s||o&1)&&r(e,n,t,t[0],s?f(n,t[0],o,null):u(t[0]),null)},i(t){s||(m(e,t),s=!0)},o(t){p(e,t),s=!1},d(t){e&&e.d(t)}}}function d(a,s,n){let{$$slots:e={},$$scope:t}=s;return a.$$set=o=>{"$$scope"in o&&n(0,t=o.$$scope)},[t,e]}class S extends _{constructor(s){super(),c(this,s,d,$,l,{})}}export{S as component}; diff --git a/frontend/build/_app/immutable/nodes/1.CgjumiZZ.js b/frontend/build/_app/immutable/nodes/1.Dc5H-1Vw.js similarity index 80% rename from frontend/build/_app/immutable/nodes/1.CgjumiZZ.js rename to frontend/build/_app/immutable/nodes/1.Dc5H-1Vw.js index 045c00f407e37e4ea328501093a9857e0799c59c..bd3cf22cc53f490893bbe0f86a344f48a85f53e2 100644 --- a/frontend/build/_app/immutable/nodes/1.CgjumiZZ.js +++ b/frontend/build/_app/immutable/nodes/1.Dc5H-1Vw.js @@ -1 +1 @@ -import{s as S,n as _,d as l,b as f,i as m,e as d,f as g,h,j as v,k as x,l as $,t as E,m as j,o as k}from"../chunks/DQebRFVS.js";import{S as q,i as y}from"../chunks/BX82rj4I.js";import{s as C}from"../chunks/J0QSAnNz.js";const H=()=>{const s=C;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},P={subscribe(s){return H().page.subscribe(s)}};function w(s){var b;let t,r=s[0].status+"",o,n,i,c=((b=s[0].error)==null?void 0:b.message)+"",u;return{c(){t=$("h1"),o=E(r),n=j(),i=$("p"),u=E(c)},l(e){t=g(e,"H1",{});var a=h(t);o=v(a,r),a.forEach(l),n=x(e),i=g(e,"P",{});var p=h(i);u=v(p,c),p.forEach(l)},m(e,a){m(e,t,a),d(t,o),m(e,n,a),m(e,i,a),d(i,u)},p(e,[a]){var p;a&1&&r!==(r=e[0].status+"")&&f(o,r),a&1&&c!==(c=((p=e[0].error)==null?void 0:p.message)+"")&&f(u,c)},i:_,o:_,d(e){e&&(l(t),l(n),l(i))}}}function z(s,t,r){let o;return k(s,P,n=>r(0,o=n)),[o]}let F=class extends q{constructor(t){super(),y(this,t,z,w,S,{})}};export{F as component}; +import{s as S,n as _,d as l,b as f,i as m,e as d,f as g,h,j as v,k as x,l as $,t as E,m as j,o as k}from"../chunks/BWggeOnc.js";import{S as q,i as y}from"../chunks/CbPkTnjI.js";import{s as C}from"../chunks/B_0L5JHM.js";const H=()=>{const s=C;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},P={subscribe(s){return H().page.subscribe(s)}};function w(s){var b;let t,r=s[0].status+"",o,n,i,c=((b=s[0].error)==null?void 0:b.message)+"",u;return{c(){t=$("h1"),o=E(r),n=j(),i=$("p"),u=E(c)},l(e){t=g(e,"H1",{});var a=h(t);o=v(a,r),a.forEach(l),n=x(e),i=g(e,"P",{});var p=h(i);u=v(p,c),p.forEach(l)},m(e,a){m(e,t,a),d(t,o),m(e,n,a),m(e,i,a),d(i,u)},p(e,[a]){var p;a&1&&r!==(r=e[0].status+"")&&f(o,r),a&1&&c!==(c=((p=e[0].error)==null?void 0:p.message)+"")&&f(u,c)},i:_,o:_,d(e){e&&(l(t),l(n),l(i))}}}function z(s,t,r){let o;return k(s,P,n=>r(0,o=n)),[o]}let F=class extends q{constructor(t){super(),y(this,t,z,w,S,{})}};export{F as component}; diff --git a/frontend/build/_app/immutable/nodes/2.BGMK1cUJ.js b/frontend/build/_app/immutable/nodes/2.BGMK1cUJ.js new file mode 100644 index 0000000000000000000000000000000000000000..0f0e182c8f4abdd17f09824344b9501452cf7174 --- /dev/null +++ b/frontend/build/_app/immutable/nodes/2.BGMK1cUJ.js @@ -0,0 +1,5 @@ +import{s as de,n as W,d as p,i as k,e as y,p as _,f as b,h as L,r as P,k as w,l as q,m as I,o as pe,v as _e,w as he,b as J,j as x,t as U,x as me,L as $,y as H,C as ve,F as ee,A as F}from"../chunks/BWggeOnc.js";import{g as ye,e as le}from"../chunks/B2mXDaCf.js";import{S as be,i as qe}from"../chunks/CbPkTnjI.js";import{g as Ce}from"../chunks/B_0L5JHM.js";import{r as te,a as X,g as ze,m as ke,w as ge,p as Z}from"../chunks/JF702EPM.js";const Te=!1,Ee=!1,Je=Object.freeze(Object.defineProperty({__proto__:null,prerender:Ee,ssr:Te},Symbol.toStringTag,{value:"Module"})),Ne=""+new URL("../assets/cover.CJg7zHH3.jpg",import.meta.url).href;function ne(i,e,t){const s=i.slice();return s[29]=e[t],s}function we(i){const e=i.slice(),t=e[0];return e[28]=t,e}function ae(i){let e,t;return{c(){e=q("div"),t=U(i[5]),this.h()},l(s){e=b(s,"DIV",{class:!0});var a=L(e);t=x(a,i[5]),a.forEach(p),this.h()},h(){_(e,"class","error-toast svelte-my9qcz")},m(s,a){k(s,e,a),y(e,t)},p(s,a){a[0]&32&&J(t,s[5])},d(s){s&&p(e)}}}function ie(i){var G,K,Q;let e,t,s,a="Room Code",o,l,n=((G=i[28])==null?void 0:G.room_id)+"",u,c,r,f="Copy",h,d,m,C,B,j,A;function M(){return i[21](i[28])}let N=le(((K=i[28])==null?void 0:K.players)??[]),g=[];for(let z=0;z0)return Se;if(z[9])return Le;if((V=z[1])!=null&&V.ready)return Me}let D=Y(i),S=D&&D(i);return{c(){e=q("section"),t=q("div"),s=q("span"),s.textContent=a,o=I(),l=q("span"),u=U(n),c=I(),r=q("button"),r.textContent=f,h=I(),d=q("section");for(let z=0;z⚔️ + Quick Match`,r,f,h=`🏠 + Create Private Room`,d,m,C=`🔗 + Join with a Code`,B,j,A,M=i[6]&&ue(i);return{c(){e=q("section"),t=q("label"),t.textContent=s,a=I(),o=q("input"),l=I(),n=q("section"),u=q("button"),u.innerHTML=c,r=I(),f=q("button"),f.innerHTML=h,d=I(),m=q("button"),m.innerHTML=C,B=I(),M&&M.c(),this.h()},l(N){e=b(N,"SECTION",{class:!0});var g=L(e);t=b(g,"LABEL",{class:!0,for:!0,"data-svelte-h":!0}),P(t)!=="svelte-1c246ey"&&(t.textContent=s),a=w(g),o=b(g,"INPUT",{id:!0,class:!0,placeholder:!0,maxlength:!0}),g.forEach(p),l=w(N),n=b(N,"SECTION",{class:!0});var E=L(n);u=b(E,"BUTTON",{class:!0,"data-svelte-h":!0}),P(u)!=="svelte-1q0du9z"&&(u.innerHTML=c),r=w(E),f=b(E,"BUTTON",{class:!0,"data-svelte-h":!0}),P(f)!=="svelte-k434vp"&&(f.innerHTML=h),d=w(E),m=b(E,"BUTTON",{class:!0,"data-svelte-h":!0}),P(m)!=="svelte-1gifiyg"&&(m.innerHTML=C),B=w(E),M&&M.l(E),E.forEach(p),this.h()},h(){_(t,"class","field-label svelte-my9qcz"),_(t,"for","name-input"),_(o,"id","name-input"),_(o,"class","text-input svelte-my9qcz"),_(o,"placeholder","Commandant…"),_(o,"maxlength","20"),_(e,"class","card svelte-my9qcz"),_(u,"class","btn btn-primary svelte-my9qcz"),_(f,"class","btn btn-secondary svelte-my9qcz"),_(m,"class","btn btn-ghost svelte-my9qcz"),_(n,"class","actions svelte-my9qcz")},m(N,g){k(N,e,g),y(e,t),y(e,a),y(e,o),$(o,i[2]),k(N,l,g),k(N,n,g),y(n,u),y(n,r),y(n,f),y(n,d),y(n,m),y(n,B),M&&M.m(n,null),j||(A=[H(o,"input",i[16]),H(o,"keydown",i[17]),H(u,"click",i[13]),H(f,"click",i[11]),H(m,"click",i[18])],j=!0)},p(N,g){g[0]&4&&o.value!==N[2]&&$(o,N[2]),N[6]?M?M.p(N,g):(M=ue(N),M.c(),M.m(n,null)):M&&(M.d(1),M=null)},d(N){N&&(p(e),p(l),p(n)),M&&M.d(),j=!1,me(A)}}}function oe(i){let e,t="You";return{c(){e=q("span"),e.textContent=t,this.h()},l(s){e=b(s,"SPAN",{class:!0,"data-svelte-h":!0}),P(e)!=="svelte-7ca0ra"&&(e.textContent=t),this.h()},h(){_(e,"class","badge badge-you svelte-my9qcz")},m(s,a){k(s,e,a)},d(s){s&&p(e)}}}function re(i){let e,t,s=i[29].name+"",a,o,l,n,u=i[29].ready?"✓ Ready":"Waiting",c,r=i[29].sid===i[10].id&&oe();return{c(){e=q("div"),t=q("span"),a=U(s),o=I(),r&&r.c(),l=I(),n=q("span"),c=U(u),this.h()},l(f){e=b(f,"DIV",{class:!0});var h=L(e);t=b(h,"SPAN",{class:!0});var d=L(t);a=x(d,s),d.forEach(p),o=w(h),r&&r.l(h),l=w(h),n=b(h,"SPAN",{class:!0});var m=L(n);c=x(m,u),m.forEach(p),h.forEach(p),this.h()},h(){_(t,"class","player-name svelte-my9qcz"),_(n,"class","badge svelte-my9qcz"),F(n,"badge-ready",i[29].ready),F(n,"badge-waiting",!i[29].ready),_(e,"class","player-row svelte-my9qcz"),F(e,"is-you",i[29].sid===i[10].id)},m(f,h){k(f,e,h),y(e,t),y(t,a),y(e,o),r&&r.m(e,null),y(e,l),y(e,n),y(n,c)},p(f,h){h[0]&1&&s!==(s=f[29].name+"")&&J(a,s),f[29].sid===f[10].id?r||(r=oe(),r.c(),r.m(e,l)):r&&(r.d(1),r=null),h[0]&1&&u!==(u=f[29].ready?"✓ Ready":"Waiting")&&J(c,u),h[0]&1&&F(n,"badge-ready",f[29].ready),h[0]&1&&F(n,"badge-waiting",!f[29].ready),h[0]&1025&&F(e,"is-you",f[29].sid===f[10].id)},d(f){f&&p(e),r&&r.d()}}}function ce(i){let e,t='Waiting for an opponent…
';return{c(){e=q("div"),e.innerHTML=t,this.h()},l(s){e=b(s,"DIV",{class:!0,"data-svelte-h":!0}),P(e)!=="svelte-e9cacr"&&(e.innerHTML=t),this.h()},h(){_(e,"class","player-row player-placeholder svelte-my9qcz")},m(s,a){k(s,e,a)},d(s){s&&p(e)}}}function Me(i){let e,t="Waiting for opponent to be ready…";return{c(){e=q("p"),e.textContent=t,this.h()},l(s){e=b(s,"P",{class:!0,"data-svelte-h":!0}),P(e)!=="svelte-yz6vah"&&(e.textContent=t),this.h()},h(){_(e,"class","status-text svelte-my9qcz")},m(s,a){k(s,e,a)},p:W,d(s){s&&p(e)}}}function Le(i){let e,t="✓ Ready!",s,a;return{c(){e=q("button"),e.textContent=t,this.h()},l(o){e=b(o,"BUTTON",{class:!0,"data-svelte-h":!0}),P(e)!=="svelte-8ask0q"&&(e.textContent=t),this.h()},h(){_(e,"class","btn btn-primary btn-large svelte-my9qcz")},m(o,l){k(o,e,l),s||(a=H(e,"click",i[14]),s=!0)},p:W,d(o){o&&p(e),s=!1,a()}}}function Se(i){let e,t,s,a,o;return{c(){e=q("p"),t=U("Bot available in "),s=q("span"),a=U(i[8]),o=U("s"),this.h()},l(l){e=b(l,"P",{class:!0});var n=L(e);t=x(n,"Bot available in "),s=b(n,"SPAN",{class:!0});var u=L(s);a=x(u,i[8]),o=x(u,"s"),u.forEach(p),n.forEach(p),this.h()},h(){_(s,"class","countdown svelte-my9qcz"),_(e,"class","status-muted countdown-hint svelte-my9qcz")},m(l,n){k(l,e,n),y(e,t),y(e,s),y(s,a),y(s,o)},p(l,n){n[0]&256&&J(a,l[8])},d(l){l&&p(e)}}}function Be(i){let e,t,s="🤖",a,o,l='

No opponent joined.

Play against the AI Bot?

',n,u,c="Play",r,f;return{c(){e=q("div"),t=q("span"),t.textContent=s,a=I(),o=q("div"),o.innerHTML=l,n=I(),u=q("button"),u.textContent=c,this.h()},l(h){e=b(h,"DIV",{class:!0});var d=L(e);t=b(d,"SPAN",{class:!0,"data-svelte-h":!0}),P(t)!=="svelte-j0e6yx"&&(t.textContent=s),a=w(d),o=b(d,"DIV",{class:!0,"data-svelte-h":!0}),P(o)!=="svelte-wffmgz"&&(o.innerHTML=l),n=w(d),u=b(d,"BUTTON",{class:!0,"data-svelte-h":!0}),P(u)!=="svelte-1qh2kxo"&&(u.textContent=c),d.forEach(p),this.h()},h(){_(t,"class","bot-offer-icon svelte-my9qcz"),_(o,"class","bot-offer-text svelte-my9qcz"),_(u,"class","btn btn-primary btn-sm svelte-my9qcz"),_(e,"class","bot-offer-card svelte-my9qcz")},m(h,d){k(h,e,d),y(e,t),y(e,a),y(e,o),y(e,n),y(e,u),r||(f=H(u,"click",i[15]),r=!0)},p:W,d(h){h&&p(e),r=!1,f()}}}function Oe(i){let e,t,s,a="Looking for an opponent…",o,l;function n(r,f){return r[8]>0?je:He}let u=n(i),c=u(i);return{c(){e=q("div"),t=I(),s=q("p"),s.textContent=a,o=I(),c.c(),l=ee(),this.h()},l(r){e=b(r,"DIV",{class:!0}),L(e).forEach(p),t=w(r),s=b(r,"P",{class:!0,"data-svelte-h":!0}),P(s)!=="svelte-9m5jdz"&&(s.textContent=a),o=w(r),c.l(r),l=ee(),this.h()},h(){_(e,"class","spinner svelte-my9qcz"),_(s,"class","status-text svelte-my9qcz")},m(r,f){k(r,e,f),k(r,t,f),k(r,s,f),k(r,o,f),c.m(r,f),k(r,l,f)},p(r,f){u===(u=n(r))&&c?c.p(r,f):(c.d(1),c=u(r),c&&(c.c(),c.m(l.parentNode,l)))},d(r){r&&(p(e),p(t),p(s),p(o),p(l)),c.d(r)}}}function Ae(i){let e,t="🤖",s,a,o="No opponent found",l,n,u="Play against the AI bot?",c,r,f=`🤖 + Play vs Bot`,h,d;return{c(){e=q("div"),e.textContent=t,s=I(),a=q("p"),a.textContent=o,l=I(),n=q("p"),n.textContent=u,c=I(),r=q("button"),r.innerHTML=f,this.h()},l(m){e=b(m,"DIV",{class:!0,"data-svelte-h":!0}),P(e)!=="svelte-ecr2bm"&&(e.textContent=t),s=w(m),a=b(m,"P",{class:!0,"data-svelte-h":!0}),P(a)!=="svelte-15x1pdz"&&(a.textContent=o),l=w(m),n=b(m,"P",{class:!0,"data-svelte-h":!0}),P(n)!=="svelte-1bpva6j"&&(n.textContent=u),c=w(m),r=b(m,"BUTTON",{class:!0,"data-svelte-h":!0}),P(r)!=="svelte-1rdvgvd"&&(r.innerHTML=f),this.h()},h(){_(e,"class","bot-icon svelte-my9qcz"),_(a,"class","status-text svelte-my9qcz"),_(n,"class","status-muted svelte-my9qcz"),_(r,"class","btn btn-primary svelte-my9qcz")},m(m,C){k(m,e,C),k(m,s,C),k(m,a,C),k(m,l,C),k(m,n,C),k(m,c,C),k(m,r,C),h||(d=H(r,"click",i[15]),h=!0)},p:W,d(m){m&&(p(e),p(s),p(a),p(l),p(n),p(c),p(r)),h=!1,d()}}}function He(i){let e,t="Game starts as soon as another player joins";return{c(){e=q("p"),e.textContent=t,this.h()},l(s){e=b(s,"P",{class:!0,"data-svelte-h":!0}),P(e)!=="svelte-1xig9vu"&&(e.textContent=t),this.h()},h(){_(e,"class","status-muted svelte-my9qcz")},m(s,a){k(s,e,a)},p:W,d(s){s&&p(e)}}}function je(i){let e,t,s,a,o;return{c(){e=q("p"),t=U("Bot available in "),s=q("span"),a=U(i[8]),o=U("s"),this.h()},l(l){e=b(l,"P",{class:!0});var n=L(e);t=x(n,"Bot available in "),s=b(n,"SPAN",{class:!0});var u=L(s);a=x(u,i[8]),o=x(u,"s"),u.forEach(p),n.forEach(p),this.h()},h(){_(s,"class","countdown svelte-my9qcz"),_(e,"class","status-muted svelte-my9qcz")},m(l,n){k(l,e,n),y(e,t),y(e,s),y(s,a),y(s,o)},p(l,n){n[0]&256&&J(a,l[8])},d(l){l&&p(e)}}}function ue(i){let e,t,s,a,o="OK",l,n;return{c(){e=q("div"),t=q("input"),s=I(),a=q("button"),a.textContent=o,this.h()},l(u){e=b(u,"DIV",{class:!0});var c=L(e);t=b(c,"INPUT",{class:!0,placeholder:!0,maxlength:!0}),s=w(c),a=b(c,"BUTTON",{class:!0,"data-svelte-h":!0}),P(a)!=="svelte-advd9m"&&(a.textContent=o),c.forEach(p),this.h()},h(){_(t,"class","text-input code-input svelte-my9qcz"),_(t,"placeholder","ABC123"),_(t,"maxlength","6"),_(a,"class","btn btn-primary btn-sm svelte-my9qcz"),_(e,"class","join-row svelte-my9qcz")},m(u,c){k(u,e,c),y(e,t),$(t,i[3]),y(e,s),y(e,a),l||(n=[H(t,"input",i[19]),H(t,"keydown",i[20]),H(a,"click",i[12])],l=!0)},p(u,c){c[0]&8&&t.value!==u[3]&&$(t,u[3])},d(u){u&&p(e),l=!1,me(n)}}}function xe(i){let e,t,s=`

Real-time strategy — voice controlled

`,a,o,l,n,u='Sprites Sounds Map',c=i[5]&&ae(i);function r(m,C){if(m[4]==="home")return Pe;if(m[4]==="queue")return Ie;if(m[4]==="waiting")return ie}function f(m,C){return C===ie?we(m):m}let h=r(i),d=h&&h(f(i,h));return{c(){e=q("main"),t=q("div"),t.innerHTML=s,a=I(),c&&c.c(),o=I(),d&&d.c(),l=I(),n=q("nav"),n.innerHTML=u,this.h()},l(m){e=b(m,"MAIN",{class:!0});var C=L(e);t=b(C,"DIV",{class:!0,"data-svelte-h":!0}),P(t)!=="svelte-1eemcz4"&&(t.innerHTML=s),a=w(C),c&&c.l(C),o=w(C),d&&d.l(C),l=w(C),n=b(C,"NAV",{class:!0,"aria-label":!0,"data-svelte-h":!0}),P(n)!=="svelte-1mfgds2"&&(n.innerHTML=u),C.forEach(p),this.h()},h(){_(t,"class","hero svelte-my9qcz"),_(n,"class","admin-links svelte-my9qcz"),_(n,"aria-label","Admin"),_(e,"class","lobby svelte-my9qcz")},m(m,C){k(m,e,C),y(e,t),y(e,a),c&&c.m(e,null),y(e,o),d&&d.m(e,null),y(e,l),y(e,n)},p(m,C){m[5]?c?c.p(m,C):(c=ae(m),c.c(),c.m(e,o)):c&&(c.d(1),c=null),h===(h=r(m))&&d?d.p(f(m,h),C):(d&&d.d(1),d=h&&h(f(m,h)),d&&(d.c(),d.m(e,l)))},i:W,o:W,d(m){m&&p(e),c&&c.d(),d&&d.d()}}}const fe=10;function Ue(i,e,t){let s,a,o;pe(i,Z,v=>t(24,o=v));let l=ye(),n=o||"",u="",c="home",r="",f=null,h=!1,d=!1,m=fe,C=null;function B(v){t(5,r=v),setTimeout(()=>t(5,r=""),3e3)}function j(){t(7,d=!1),t(8,m=fe),A(),C=setInterval(()=>{t(8,m-=1),m<=0&&A()},1e3)}function A(){C!==null&&(clearInterval(C),C=null)}_e(()=>{l.on("room_created",({room_id:v,room:T})=>{te.set(v),X.set(T),t(0,f=T),t(4,c="waiting"),j()}),l.on("room_joined",({room_id:v,room:T})=>{te.set(v),X.set(T),t(0,f=T),t(4,c="waiting"),A(),t(7,d=!1)}),l.on("room_update",({room:v})=>{X.set(v),t(0,f=v),v.players.length>=2&&(A(),t(7,d=!1))}),l.on("match_found",({room_id:v,room:T})=>{te.set(v),X.set(T),t(0,f=T),t(4,c="waiting"),A(),t(7,d=!1)}),l.on("match_queued",()=>{t(4,c="queue"),j()}),l.on("bot_offer",()=>{A(),t(7,d=!0)}),l.on("game_start",({game_state:v,your_id:T})=>{A(),ze.set(v),ke.set(T),ge.set(null),Ce("/game")}),l.on("error",({message:v})=>B(v))}),he(()=>{A(),l.off("room_created"),l.off("room_joined"),l.off("room_update"),l.off("match_found"),l.off("match_queued"),l.off("bot_offer"),l.off("game_start"),l.off("error")});function M(){if(!n.trim()){B("Enter your name");return}Z.set(n.trim()),l.emit("create_room",{name:n.trim()})}function N(){if(!n.trim()){B("Enter your name");return}if(!u.trim()){B("Enter the room code");return}Z.set(n.trim()),l.emit("join_room",{room_id:u.trim().toUpperCase(),name:n.trim()})}function g(){if(!n.trim()){B("Enter your name");return}Z.set(n.trim()),l.emit("quick_match",{name:n.trim()})}function E(){l.emit("player_ready",{})}function Y(){t(7,d=!1),l.emit("accept_bot",{})}function D(){n=this.value,t(2,n)}const S=v=>v.key==="Enter"&&g(),G=()=>t(6,h=!h);function K(){u=this.value,t(3,u)}const Q=v=>v.key==="Enter"&&N(),z=v=>navigator.clipboard.writeText((v==null?void 0:v.room_id)??"");return i.$$.update=()=>{i.$$.dirty[0]&1&&t(1,s=f==null?void 0:f.players.find(v=>v.sid===l.id)),i.$$.dirty[0]&1&&(f==null||f.players.find(v=>v.sid!==l.id)),i.$$.dirty[0]&3&&t(9,a=(f==null?void 0:f.players.length)===2&&!(s!=null&&s.ready))},[f,s,n,u,c,r,h,d,m,a,l,M,N,g,E,Y,D,S,G,K,Q,z]}class Ye extends be{constructor(e){super(),qe(this,e,Ue,xe,de,{},null,[-1,-1])}}export{Ye as component,Je as universal}; diff --git a/frontend/build/_app/immutable/nodes/2.CwsLFqkm.js b/frontend/build/_app/immutable/nodes/2.CwsLFqkm.js deleted file mode 100644 index 6265ff915dc3ad7a1772c7ee81895004c882aad8..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/nodes/2.CwsLFqkm.js +++ /dev/null @@ -1,4 +0,0 @@ -import{s as fe,n as x,d as g,i as S,e as v,p as _,f as j,h as q,q as H,k as L,l as y,m as P,o as pe,r as _e,v as he,b as G,j as Q,t as W,w as ue,x as X,y as B,z as me,A as se,B as z}from"../chunks/DQebRFVS.js";import{g as ve,r as Y,a as K,b as ge,m as be,w as je,p as F,e as le}from"../chunks/BxRIPBY0.js";import{S as ye,i as ke}from"../chunks/BX82rj4I.js";import{g as Ce}from"../chunks/J0QSAnNz.js";const Te=!1,Ee=!1,Be=Object.freeze(Object.defineProperty({__proto__:null,prerender:Ee,ssr:Te},Symbol.toStringTag,{value:"Module"}));function ne(l,e,t){const s=l.slice();return s[23]=e[t],s}function Ne(l){const e=l.slice(),t=e[0];return e[22]=t,e}function ie(l){let e,t;return{c(){e=y("div"),t=W(l[5]),this.h()},l(s){e=j(s,"DIV",{class:!0});var r=q(e);t=Q(r,l[5]),r.forEach(g),this.h()},h(){_(e,"class","error-toast svelte-cigodj")},m(s,r){S(s,e,r),v(e,t)},p(s,r){r&32&&G(t,s[5])},d(s){s&&g(e)}}}function ae(l){var Z,$,ee;let e,t,s,r="Code de la room",u,n,a=((Z=l[22])==null?void 0:Z.room_id)+"",d,f,o,i="Copier",c,C,N,A,O,U,D;function E(){return l[18](l[22])}let T=le((($=l[22])==null?void 0:$.players)??[]),h=[];for(let m=0;m⚔️ - Partie rapide`,o,i,c=`🏠 - Créer une room privée`,C,N,A=`🔗 - Rejoindre avec un code`,O,U,D,E=l[6]&&de(l);return{c(){e=y("section"),t=y("label"),t.textContent=s,r=P(),u=y("input"),n=P(),a=y("section"),d=y("button"),d.innerHTML=f,o=P(),i=y("button"),i.innerHTML=c,C=P(),N=y("button"),N.innerHTML=A,O=P(),E&&E.c(),this.h()},l(T){e=j(T,"SECTION",{class:!0});var h=q(e);t=j(h,"LABEL",{class:!0,for:!0,"data-svelte-h":!0}),H(t)!=="svelte-1bn4wxd"&&(t.textContent=s),r=L(h),u=j(h,"INPUT",{id:!0,class:!0,placeholder:!0,maxlength:!0}),h.forEach(g),n=L(T),a=j(T,"SECTION",{class:!0});var k=q(a);d=j(k,"BUTTON",{class:!0,"data-svelte-h":!0}),H(d)!=="svelte-vmjj4t"&&(d.innerHTML=f),o=L(k),i=j(k,"BUTTON",{class:!0,"data-svelte-h":!0}),H(i)!=="svelte-1yhx17u"&&(i.innerHTML=c),C=L(k),N=j(k,"BUTTON",{class:!0,"data-svelte-h":!0}),H(N)!=="svelte-roe2vr"&&(N.innerHTML=A),O=L(k),E&&E.l(k),k.forEach(g),this.h()},h(){_(t,"class","field-label svelte-cigodj"),_(t,"for","name-input"),_(u,"id","name-input"),_(u,"class","text-input svelte-cigodj"),_(u,"placeholder","Commandant…"),_(u,"maxlength","20"),_(e,"class","card svelte-cigodj"),_(d,"class","btn btn-primary svelte-cigodj"),_(i,"class","btn btn-secondary svelte-cigodj"),_(N,"class","btn btn-ghost svelte-cigodj"),_(a,"class","actions svelte-cigodj")},m(T,h){S(T,e,h),v(e,t),v(e,r),v(e,u),X(u,l[2]),S(T,n,h),S(T,a,h),v(a,d),v(a,o),v(a,i),v(a,C),v(a,N),v(a,O),E&&E.m(a,null),U||(D=[B(u,"input",l[13]),B(u,"keydown",l[14]),B(d,"click",l[11]),B(i,"click",l[9]),B(N,"click",l[15])],U=!0)},p(T,h){h&4&&u.value!==T[2]&&X(u,T[2]),T[6]?E?E.p(T,h):(E=de(T),E.c(),E.m(a,null)):E&&(E.d(1),E=null)},d(T){T&&(g(e),g(n),g(a)),E&&E.d(),U=!1,ue(D)}}}function oe(l){let e,t="Toi";return{c(){e=y("span"),e.textContent=t,this.h()},l(s){e=j(s,"SPAN",{class:!0,"data-svelte-h":!0}),H(e)!=="svelte-1w50377"&&(e.textContent=t),this.h()},h(){_(e,"class","badge badge-you svelte-cigodj")},m(s,r){S(s,e,r)},d(s){s&&g(e)}}}function re(l){let e,t,s=l[23].name+"",r,u,n,a,d=l[23].ready?"✓ Prêt":"En attente",f,o=l[23].sid===l[8].id&&oe();return{c(){e=y("div"),t=y("span"),r=W(s),u=P(),o&&o.c(),n=P(),a=y("span"),f=W(d),this.h()},l(i){e=j(i,"DIV",{class:!0});var c=q(e);t=j(c,"SPAN",{class:!0});var C=q(t);r=Q(C,s),C.forEach(g),u=L(c),o&&o.l(c),n=L(c),a=j(c,"SPAN",{class:!0});var N=q(a);f=Q(N,d),N.forEach(g),c.forEach(g),this.h()},h(){_(t,"class","player-name svelte-cigodj"),_(a,"class","badge svelte-cigodj"),z(a,"badge-ready",l[23].ready),z(a,"badge-waiting",!l[23].ready),_(e,"class","player-row svelte-cigodj"),z(e,"is-you",l[23].sid===l[8].id)},m(i,c){S(i,e,c),v(e,t),v(t,r),v(e,u),o&&o.m(e,null),v(e,n),v(e,a),v(a,f)},p(i,c){c&1&&s!==(s=i[23].name+"")&&G(r,s),i[23].sid===i[8].id?o||(o=oe(),o.c(),o.m(e,n)):o&&(o.d(1),o=null),c&1&&d!==(d=i[23].ready?"✓ Prêt":"En attente")&&G(f,d),c&1&&z(a,"badge-ready",i[23].ready),c&1&&z(a,"badge-waiting",!i[23].ready),c&257&&z(e,"is-you",i[23].sid===i[8].id)},d(i){i&&g(e),o&&o.d()}}}function ce(l){let e,t='En attente d'un adversaire…
';return{c(){e=y("div"),e.innerHTML=t,this.h()},l(s){e=j(s,"DIV",{class:!0,"data-svelte-h":!0}),H(e)!=="svelte-okzxxd"&&(e.innerHTML=t),this.h()},h(){_(e,"class","player-row player-placeholder svelte-cigodj")},m(s,r){S(s,e,r)},d(s){s&&g(e)}}}function Ie(l){let e,t="En attente que l'adversaire soit prêt…";return{c(){e=y("p"),e.textContent=t,this.h()},l(s){e=j(s,"P",{class:!0,"data-svelte-h":!0}),H(e)!=="svelte-bcni62"&&(e.textContent=t),this.h()},h(){_(e,"class","status-text svelte-cigodj")},m(s,r){S(s,e,r)},p:x,d(s){s&&g(e)}}}function Me(l){let e,t="✓ Prêt !",s,r;return{c(){e=y("button"),e.textContent=t,this.h()},l(u){e=j(u,"BUTTON",{class:!0,"data-svelte-h":!0}),H(e)!=="svelte-1kv5ql1"&&(e.textContent=t),this.h()},h(){_(e,"class","btn btn-primary btn-large svelte-cigodj")},m(u,n){S(u,e,n),s||(r=B(e,"click",l[12]),s=!0)},p:x,d(u){u&&g(e),s=!1,r()}}}function de(l){let e,t,s,r,u="OK",n,a;return{c(){e=y("div"),t=y("input"),s=P(),r=y("button"),r.textContent=u,this.h()},l(d){e=j(d,"DIV",{class:!0});var f=q(e);t=j(f,"INPUT",{class:!0,placeholder:!0,maxlength:!0}),s=L(f),r=j(f,"BUTTON",{class:!0,"data-svelte-h":!0}),H(r)!=="svelte-advd9m"&&(r.textContent=u),f.forEach(g),this.h()},h(){_(t,"class","text-input code-input svelte-cigodj"),_(t,"placeholder","ABC123"),_(t,"maxlength","6"),_(r,"class","btn btn-primary btn-sm svelte-cigodj"),_(e,"class","join-row svelte-cigodj")},m(d,f){S(d,e,f),v(e,t),X(t,l[3]),v(e,s),v(e,r),n||(a=[B(t,"input",l[16]),B(t,"keydown",l[17]),B(r,"click",l[10])],n=!0)},p(d,f){f&8&&t.value!==d[3]&&X(t,d[3])},d(d){d&&g(e),n=!1,ue(a)}}}function Le(l){let e,t,s='

Stratégie en temps réel — commandé à la voix

',r,u,n=l[5]&&ie(l);function a(i,c){if(i[4]==="home")return Se;if(i[4]==="queue")return we;if(i[4]==="waiting")return ae}function d(i,c){return c===ae?Ne(i):i}let f=a(l),o=f&&f(d(l,f));return{c(){e=y("main"),t=y("header"),t.innerHTML=s,r=P(),n&&n.c(),u=P(),o&&o.c(),this.h()},l(i){e=j(i,"MAIN",{class:!0});var c=q(e);t=j(c,"HEADER",{class:!0,"data-svelte-h":!0}),H(t)!=="svelte-igadcu"&&(t.innerHTML=s),r=L(c),n&&n.l(c),u=L(c),o&&o.l(c),c.forEach(g),this.h()},h(){_(t,"class","lobby-header svelte-cigodj"),_(e,"class","lobby svelte-cigodj")},m(i,c){S(i,e,c),v(e,t),v(e,r),n&&n.m(e,null),v(e,u),o&&o.m(e,null)},p(i,[c]){i[5]?n?n.p(i,c):(n=ie(i),n.c(),n.m(e,u)):n&&(n.d(1),n=null),f===(f=a(i))&&o?o.p(d(i,f),c):(o&&o.d(1),o=f&&f(d(i,f)),o&&(o.c(),o.m(e,null)))},i:x,o:x,d(i){i&&g(e),n&&n.d(),o&&o.d()}}}function Pe(l,e,t){let s,r,u;pe(l,F,p=>t(20,u=p));let n=ve(),a=u||"",d="",f="home",o="",i=null,c=!1;function C(p){t(5,o=p),setTimeout(()=>t(5,o=""),3e3)}_e(()=>{n.on("room_created",({room_id:p,room:b})=>{Y.set(p),K.set(b),t(0,i=b),t(4,f="waiting")}),n.on("room_joined",({room_id:p,room:b})=>{Y.set(p),K.set(b),t(0,i=b),t(4,f="waiting")}),n.on("room_update",({room:p})=>{K.set(p),t(0,i=p)}),n.on("match_found",({room_id:p,room:b})=>{Y.set(p),K.set(b),t(0,i=b),t(4,f="waiting")}),n.on("match_queued",()=>{t(4,f="queue")}),n.on("game_start",({game_state:p,your_id:b})=>{ge.set(p),be.set(b),je.set(null),Ce("/game")}),n.on("error",({message:p})=>C(p))}),he(()=>{n.off("room_created"),n.off("room_joined"),n.off("room_update"),n.off("match_found"),n.off("match_queued"),n.off("game_start"),n.off("error")});function N(){if(!a.trim()){C("Entre ton nom");return}F.set(a.trim()),n.emit("create_room",{name:a.trim()})}function A(){if(!a.trim()){C("Entre ton nom");return}if(!d.trim()){C("Entre le code de la room");return}F.set(a.trim()),n.emit("join_room",{room_id:d.trim().toUpperCase(),name:a.trim()})}function O(){if(!a.trim()){C("Entre ton nom");return}F.set(a.trim()),n.emit("quick_match",{name:a.trim()})}function U(){n.emit("player_ready",{})}function D(){a=this.value,t(2,a)}const E=p=>p.key==="Enter"&&O(),T=()=>t(6,c=!c);function h(){d=this.value,t(3,d)}const k=p=>p.key==="Enter"&&A(),J=p=>navigator.clipboard.writeText((p==null?void 0:p.room_id)??"");return l.$$.update=()=>{l.$$.dirty&1&&t(1,s=i==null?void 0:i.players.find(p=>p.sid===n.id)),l.$$.dirty&1&&(i==null||i.players.find(p=>p.sid!==n.id)),l.$$.dirty&3&&t(7,r=(i==null?void 0:i.players.length)===2&&!(s!=null&&s.ready))},[i,s,a,d,f,o,c,r,n,N,A,O,U,D,E,T,h,k,J]}class Ue extends ye{constructor(e){super(),ke(this,e,Pe,Le,fe,{})}}export{Ue as component,Be as universal}; diff --git a/frontend/build/_app/immutable/nodes/3.BjvrdcVH.js b/frontend/build/_app/immutable/nodes/3.BjvrdcVH.js new file mode 100644 index 0000000000000000000000000000000000000000..ddf7ff1e70b4581d2da73494b005627ed5a13f11 --- /dev/null +++ b/frontend/build/_app/immutable/nodes/3.BjvrdcVH.js @@ -0,0 +1,3 @@ +import{s as ct,n as Se,d as g,i as P,e as m,p as o,q as ut,k as L,f as S,h as j,r as te,m as q,l as D,v as ft,w as ht,b as ue,j as U,t as z,A as Ee,B as ze,C as ce,x as De,z as Oe,D as Ae,y as X,E as Z,F as Q,G as K,H as Be}from"../chunks/BWggeOnc.js";import{b as _t,e as G}from"../chunks/B2mXDaCf.js";import{S as pt,i as dt}from"../chunks/CbPkTnjI.js";const bt=!1,jt=Object.freeze(Object.defineProperty({__proto__:null,ssr:bt},Symbol.toStringTag,{value:"Module"}));function Le(l,e,t){const n=l.slice();return n[47]=e[t],n[49]=t,n}function qe(l,e,t){const n=l.slice();return n[50]=e[t],n[52]=t,n}function Fe(l,e,t){const n=l.slice();return n[52]=e[t],n}function He(l,e,t){const n=l.slice();return n[55]=e[t][0],n[56]=e[t][1],n}function We(l,e,t){const n=l.slice();return n[59]=e[t],n}function Ve(l,e,t){const n=l.slice();return n[50]=e[t],n[52]=t,n}function Ye(l,e,t){const n=l.slice();return n[50]=e[t],n[52]=t,n}function Ge(l,e,t){const n=l.slice();return n[64]=e[t],n[49]=t,n}function Re(l,e,t){const n=l.slice();return n[55]=e[t][0],n[56]=e[t][1],n[67]=t,n}function Xe(l){let e,t;return{c(){e=D("p"),t=z(l[5]),this.h()},l(n){e=S(n,"P",{class:!0});var i=j(e);t=U(i,l[5]),i.forEach(g),this.h()},h(){o(e,"class","error svelte-8d56b4")},m(n,i){P(n,e,i),m(e,t)},p(n,i){i[0]&32&&ue(t,n[5])},d(n){n&&g(e)}}}function Je(l){let e,t;return{c(){e=D("p"),t=z(l[6]),this.h()},l(n){e=S(n,"P",{class:!0});var i=j(e);t=U(i,l[6]),i.forEach(g),this.h()},h(){o(e,"class","save-status svelte-8d56b4"),Ee(e,"status-success",l[6].startsWith("Sauvegardé"))},m(n,i){P(n,e,i),m(e,t)},p(n,i){i[0]&64&&ue(t,n[6]),i[0]&64&&Ee(e,"status-success",n[6].startsWith("Sauvegardé"))},d(n){n&&g(e)}}}function gt(l){let e,t,n,i,s,r,p,u,_,f,h,d,v,k,I,T,M=l[7]?"Enregistrement…":"Enregistrer",x,ne,J,F,se,we="Positions de jeu (départ + expansions)",de,oe,xe="3 positions de départ : à chaque partie, 2 sont tirées au sort pour les 2 joueurs. Les minerais (et geysers) sont générés autour de chaque position.",be,$,re,Ce,le,pe=l[8]?"Enregistrement…":"Enregistrer et générer les minerais",ge,me,ve,ie,ke,je,ae=G(l[1]),H=[];for(let a=0;a0&&tt(l);function Ne(a,E){return a[12]?yt:a[13]?kt:vt}let Te=Ne(l),b=Te(l),y=G([1,2,3]),C=[];for(let a=0;a<3;a+=1)C[a]=nt(Fe(l,y,a));let O=G(l[3]),w=[];for(let a=0;a0&&ot(l);return{c(){e=D("div"),t=D("img"),i=q(),s=K("svg");for(let a=0;a0?R?R.p(a,E):(R=tt(a),R.c(),R.m(s,f)):R&&(R.d(1),R=null),E[0]&12288&&h!==(h=a[13]?"Carte : clic pour placer une position":a[12]?"Carte : en mode ajout, clic pour placer un point":"")&&o(f,"aria-label",h),E[0]&12288&&d!==(d=a[12]||a[13]?0:-1)&&o(f,"tabindex",d),E[0]&12288&&Ee(f,"active",a[12]||a[13]),E[0]&131072&&Ae(e,"aspect-ratio",a[17]),Te===(Te=Ne(a))&&b?b.p(a,E):(b.d(1),b=Te(a),b&&(b.c(),b.m(k,I))),E[0]&128&&M!==(M=a[7]?"Enregistrement…":"Enregistrer")&&ue(x,M),E[0]&130&&ne!==(ne=a[7]||a[1].length===0)&&(T.disabled=ne),E[0]&268435460){y=G([1,2,3]);let c;for(c=0;c<3;c+=1){const N=Fe(a,y,c);C[c]?C[c].p(N,E):(C[c]=nt(N),C[c].c(),C[c].m($,re))}for(;c<3;c+=1)C[c].d(1)}if(E[0]&536870920){O=G(a[3]);let c;for(c=0;c0?A?A.p(a,E):(A=ot(a),A.c(),A.m(ie.parentNode,ie)):A&&(A.d(1),A=null)},d(a){a&&(g(e),g(v),g(k),g(J),g(F),g(ve),g(ie)),ce(H,a),ce(W,a),ce(V,a),ce(Y,a),R&&R.d(),l[37](null),l[38](null),b.d(),ce(C,a),ce(w,a),A&&A.d(a),ke=!1,De(je)}}}function mt(l){let e,t="Chargement…";return{c(){e=D("p"),e.textContent=t},l(n){e=S(n,"P",{"data-svelte-h":!0}),te(e)!=="svelte-5hnkvu"&&(e.textContent=t)},m(n,i){P(n,e,i)},p:Se,d(n){n&&g(e)}}}function Ze(l){let e,t,n,i,s;function r(...u){return l[34](l[49],l[67],...u)}function p(){return l[35](l[49],l[67])}return{c(){e=K("circle"),this.h()},l(u){e=Z(u,"circle",{cx:!0,cy:!0,r:!0,fill:!0,stroke:!0,"stroke-width":!0,class:!0,role:!0,tabindex:!0,"aria-label":!0}),j(e).forEach(g),this.h()},h(){var u,_;o(e,"cx",t=l[55]),o(e,"cy",n=l[56]),o(e,"r","1.2"),o(e,"fill","rgb(34, 197, 94)"),o(e,"stroke","white"),o(e,"stroke-width","0.3"),o(e,"class","poly-point svelte-8d56b4"),o(e,"role","button"),o(e,"tabindex","0"),o(e,"aria-label","Double-clic pour supprimer le point"),Ee(e,"dragging",((u=l[11])==null?void 0:u.polygonIndex)===l[49]&&((_=l[11])==null?void 0:_.pointIndex)===l[67])},m(u,_){P(u,e,_),i||(s=[X(e,"pointerdown",Be(r)),X(e,"dblclick",Be(p))],i=!0)},p(u,_){var f,h;l=u,_[0]&2&&t!==(t=l[55])&&o(e,"cx",t),_[0]&2&&n!==(n=l[56])&&o(e,"cy",n),_[0]&2048&&Ee(e,"dragging",((f=l[11])==null?void 0:f.polygonIndex)===l[49]&&((h=l[11])==null?void 0:h.pointIndex)===l[67])},d(u){u&&g(e),i=!1,De(s)}}}function Ke(l){let e,t,n,i,s;function r(..._){return l[33](l[49],..._)}let p=G(l[64]),u=[];for(let _=0;_`${e},${t}`).join(" ")}const at=([l,e])=>`${l},${e}`;function Ct(l,e,t){let n="",i=[],s=[],r=[],p=[],u=!0,_="",f="",h=!1,d=!1,v=!1,k,I,T=null,M=!1,x=!1,ne="start1",J=[],F="",se=1;function we(b,y){const C=s[b];if(C.length<=3){t(1,s=s.filter((O,w)=>w!==b));return}t(1,s[b]=C.filter((O,w)=>w!==y),s),t(1,s)}function de(b,y){if(!k)return;b.stopPropagation();const[C,O]=Ie(k,b.clientX,b.clientY),w=s[y],A=2.5;let a={dist:1/0,t:0,x:0,y:0};const E=w.length;for(let c=0;c({x:w.x,y:w.y}))),t(3,p=(C.expansion_positions??[]).map(w=>({x:w.x,y:w.y})));const O=C.walkable_polygons??((b=C.walkable_polygon)!=null&&b.length?[C.walkable_polygon]:[]);t(1,s=O.map(w=>w.map(A=>[A[0],A[1]])))}catch(y){if(v)return;t(5,_=y instanceof Error?y.message:String(y))}finally{v||t(4,u=!1)}}async function xe(){if(s.length){t(7,h=!0),t(6,f="");try{const b=await fetch(`${F}/api/map/walkable`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({polygons:s})}),y=await b.json().catch(()=>({}));if(!b.ok)throw new Error(y.detail||b.statusText);t(6,f="Sauvegardé.")}catch(b){t(6,f=b instanceof Error?b.message:String(b))}finally{v||t(7,h=!1)}}}function be(b,y,C){var O,w;b.preventDefault(),(w=(O=b.currentTarget).setPointerCapture)==null||w.call(O,b.pointerId),t(11,T={polygonIndex:y,pointIndex:C})}function $(b){if(!k||T===null)return;const[y,C]=Ie(k,b.clientX,b.clientY),O=[ye(y,0,100),ye(C,0,100)],{polygonIndex:w,pointIndex:A}=T;t(1,s[w][A]=O,s),t(1,s)}function re(){t(11,T=null)}function Ce(){t(12,M=!0),t(15,J=[])}function le(){J.length>=3&&t(1,s=[...s,[...J]]),t(15,J=[]),t(12,M=!1)}function pe(){t(15,J=[]),t(12,M=!1)}function ge(b){if(!k)return;const[y,C]=Ie(k,b.clientX,b.clientY),O=[ye(y,0,100),ye(C,0,100)];if(x){b.preventDefault(),b.stopPropagation(),r.length<3?(t(2,r=[...r,{x:O[0],y:O[1]}]),t(14,ne=r.length===1?"start2":r.length===2?"start3":"expansion")):t(3,p=[...p,{x:O[0],y:O[1]}]);return}M&&(b.preventDefault(),b.stopPropagation(),t(15,J=[...J,O]))}function me(){t(13,x=!0),t(12,M=!1),r.length>=3?t(14,ne="expansion"):r.length===2?t(14,ne="start3"):r.length===1?t(14,ne="start2"):t(14,ne="start1")}function ve(){t(13,x=!1)}function ie(b){t(2,r=r.filter((y,C)=>C!==b))}function ke(b){t(3,p=p.filter((y,C)=>C!==b))}async function je(){if(r.length!==3){t(6,f="Il faut exactement 3 positions de départ (2 seront tirées au sort par partie).");return}t(8,d=!0),t(6,f="");try{const b=await fetch(`${F}/api/map/positions`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({starting_positions:r,expansion_positions:p})}),y=await b.json().catch(()=>({}));if(!b.ok)throw new Error(y.detail||b.statusText);t(6,f=`Positions enregistrées. ${y.minerals_count??0} minerais et ${y.geysers_count??0} geysers générés.`)}catch(b){t(6,f=b instanceof Error?b.message:String(b))}finally{v||t(8,d=!1)}}function ae(b){t(1,s=s.filter((y,C)=>C!==b))}function H(b){const y=b.target;y!=null&&y.naturalWidth&&y.naturalHeight&&t(17,se=y.naturalWidth/y.naturalHeight)}ft(()=>{typeof window>"u"||(t(16,F=_t()),oe(),window.addEventListener("pointermove",$),window.addEventListener("pointerup",re),window.addEventListener("pointerleave",re))}),ht(()=>{v=!0,typeof window<"u"&&(window.removeEventListener("pointermove",$),window.removeEventListener("pointerup",re),window.removeEventListener("pointerleave",re))});const fe=(b,y)=>de(y,b),W=(b,y,C)=>be(C,b,y),he=(b,y)=>we(b,y),V=b=>{M&&(b.key==="Enter"||b.key===" ")&&(b.preventDefault(),t(15,J=[...J,[50,50]]))};function _e(b){ze[b?"unshift":"push"](()=>{k=b,t(9,k)})}function Y(b){ze[b?"unshift":"push"](()=>{I=b,t(10,I)})}return[i,s,r,p,u,_,f,h,d,k,I,T,M,x,ne,J,F,se,we,de,xe,be,Ce,le,pe,ge,me,ve,ie,ke,je,ae,H,fe,W,he,V,_e,Y,b=>ie(b-1),b=>ke(b),b=>ae(b)]}class Mt extends pt{constructor(e){super(),dt(this,e,Ct,Et,ct,{},null,[-1,-1,-1])}}export{Mt as component,jt as universal}; diff --git a/frontend/build/_app/immutable/nodes/3.xYrSFp1n.js b/frontend/build/_app/immutable/nodes/3.xYrSFp1n.js deleted file mode 100644 index d0127db83a39ba2dad07bf35bbecedbc998cb1a5..0000000000000000000000000000000000000000 --- a/frontend/build/_app/immutable/nodes/3.xYrSFp1n.js +++ /dev/null @@ -1 +0,0 @@ -import{s as Ne,n as ce,d as c,z as Pe,w as Ke,p as i,i as M,e as d,y as ie,U as Q,h as C,A as H,V as $,o as se,b as K,j as Y,t as X,G as Bt,B as fe,f as A,q as ae,k as B,l as I,m as U,W as Ut,F as ye,r as Yt,v as Xt}from"../chunks/DQebRFVS.js";import{S as je,i as Ae,d as Ve,t as Te,a as Oe,m as Be,e as Ue,b as Ye}from"../chunks/BX82rj4I.js";import{g as xe}from"../chunks/J0QSAnNz.js";import{e as oe,m as Qe,b as Le,s as ke,c as we,d as Lt,f as qt,v as Ee,l as Xe,h as Je,g as Rt,w as Ge}from"../chunks/BxRIPBY0.js";const zt=!1,Ft=!1,Dl=Object.freeze(Object.defineProperty({__proto__:null,prerender:Ft,ssr:zt},Symbol.toStringTag,{value:"Module"}));async function Ht(){const s=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,sampleRate:16e3,echoCancellation:!0,noiseSuppression:!0}}),e=Zt(),t=new MediaRecorder(s,e?{mimeType:e}:void 0),l=[];t.ondataavailable=a=>{a.data.size>0&&l.push(a.data)};let n;const r=new Promise(a=>{n=a});return t.onstop=()=>{s.getTracks().forEach(u=>u.stop());const a=new Blob(l,{type:t.mimeType||"audio/webm"});n({blob:a,mimeType:t.mimeType||"audio/webm"})},t.start(100),{stop:()=>{t.state!=="inactive"&&t.stop()},result:r}}async function Wt(s){return new Promise((e,t)=>{const l=new FileReader;l.onload=()=>{const n=l.result;e(n.split(",")[1])},l.onerror=t,l.readAsDataURL(s)})}function Gt(s,e="fr-FR"){if(typeof window>"u"||!("speechSynthesis"in window))return;window.speechSynthesis.cancel();const t=new SpeechSynthesisUtterance(s);t.lang=e,t.rate=1.05,t.pitch=1,t.volume=1,window.speechSynthesis.speak(t)}function Zt(){const s=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus","audio/mp4"];for(const e of s)if(typeof MediaRecorder<"u"&&MediaRecorder.isTypeSupported(e))return e;return""}const Tt={command_center:{w:4,h:3},barracks:{w:4,h:3},factory:{w:4,h:3},starport:{w:4,h:2},supply_depot:{w:3,h:2},engineering_bay:{w:3,h:2},armory:{w:3,h:2},refinery:{w:2,h:2}},Ot={command_center:"CC",supply_depot:"SD",barracks:"BAR",engineering_bay:"EB",refinery:"REF",factory:"FAC",armory:"ARM",starport:"SP"},Jt={scv:"S",marine:"M",medic:"+",goliath:"G",tank:"T",wraith:"W"},et={scv:"SCV — Ouvrier. Collecte ressources, construit bâtiments.",marine:"Marine — Infanterie de base. Anti-sol et anti-air.",medic:"Medic — Soigne l'infanterie adjacente.",goliath:"Goliath — Véhicule lourd. Anti-sol et anti-air.",tank:"Siege Tank — Véhicule d'artillerie. Mode siège : portée +5, dégâts +20.",wraith:"Wraith — Vaisseau aérien. Peut se camoufler."};function tt(s,e,t){var u;const l=s.slice();l[28]=e[t].u,l[29]=e[t].isOwn;const n=l[29]?"#58a6ff":"#f85149";l[30]=n;const r=Jt[l[28].unit_type];l[31]=r;const a=l[28].id===((u=l[9])==null?void 0:u.id);return l[32]=a,l}function lt(s,e,t){const l=s.slice();return l[35]=e[t].b,l[29]=e[t].isOwn,l}function st(s,e,t){const l=s.slice();return l[40]=e[t],l[42]=t,l}function Ze(s){const e=s.slice(),t=Tt[e[35].building_type];e[38]=t;const l=e[29]?"#1a3a6b":"#6b1a1a";e[30]=l;const n=e[29]?"#58a6ff":"#f85149";e[39]=n;const r=Ot[e[35].building_type];return e[31]=r,e}function nt(s,e,t){const l=s.slice();return l[43]=e[t],l}function it(s,e,t){const l=s.slice();return l[40]=e[t],l[47]=t,l}function rt(s,e,t){const l=s.slice();return l[40]=e[t],l[47]=t,l}function at(s){let e;return{c(){e=$("line"),this.h()},l(t){e=Q(t,"line",{x1:!0,y1:!0,x2:!0,y2:!0,stroke:!0,"stroke-width":!0}),C(e).forEach(c),this.h()},h(){i(e,"x1",s[47]),i(e,"y1",0),i(e,"x2",s[47]),i(e,"y2",me),i(e,"stroke","rgba(255,255,255,0.025)"),i(e,"stroke-width","0.02")},m(t,l){M(t,e,l)},p:ce,d(t){t&&c(e)}}}function ot(s){let e;return{c(){e=$("line"),this.h()},l(t){e=Q(t,"line",{x1:!0,y1:!0,x2:!0,y2:!0,stroke:!0,"stroke-width":!0}),C(e).forEach(c),this.h()},h(){i(e,"x1",0),i(e,"y1",s[47]),i(e,"x2",ve),i(e,"y2",s[47]),i(e,"stroke","rgba(255,255,255,0.025)"),i(e,"stroke-width","0.02")},m(t,l){M(t,e,l)},p:ce,d(t){t&&c(e)}}}function Kt(s){let e,t,l,n,r,a,u;return{c(){e=$("g"),t=$("circle"),n=$("text"),r=X("⛽"),this.h()},l(_){e=Q(_,"g",{transform:!0});var f=C(e);t=Q(f,"circle",{r:!0,fill:!0,opacity:!0}),C(t).forEach(c),n=Q(f,"text",{"text-anchor":!0,"dominant-baseline":!0,"font-size":!0,fill:!0});var v=C(n);r=Y(v,"⛽"),v.forEach(c),f.forEach(c),this.h()},h(){i(t,"r","0.35"),i(t,"fill",l=s[43].has_refinery?"#2e7d52":"#1b5e35"),i(t,"opacity","0.85"),i(n,"text-anchor","middle"),i(n,"dominant-baseline","central"),i(n,"font-size","0.35"),i(n,"fill",a=s[43].has_refinery?"#69f0ae":"#4caf50"),i(e,"transform",u="translate("+(s[43].x+.5)+","+(s[43].y+.5)+")")},m(_,f){M(_,e,f),d(e,t),d(e,n),d(n,r)},p(_,f){f[0]&128&&l!==(l=_[43].has_refinery?"#2e7d52":"#1b5e35")&&i(t,"fill",l),f[0]&128&&a!==(a=_[43].has_refinery?"#69f0ae":"#4caf50")&&i(n,"fill",a),f[0]&128&&u!==(u="translate("+(_[43].x+.5)+","+(_[43].y+.5)+")")&&i(e,"transform",u)},d(_){_&&c(e)}}}function Qt(s){let e,t,l,n;return{c(){e=$("g"),t=$("polygon"),l=$("polygon"),this.h()},l(r){e=Q(r,"g",{transform:!0});var a=C(e);t=Q(a,"polygon",{points:!0,fill:!0,opacity:!0}),C(t).forEach(c),l=Q(a,"polygon",{points:!0,fill:!0,opacity:!0}),C(l).forEach(c),a.forEach(c),this.h()},h(){i(t,"points","0,-0.38 0.33,0.19 -0.33,0.19"),i(t,"fill","#4fc3f7"),i(t,"opacity","0.9"),i(l,"points","0,-0.22 0.19,0.11 -0.19,0.11"),i(l,"fill","#b3e5fc"),i(l,"opacity","0.7"),i(e,"transform",n="translate("+(s[43].x+.5)+","+(s[43].y+.5)+")")},m(r,a){M(r,e,a),d(e,t),d(e,l)},p(r,a){a[0]&128&&n!==(n="translate("+(r[43].x+.5)+","+(r[43].y+.5)+")")&&i(e,"transform",n)},d(r){r&&c(e)}}}function ct(s){let e;function t(r,a){if(r[43].resource_type==="mineral"&&r[43].amount>0)return Qt;if(r[43].resource_type==="geyser")return Kt}let l=t(s),n=l&&l(s);return{c(){n&&n.c(),e=H()},l(r){n&&n.l(r),e=H()},m(r,a){n&&n.m(r,a),M(r,e,a)},p(r,a){l===(l=t(r))&&n?n.p(r,a):(n&&n.d(1),n=l&&l(r),n&&(n.c(),n.m(e.parentNode,e)))},d(r){r&&c(e),n&&n.d(r)}}}function ut(s){let e,t,l,n,r,a,u,_,f,v,y=s[31]+"",b,p,P,w,D,m,N,E,h,k,o,V,Z,x,ee,L=s[35].status==="constructing"&&ft(s),T=s[35].production_queue.length>0&&ht(s);return{c(){e=$("g"),t=$("rect"),L&&L.c(),v=$("text"),b=X(y),m=$("rect"),k=$("rect"),T&&T.c(),this.h()},l(j){e=Q(j,"g",{opacity:!0});var S=C(e);t=Q(S,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,stroke:!0,"stroke-width":!0,rx:!0}),C(t).forEach(c),L&&L.l(S),v=Q(S,"text",{x:!0,y:!0,"text-anchor":!0,"dominant-baseline":!0,"font-size":!0,"font-weight":!0,fill:!0,"font-family":!0});var g=C(v);b=Y(g,y),g.forEach(c),m=Q(S,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,rx:!0}),C(m).forEach(c),k=Q(S,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,rx:!0}),C(k).forEach(c),T&&T.l(S),S.forEach(c),this.h()},h(){var j;i(t,"x",l=s[35].x+.06),i(t,"y",n=s[35].y+.06),i(t,"width",r=s[38].w-.12),i(t,"height",a=s[38].h-.12),i(t,"fill",u=s[30]),i(t,"stroke",_=s[39]),i(t,"stroke-width",f=s[35].id===((j=s[8])==null?void 0:j.id)?.12:.06),i(t,"rx","0.15"),i(v,"x",p=s[35].x+s[38].w/2),i(v,"y",P=s[35].y+s[38].h/2),i(v,"text-anchor","middle"),i(v,"dominant-baseline","central"),i(v,"font-size",w=s[38].w>=4?.5:.42),i(v,"font-weight","700"),i(v,"fill",D=s[39]),i(v,"font-family","monospace"),i(m,"x",N=s[35].x+.1),i(m,"y",E=s[35].y+s[38].h-.22),i(m,"width",h=s[38].w-.2),i(m,"height","0.12"),i(m,"fill","rgba(0,0,0,0.5)"),i(m,"rx","0.06"),i(k,"x",o=s[35].x+.1),i(k,"y",V=s[35].y+s[38].h-.22),i(k,"width",Z=(s[38].w-.2)*(s[35].hp/s[35].max_hp)),i(k,"height","0.12"),i(k,"fill",x=ze(s[35].hp,s[35].max_hp)),i(k,"rx","0.06"),i(e,"opacity",ee=yt(s[35].status))},m(j,S){M(j,e,S),d(e,t),L&&L.m(e,null),d(e,v),d(v,b),d(e,m),d(e,k),T&&T.m(e,null)},p(j,S){var g;S[0]&64&&l!==(l=j[35].x+.06)&&i(t,"x",l),S[0]&64&&n!==(n=j[35].y+.06)&&i(t,"y",n),S[0]&64&&r!==(r=j[38].w-.12)&&i(t,"width",r),S[0]&64&&a!==(a=j[38].h-.12)&&i(t,"height",a),S[0]&64&&u!==(u=j[30])&&i(t,"fill",u),S[0]&64&&_!==(_=j[39])&&i(t,"stroke",_),S[0]&320&&f!==(f=j[35].id===((g=j[8])==null?void 0:g.id)?.12:.06)&&i(t,"stroke-width",f),j[35].status==="constructing"?L?L.p(j,S):(L=ft(j),L.c(),L.m(e,v)):L&&(L.d(1),L=null),S[0]&64&&y!==(y=j[31]+"")&&K(b,y),S[0]&64&&p!==(p=j[35].x+j[38].w/2)&&i(v,"x",p),S[0]&64&&P!==(P=j[35].y+j[38].h/2)&&i(v,"y",P),S[0]&64&&w!==(w=j[38].w>=4?.5:.42)&&i(v,"font-size",w),S[0]&64&&D!==(D=j[39])&&i(v,"fill",D),S[0]&64&&N!==(N=j[35].x+.1)&&i(m,"x",N),S[0]&64&&E!==(E=j[35].y+j[38].h-.22)&&i(m,"y",E),S[0]&64&&h!==(h=j[38].w-.2)&&i(m,"width",h),S[0]&64&&o!==(o=j[35].x+.1)&&i(k,"x",o),S[0]&64&&V!==(V=j[35].y+j[38].h-.22)&&i(k,"y",V),S[0]&64&&Z!==(Z=(j[38].w-.2)*(j[35].hp/j[35].max_hp))&&i(k,"width",Z),S[0]&64&&x!==(x=ze(j[35].hp,j[35].max_hp))&&i(k,"fill",x),j[35].production_queue.length>0?T?T.p(j,S):(T=ht(j),T.c(),T.m(e,null)):T&&(T.d(1),T=null),S[0]&64&&ee!==(ee=yt(j[35].status))&&i(e,"opacity",ee)},d(j){j&&c(e),L&&L.d(),T&&T.d()}}}function ft(s){let e,t,l,n,r,a;return{c(){e=$("rect"),this.h()},l(u){e=Q(u,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,stroke:!0,"stroke-width":!0,"stroke-dasharray":!0,rx:!0,opacity:!0}),C(e).forEach(c),this.h()},h(){i(e,"x",t=s[35].x+.06),i(e,"y",l=s[35].y+.06),i(e,"width",n=s[38].w-.12),i(e,"height",r=s[38].h-.12),i(e,"fill","none"),i(e,"stroke",a=s[39]),i(e,"stroke-width","0.07"),i(e,"stroke-dasharray","0.25 0.15"),i(e,"rx","0.15"),i(e,"opacity","0.6")},m(u,_){M(u,e,_)},p(u,_){_[0]&64&&t!==(t=u[35].x+.06)&&i(e,"x",t),_[0]&64&&l!==(l=u[35].y+.06)&&i(e,"y",l),_[0]&64&&n!==(n=u[38].w-.12)&&i(e,"width",n),_[0]&64&&r!==(r=u[38].h-.12)&&i(e,"height",r),_[0]&64&&a!==(a=u[39])&&i(e,"stroke",a)},d(u){u&&c(e)}}}function ht(s){let e,t=oe({length:Math.min(s[35].production_queue.length,5)}),l=[];for(let n=0;n.6?"#3fb950":t>.3?"#d29922":"#f85149"}function yt(s){return s==="destroyed"?.2:s==="constructing"?.55:1}function xt(s,e,t){let l,n,r,a,u,_,f,v,y;se(s,Qe,g=>t(19,_=g)),se(s,Le,g=>t(20,f=g)),se(s,ke,g=>t(8,v=g)),se(s,we,g=>t(9,y=g));let b=0,p=0,P=ve,w=me,D,m=null;function N(g,R){const z=D.getBoundingClientRect();return[b+(g-z.left)*(P/z.width),p+(R-z.top)*(w/z.height)]}function E(g){if(g.touches.length===1)m={kind:"pan",startX:g.touches[0].clientX,startY:g.touches[0].clientY,lastX:g.touches[0].clientX,lastY:g.touches[0].clientY,moved:!1};else if(g.touches.length===2){const R=g.touches[0].clientX-g.touches[1].clientX,z=g.touches[0].clientY-g.touches[1].clientY;m={kind:"pinch",startDist:Math.hypot(R,z),startW:P,startH:w,cx:(g.touches[0].clientX+g.touches[1].clientX)/2,cy:(g.touches[0].clientY+g.touches[1].clientY)/2}}}function h(g){if(m){if(m.kind==="pan"&&g.touches.length===1){const R=D.getBoundingClientRect(),z=P/R.width,W=w/R.height,q=(g.touches[0].clientX-m.lastX)*z,G=(g.touches[0].clientY-m.lastY)*W,re=Math.hypot(g.touches[0].clientX-m.startX,g.touches[0].clientY-m.startY)>6;t(0,b=de(b-q,0,ve-P)),t(1,p=de(p-G,0,me-w)),m={...m,lastX:g.touches[0].clientX,lastY:g.touches[0].clientY,moved:re}}else if(m.kind==="pinch"&&g.touches.length===2){const R=g.touches[0].clientX-g.touches[1].clientX,z=g.touches[0].clientY-g.touches[1].clientY,W=Math.hypot(R,z),q=m.startDist/W,G=de(m.startW*q,8,ve),re=de(m.startH*q,8,me),[ue,he]=N(m.cx,m.cy);t(2,P=G),t(3,w=re),t(0,b=de(ue-G/2,0,ve-G)),t(1,p=de(he-re/2,0,me-re))}}}function k(g){if((m==null?void 0:m.kind)==="pan"&&!m.moved){const[R,z]=N(m.startX,m.startY);j(R,z)}g.touches.length===0&&(m=null)}let o=!1,V=0,Z=0;function x(g){o=!0,V=g.clientX,Z=g.clientY}function ee(g){if(!o)return;const R=D.getBoundingClientRect(),z=P/R.width,W=w/R.height;t(0,b=de(b-(g.clientX-V)*z,0,ve-P)),t(1,p=de(p-(g.clientY-Z)*W,0,me-w)),V=g.clientX,Z=g.clientY}function L(){o=!1}function T(g){g.preventDefault();const R=g.deltaY>0?1.15:.87,[z,W]=N(g.clientX,g.clientY),q=de(P*R,6,ve),G=de(w*R,6,me);t(2,P=q),t(3,w=G),t(0,b=de(z-q/2,0,ve-q)),t(1,p=de(W-G/2,0,me-G))}function j(g,R){const z=f;if(z){for(const W of Object.values(z.players))for(const q of Object.values(W.units))if(Math.hypot(q.x-g,q.y-R)<.7){we.set(q),ke.set(null);return}for(const W of Object.values(z.players))for(const q of Object.values(W.buildings)){const G=Tt[q.building_type];if(g>=q.x&&g=q.y&&R{D=g,t(4,D)})}return s.$$.update=()=>{s.$$.dirty[0]&1048576&&t(18,l=f),s.$$.dirty[0]&524288&&t(17,n=_),s.$$.dirty[0]&262144&&t(7,r=(l==null?void 0:l.game_map.resources)??[]),s.$$.dirty[0]&393216&&t(6,a=l?Object.values(l.players).flatMap(g=>Object.values(g.buildings).map(R=>({b:R,isOwn:g.player_id===n}))):[]),s.$$.dirty[0]&393216&&t(5,u=l?Object.values(l.players).flatMap(g=>Object.values(g.units).map(R=>({u:R,isOwn:g.player_id===n}))):[])},[b,p,P,w,D,u,a,r,v,y,E,h,k,x,ee,L,T,n,l,_,f,S]}class el extends je{constructor(e){super(),Ae(this,e,xt,$t,Ne,{},null,[-1,-1])}}function tl(s){var ne,O,F,Ie,Me;let e,t,l,n,r="💎",a,u,_=(((ne=s[3])==null?void 0:ne.minerals)??0)+"",f,v,y,b,p="⛽",P,w,D=(((O=s[3])==null?void 0:O.gas)??0)+"",m,N,E,h,k="👥",o,V,Z=(((F=s[3])==null?void 0:F.supply_used)??0)+"",x,ee,L=(((Ie=s[3])==null?void 0:Ie.supply_max)??0)+"",T,j,S,g,R,z,W,q,G,re=(((Me=s[2])==null?void 0:Me.player_name)??"…")+"",ue,he,te;return{c(){e=I("div"),t=I("div"),l=I("span"),n=I("span"),n.textContent=r,a=U(),u=I("span"),f=X(_),v=U(),y=I("span"),b=I("span"),b.textContent=p,P=U(),w=I("span"),m=X(D),N=U(),E=I("span"),h=I("span"),h.textContent=k,o=U(),V=I("span"),x=X(Z),ee=X("/"),T=X(L),j=U(),S=I("div"),g=X(s[1]),R=X(":"),z=X(s[0]),W=U(),q=I("div"),G=I("span"),ue=X(re),he=U(),te=I("span"),this.h()},l(le){e=A(le,"DIV",{class:!0});var J=C(e);t=A(J,"DIV",{class:!0});var _e=C(t);l=A(_e,"SPAN",{class:!0});var pe=C(l);n=A(pe,"SPAN",{class:!0,"data-svelte-h":!0}),ae(n)!=="svelte-s41lk"&&(n.textContent=r),a=B(pe),u=A(pe,"SPAN",{class:!0});var be=C(u);f=Y(be,_),be.forEach(c),pe.forEach(c),v=B(_e),y=A(_e,"SPAN",{class:!0});var ge=C(y);b=A(ge,"SPAN",{class:!0,"data-svelte-h":!0}),ae(b)!=="svelte-13wjbyu"&&(b.textContent=p),P=B(ge),w=A(ge,"SPAN",{class:!0});var De=C(w);m=Y(De,D),De.forEach(c),ge.forEach(c),N=B(_e),E=A(_e,"SPAN",{class:!0});var Se=C(E);h=A(Se,"SPAN",{class:!0,"data-svelte-h":!0}),ae(h)!=="svelte-qf8m2b"&&(h.textContent=k),o=B(Se),V=A(Se,"SPAN",{class:!0});var Ce=C(V);x=Y(Ce,Z),ee=Y(Ce,"/"),T=Y(Ce,L),Ce.forEach(c),Se.forEach(c),_e.forEach(c),j=B(J),S=A(J,"DIV",{class:!0});var qe=C(S);g=Y(qe,s[1]),R=Y(qe,":"),z=Y(qe,s[0]),qe.forEach(c),W=B(J),q=A(J,"DIV",{class:!0});var Re=C(q);G=A(Re,"SPAN",{class:!0});var $e=C(G);ue=Y($e,re),$e.forEach(c),he=B(Re),te=A(Re,"SPAN",{class:!0}),C(te).forEach(c),Re.forEach(c),J.forEach(c),this.h()},h(){var le,J;i(n,"class","res-icon svelte-ra6vcp"),i(u,"class","res-val mono svelte-ra6vcp"),i(l,"class","res mineral svelte-ra6vcp"),i(b,"class","res-icon svelte-ra6vcp"),i(w,"class","res-val mono svelte-ra6vcp"),i(y,"class","res gas svelte-ra6vcp"),i(h,"class","res-icon svelte-ra6vcp"),i(V,"class","res-val mono svelte-ra6vcp"),i(E,"class","res supply svelte-ra6vcp"),fe(E,"supply-critical",(((le=s[3])==null?void 0:le.supply_max)??0)-(((J=s[3])==null?void 0:J.supply_used)??0)<=2),i(t,"class","resources own svelte-ra6vcp"),i(S,"class","timer mono svelte-ra6vcp"),i(G,"class","enemy-name svelte-ra6vcp"),i(te,"class","enemy-dot svelte-ra6vcp"),i(q,"class","enemy-label svelte-ra6vcp"),i(e,"class","resource-bar svelte-ra6vcp")},m(le,J){M(le,e,J),d(e,t),d(t,l),d(l,n),d(l,a),d(l,u),d(u,f),d(t,v),d(t,y),d(y,b),d(y,P),d(y,w),d(w,m),d(t,N),d(t,E),d(E,h),d(E,o),d(E,V),d(V,x),d(V,ee),d(V,T),d(e,j),d(e,S),d(S,g),d(S,R),d(S,z),d(e,W),d(e,q),d(q,G),d(G,ue),d(q,he),d(q,te)},p(le,[J]){var _e,pe,be,ge,De,Se,Ce;J&8&&_!==(_=(((_e=le[3])==null?void 0:_e.minerals)??0)+"")&&K(f,_),J&8&&D!==(D=(((pe=le[3])==null?void 0:pe.gas)??0)+"")&&K(m,D),J&8&&Z!==(Z=(((be=le[3])==null?void 0:be.supply_used)??0)+"")&&K(x,Z),J&8&&L!==(L=(((ge=le[3])==null?void 0:ge.supply_max)??0)+"")&&K(T,L),J&8&&fe(E,"supply-critical",(((De=le[3])==null?void 0:De.supply_max)??0)-(((Se=le[3])==null?void 0:Se.supply_used)??0)<=2),J&2&&K(g,le[1]),J&1&&K(z,le[0]),J&4&&re!==(re=(((Ce=le[2])==null?void 0:Ce.player_name)??"…")+"")&&K(ue,re)},i:ce,o:ce,d(le){le&&c(e)}}}function ll(s,e,t){let l,n,r,a,u,_,f,v,y;return se(s,Le,b=>t(6,f=b)),se(s,Lt,b=>t(7,v=b)),se(s,qt,b=>t(8,y=b)),s.$$.update=()=>{s.$$.dirty&256&&t(3,l=y),s.$$.dirty&128&&t(2,n=v),s.$$.dirty&64&&t(5,r=(f==null?void 0:f.tick)??0),s.$$.dirty&32&&t(4,a=Math.floor(r/4)),s.$$.dirty&16&&t(1,u=Math.floor(a/60).toString().padStart(2,"0")),s.$$.dirty&16&&t(0,_=(a%60).toString().padStart(2,"0"))},[_,u,n,l,a,r,f,v,y]}class sl extends je{constructor(e){super(),Ae(this,e,ll,tl,Ne,{})}}function nl(s){let e,t="🎙️";return{c(){e=I("span"),e.textContent=t,this.h()},l(l){e=A(l,"SPAN",{class:!0,"data-svelte-h":!0}),ae(e)!=="svelte-e6vbro"&&(e.textContent=t),this.h()},h(){i(e,"class","mic-icon svelte-i113ea")},m(l,n){M(l,e,n)},d(l){l&&c(e)}}}function il(s){let e;return{c(){e=I("span"),this.h()},l(t){e=A(t,"SPAN",{class:!0}),C(e).forEach(c),this.h()},h(){i(e,"class","spinner svelte-i113ea")},m(t,l){M(t,e,l)},d(t){t&&c(e)}}}function rl(s){let e,t,l,n,r,a="🔴";return{c(){e=I("span"),t=U(),l=I("span"),n=U(),r=I("span"),r.textContent=a,this.h()},l(u){e=A(u,"SPAN",{class:!0}),C(e).forEach(c),t=B(u),l=A(u,"SPAN",{class:!0}),C(l).forEach(c),n=B(u),r=A(u,"SPAN",{class:!0,"data-svelte-h":!0}),ae(r)!=="svelte-1yq9ug3"&&(r.textContent=a),this.h()},h(){i(e,"class","pulse-ring svelte-i113ea"),i(l,"class","pulse-ring ring2 svelte-i113ea"),i(r,"class","mic-icon svelte-i113ea")},m(u,_){M(u,e,_),M(u,t,_),M(u,l,_),M(u,n,_),M(u,r,_)},d(u){u&&(c(e),c(t),c(l),c(n),c(r))}}}function al(s){let e;return{c(){e=X("Maintenir pour parler")},l(t){e=Y(t,"Maintenir pour parler")},m(t,l){M(t,e,l)},d(t){t&&c(e)}}}function ol(s){let e;return{c(){e=X("Traitement…")},l(t){e=Y(t,"Traitement…")},m(t,l){M(t,e,l)},d(t){t&&c(e)}}}function cl(s){let e;return{c(){e=X("En écoute…")},l(t){e=Y(t,"En écoute…")},m(t,l){M(t,e,l)},d(t){t&&c(e)}}}function ul(s){let e,t,l,n,r,a;function u(p,P){return p[0]==="recording"?rl:p[0]==="processing"?il:nl}let _=u(s),f=_(s);function v(p,P){return p[0]==="recording"?cl:p[0]==="processing"?ol:al}let y=v(s),b=y(s);return{c(){e=I("button"),f.c(),t=U(),l=I("span"),b.c(),this.h()},l(p){e=A(p,"BUTTON",{class:!0,"aria-label":!0});var P=C(e);f.l(P),t=B(P),l=A(P,"SPAN",{class:!0});var w=C(l);b.l(w),w.forEach(c),P.forEach(c),this.h()},h(){i(l,"class","btn-label svelte-i113ea"),i(e,"class","voice-btn svelte-i113ea"),e.disabled=n=s[0]==="processing",i(e,"aria-label","Maintenir pour parler"),fe(e,"recording",s[0]==="recording"),fe(e,"processing",s[0]==="processing")},m(p,P){M(p,e,P),f.m(e,null),d(e,t),d(e,l),b.m(l,null),r||(a=[ie(e,"mousedown",s[1]),ie(e,"mouseup",s[2]),ie(e,"mouseleave",s[2]),ie(e,"touchstart",s[1],{passive:!1}),ie(e,"touchend",s[2],{passive:!1}),ie(e,"touchcancel",s[2])],r=!0)},p(p,[P]){_!==(_=u(p))&&(f.d(1),f=_(p),f&&(f.c(),f.m(e,t))),y!==(y=v(p))&&(b.d(1),b=y(p),b&&(b.c(),b.m(l,null))),P&1&&n!==(n=p[0]==="processing")&&(e.disabled=n),P&1&&fe(e,"recording",p[0]==="recording"),P&1&&fe(e,"processing",p[0]==="processing")},i:ce,o:ce,d(p){p&&c(e),f.d(),b.d(),r=!1,Ke(a)}}}function fl(s,e,t){let l;se(s,Ee,_=>t(0,l=_));const n=Ut();let r=null;async function a(_){if(_.preventDefault(),l==="idle")try{const{stop:f,result:v}=await Ht();r=f,Ee.set("recording"),v.then(async({blob:y,mimeType:b})=>{const p=await Wt(y);n("send",{audio_b64:p,mime_type:b})})}catch{Ee.set("idle"),console.error("Microphone not available")}}function u(_){_.preventDefault(),l==="recording"&&(r==null||r(),r=null,Ee.set("processing"))}return[l,a,u]}class hl extends je{constructor(e){super(),Ae(this,e,fl,ul,Ne,{})}}function kt(s,e,t){const l=s.slice();return l[10]=e[t],l[12]=t,l}function _l(s){const e=s.slice(),t=Ot[e[0].building_type];return e[9]=t,e}function wt(s){let e,t,l,n,r="✕",a,u,_;function f(p,P){if(p[1])return pl;if(p[0])return Et}function v(p,P){return P===Et?_l(p):p}let y=f(s),b=y&&y(v(s,y));return{c(){e=I("div"),t=U(),l=I("div"),n=I("button"),n.textContent=r,a=U(),b&&b.c(),this.h()},l(p){e=A(p,"DIV",{class:!0}),C(e).forEach(c),t=B(p),l=A(p,"DIV",{class:!0});var P=C(l);n=A(P,"BUTTON",{class:!0,"aria-label":!0,"data-svelte-h":!0}),ae(n)!=="svelte-1l1r1nr"&&(n.textContent=r),a=B(P),b&&b.l(P),P.forEach(c),this.h()},h(){i(e,"class","panel-backdrop svelte-15lj9ka"),i(n,"class","close-btn svelte-15lj9ka"),i(n,"aria-label","Fermer"),i(l,"class","panel svelte-15lj9ka"),fe(l,"is-enemy",!s[2])},m(p,P){M(p,e,P),M(p,t,P),M(p,l,P),d(l,n),d(l,a),b&&b.m(l,null),u||(_=[ie(e,"click",s[4]),ie(n,"click",s[4])],u=!0)},p(p,P){y===(y=f(p))&&b?b.p(v(p,y),P):(b&&b.d(1),b=y&&y(v(p,y)),b&&(b.c(),b.m(l,null))),P&4&&fe(l,"is-enemy",!p[2])},d(p){p&&(c(e),c(t),c(l)),b&&b.d(),u=!1,Ke(_)}}}function Et(s){let e,t,l,n,r=s[0].building_type.replace(/_/g," ").toUpperCase()+"",a,u,_,f=s[2]?"Allié":"Ennemi",v,y,b,p,P="Vie",w,D,m,N,E,h=Math.ceil(s[0].hp)+"",k,o,V=s[0].max_hp+"",Z,x,ee,L,T=s[0].status==="constructing"&&St(s),j=s[0].production_queue.length>0&&Ct(s);return{c(){e=I("div"),t=I("span"),l=U(),n=I("span"),a=X(r),u=U(),_=I("span"),v=X(f),y=U(),b=I("div"),p=I("span"),p.textContent=P,w=U(),D=I("div"),m=I("div"),N=U(),E=I("span"),k=X(h),o=X(" / "),Z=X(V),x=U(),T&&T.c(),ee=U(),j&&j.c(),L=H(),this.h()},l(S){e=A(S,"DIV",{class:!0});var g=C(e);t=A(g,"SPAN",{class:!0}),C(t).forEach(c),l=B(g),n=A(g,"SPAN",{class:!0});var R=C(n);a=Y(R,r),R.forEach(c),u=B(g),_=A(g,"SPAN",{class:!0});var z=C(_);v=Y(z,f),z.forEach(c),g.forEach(c),y=B(S),b=A(S,"DIV",{class:!0});var W=C(b);p=A(W,"SPAN",{class:!0,"data-svelte-h":!0}),ae(p)!=="svelte-k7l202"&&(p.textContent=P),w=B(W),D=A(W,"DIV",{class:!0});var q=C(D);m=A(q,"DIV",{class:!0,style:!0}),C(m).forEach(c),q.forEach(c),N=B(W),E=A(W,"SPAN",{class:!0});var G=C(E);k=Y(G,h),o=Y(G," / "),Z=Y(G,V),G.forEach(c),W.forEach(c),x=B(S),T&&T.l(S),ee=B(S),j&&j.l(S),L=H(),this.h()},h(){i(t,"class","owner-dot svelte-15lj9ka"),fe(t,"own",s[2]),i(n,"class","unit-name svelte-15lj9ka"),i(_,"class","owner-label svelte-15lj9ka"),i(e,"class","panel-header svelte-15lj9ka"),i(p,"class","stat-label svelte-15lj9ka"),i(m,"class","hp-bar svelte-15lj9ka"),ye(m,"width",Fe(s[0].hp,s[0].max_hp)+"%"),ye(m,"background",He(s[0].hp,s[0].max_hp)),i(D,"class","hp-bar-wrap svelte-15lj9ka"),i(E,"class","stat-val svelte-15lj9ka"),i(b,"class","stat-row svelte-15lj9ka")},m(S,g){M(S,e,g),d(e,t),d(e,l),d(e,n),d(n,a),d(e,u),d(e,_),d(_,v),M(S,y,g),M(S,b,g),d(b,p),d(b,w),d(b,D),d(D,m),d(b,N),d(b,E),d(E,k),d(E,o),d(E,Z),M(S,x,g),T&&T.m(S,g),M(S,ee,g),j&&j.m(S,g),M(S,L,g)},p(S,g){g&4&&fe(t,"own",S[2]),g&1&&r!==(r=S[0].building_type.replace(/_/g," ").toUpperCase()+"")&&K(a,r),g&4&&f!==(f=S[2]?"Allié":"Ennemi")&&K(v,f),g&1&&ye(m,"width",Fe(S[0].hp,S[0].max_hp)+"%"),g&1&&ye(m,"background",He(S[0].hp,S[0].max_hp)),g&1&&h!==(h=Math.ceil(S[0].hp)+"")&&K(k,h),g&1&&V!==(V=S[0].max_hp+"")&&K(Z,V),S[0].status==="constructing"?T?T.p(S,g):(T=St(S),T.c(),T.m(ee.parentNode,ee)):T&&(T.d(1),T=null),S[0].production_queue.length>0?j?j.p(S,g):(j=Ct(S),j.c(),j.m(L.parentNode,L)):j&&(j.d(1),j=null)},d(S){S&&(c(e),c(y),c(b),c(x),c(ee),c(L)),T&&T.d(S),j&&j.d(S)}}}function pl(s){let e,t,l,n,r=s[1].unit_type.toUpperCase()+"",a,u,_,f=s[2]?"Allié":"Ennemi",v,y,b,p=et[s[1].unit_type]+"",P,w,D,m,N="Vie",E,h,k,o,V,Z=Math.ceil(s[1].hp)+"",x,ee,L=s[1].max_hp+"",T,j,S,g,R="État",z,W,q=At(s[1].status)+"",G,re,ue,he,te=s[1].is_sieged&&Nt(),ne=s[1].is_cloaked&&jt();return{c(){e=I("div"),t=I("span"),l=U(),n=I("span"),a=X(r),u=U(),_=I("span"),v=X(f),y=U(),b=I("p"),P=X(p),w=U(),D=I("div"),m=I("span"),m.textContent=N,E=U(),h=I("div"),k=I("div"),o=U(),V=I("span"),x=X(Z),ee=X(" / "),T=X(L),j=U(),S=I("div"),g=I("span"),g.textContent=R,z=U(),W=I("span"),G=X(q),re=U(),te&&te.c(),ue=U(),ne&&ne.c(),he=H(),this.h()},l(O){e=A(O,"DIV",{class:!0});var F=C(e);t=A(F,"SPAN",{class:!0}),C(t).forEach(c),l=B(F),n=A(F,"SPAN",{class:!0});var Ie=C(n);a=Y(Ie,r),Ie.forEach(c),u=B(F),_=A(F,"SPAN",{class:!0});var Me=C(_);v=Y(Me,f),Me.forEach(c),F.forEach(c),y=B(O),b=A(O,"P",{class:!0});var le=C(b);P=Y(le,p),le.forEach(c),w=B(O),D=A(O,"DIV",{class:!0});var J=C(D);m=A(J,"SPAN",{class:!0,"data-svelte-h":!0}),ae(m)!=="svelte-k7l202"&&(m.textContent=N),E=B(J),h=A(J,"DIV",{class:!0});var _e=C(h);k=A(_e,"DIV",{class:!0,style:!0}),C(k).forEach(c),_e.forEach(c),o=B(J),V=A(J,"SPAN",{class:!0});var pe=C(V);x=Y(pe,Z),ee=Y(pe," / "),T=Y(pe,L),pe.forEach(c),J.forEach(c),j=B(O),S=A(O,"DIV",{class:!0});var be=C(S);g=A(be,"SPAN",{class:!0,"data-svelte-h":!0}),ae(g)!=="svelte-rktcsi"&&(g.textContent=R),z=B(be),W=A(be,"SPAN",{class:!0});var ge=C(W);G=Y(ge,q),ge.forEach(c),be.forEach(c),re=B(O),te&&te.l(O),ue=B(O),ne&&ne.l(O),he=H(),this.h()},h(){i(t,"class","owner-dot svelte-15lj9ka"),fe(t,"own",s[2]),i(n,"class","unit-name svelte-15lj9ka"),i(_,"class","owner-label svelte-15lj9ka"),i(e,"class","panel-header svelte-15lj9ka"),i(b,"class","description svelte-15lj9ka"),i(m,"class","stat-label svelte-15lj9ka"),i(k,"class","hp-bar svelte-15lj9ka"),ye(k,"width",Fe(s[1].hp,s[1].max_hp)+"%"),ye(k,"background",He(s[1].hp,s[1].max_hp)),i(h,"class","hp-bar-wrap svelte-15lj9ka"),i(V,"class","stat-val svelte-15lj9ka"),i(D,"class","stat-row svelte-15lj9ka"),i(g,"class","stat-label svelte-15lj9ka"),i(W,"class","stat-val status svelte-15lj9ka"),i(S,"class","stat-row svelte-15lj9ka")},m(O,F){M(O,e,F),d(e,t),d(e,l),d(e,n),d(n,a),d(e,u),d(e,_),d(_,v),M(O,y,F),M(O,b,F),d(b,P),M(O,w,F),M(O,D,F),d(D,m),d(D,E),d(D,h),d(h,k),d(D,o),d(D,V),d(V,x),d(V,ee),d(V,T),M(O,j,F),M(O,S,F),d(S,g),d(S,z),d(S,W),d(W,G),M(O,re,F),te&&te.m(O,F),M(O,ue,F),ne&&ne.m(O,F),M(O,he,F)},p(O,F){F&4&&fe(t,"own",O[2]),F&2&&r!==(r=O[1].unit_type.toUpperCase()+"")&&K(a,r),F&4&&f!==(f=O[2]?"Allié":"Ennemi")&&K(v,f),F&2&&p!==(p=et[O[1].unit_type]+"")&&K(P,p),F&2&&ye(k,"width",Fe(O[1].hp,O[1].max_hp)+"%"),F&2&&ye(k,"background",He(O[1].hp,O[1].max_hp)),F&2&&Z!==(Z=Math.ceil(O[1].hp)+"")&&K(x,Z),F&2&&L!==(L=O[1].max_hp+"")&&K(T,L),F&2&&q!==(q=At(O[1].status)+"")&&K(G,q),O[1].is_sieged?te||(te=Nt(),te.c(),te.m(ue.parentNode,ue)):te&&(te.d(1),te=null),O[1].is_cloaked?ne||(ne=jt(),ne.c(),ne.m(he.parentNode,he)):ne&&(ne.d(1),ne=null)},d(O){O&&(c(e),c(y),c(b),c(w),c(D),c(j),c(S),c(re),c(ue),c(he)),te&&te.d(O),ne&&ne.d(O)}}}function St(s){let e,t,l="Construction",n,r,a=We(s[0].construction_ticks_remaining)+"",u,_;return{c(){e=I("div"),t=I("span"),t.textContent=l,n=U(),r=I("span"),u=X(a),_=X(" restants"),this.h()},l(f){e=A(f,"DIV",{class:!0});var v=C(e);t=A(v,"SPAN",{class:!0,"data-svelte-h":!0}),ae(t)!=="svelte-12zyrz9"&&(t.textContent=l),n=B(v),r=A(v,"SPAN",{class:!0});var y=C(r);u=Y(y,a),_=Y(y," restants"),y.forEach(c),v.forEach(c),this.h()},h(){i(t,"class","stat-label svelte-15lj9ka"),i(r,"class","stat-val svelte-15lj9ka"),i(e,"class","stat-row svelte-15lj9ka")},m(f,v){M(f,e,v),d(e,t),d(e,n),d(e,r),d(r,u),d(r,_)},p(f,v){v&1&&a!==(a=We(f[0].construction_ticks_remaining)+"")&&K(u,a)},d(f){f&&c(e)}}}function Ct(s){let e,t="File de production",l,n,r=oe(s[0].production_queue),a=[];for(let u=0;u.6?"var(--success)":t>.3?"var(--warning)":"var(--danger)"}function At(s){return{idle:"Inactif",moving:"En déplacement",attacking:"En combat",mining_minerals:"Collecte minéraux",mining_gas:"Collecte gaz",building:"Construction",healing:"Soin",sieged:"Mode siège",patrolling:"Patrouille"}[s]??s}function We(s){return(s/4).toFixed(0)+"s"}function ml(s,e,t){let l,n,r,a,u,_,f,v;se(s,Qe,b=>t(6,_=b)),se(s,ke,b=>t(7,f=b)),se(s,we,b=>t(8,v=b));function y(){we.set(null),ke.set(null)}return s.$$.update=()=>{s.$$.dirty&256&&t(1,l=v),s.$$.dirty&128&&t(0,n=f),s.$$.dirty&64&&t(5,r=_),s.$$.dirty&3&&t(3,a=!!l||!!n),s.$$.dirty&35&&t(2,u=l?l.owner===r:n?n.owner===r:!1)},[n,l,u,a,y,r,_,f,v]}class bl extends je{constructor(e){super(),Ae(this,e,ml,vl,Ne,{})}}function It(s){let e;function t(r,a){return r[0]&&!r[2]?yl:gl}let l=t(s),n=l(s);return{c(){e=I("div"),n.c(),this.h()},l(r){e=A(r,"DIV",{class:!0});var a=C(e);n.l(a),a.forEach(c),this.h()},h(){i(e,"class","overlay svelte-1rkl0l5")},m(r,a){M(r,e,a),n.m(e,null)},p(r,a){l===(l=t(r))&&n?n.p(r,a):(n.d(1),n=l(r),n&&(n.c(),n.m(e,null)))},d(r){r&&c(e),n.d()}}}function gl(s){let e,t,l=s[2]&&Mt(s),n=s[1]&&Dt(s);return{c(){l&&l.c(),e=U(),n&&n.c(),t=H()},l(r){l&&l.l(r),e=B(r),n&&n.l(r),t=H()},m(r,a){l&&l.m(r,a),M(r,e,a),n&&n.m(r,a),M(r,t,a)},p(r,a){r[2]?l?l.p(r,a):(l=Mt(r),l.c(),l.m(e.parentNode,e)):l&&(l.d(1),l=null),r[1]?n?n.p(r,a):(n=Dt(r),n.c(),n.m(t.parentNode,t)):n&&(n.d(1),n=null)},d(r){r&&(c(e),c(t)),l&&l.d(r),n&&n.d(r)}}}function yl(s){let e,t='';return{c(){e=I("div"),e.innerHTML=t,this.h()},l(l){e=A(l,"DIV",{class:!0,"data-svelte-h":!0}),ae(e)!=="svelte-1fwsd0q"&&(e.innerHTML=t),this.h()},h(){i(e,"class","processing-dots svelte-1rkl0l5")},m(l,n){M(l,e,n)},p:ce,d(l){l&&c(e)}}}function Mt(s){let e,t,l,n;return{c(){e=I("p"),t=X('"'),l=X(s[2]),n=X('"'),this.h()},l(r){e=A(r,"P",{class:!0});var a=C(e);t=Y(a,'"'),l=Y(a,s[2]),n=Y(a,'"'),a.forEach(c),this.h()},h(){i(e,"class","transcription svelte-1rkl0l5")},m(r,a){M(r,e,a),d(e,t),d(e,l),d(e,n)},p(r,a){a&4&&K(l,r[2])},d(r){r&&c(e)}}}function Dt(s){let e,t;return{c(){e=I("p"),t=X(s[1]),this.h()},l(l){e=A(l,"P",{class:!0});var n=C(e);t=Y(n,s[1]),n.forEach(c),this.h()},h(){i(e,"class","feedback svelte-1rkl0l5")},m(l,n){M(l,e,n),d(e,t)},p(l,n){n&2&&K(t,l[1])},d(l){l&&c(e)}}}function kl(s){let e,t=(s[2]||s[1]||s[0])&&It(s);return{c(){t&&t.c(),e=H()},l(l){t&&t.l(l),e=H()},m(l,n){t&&t.m(l,n),M(l,e,n)},p(l,[n]){l[2]||l[1]||l[0]?t?t.p(l,n):(t=It(l),t.c(),t.m(e.parentNode,e)):t&&(t.d(1),t=null)},i:ce,o:ce,d(l){l&&c(e),t&&t.d(l)}}}function wl(s,e,t){let l,n,r,a,u,_;return se(s,Ee,f=>t(3,a=f)),se(s,Xe,f=>t(4,u=f)),se(s,Je,f=>t(5,_=f)),s.$$.update=()=>{s.$$.dirty&32&&t(2,l=_),s.$$.dirty&16&&t(1,n=u),s.$$.dirty&8&&t(0,r=a==="processing")},[r,n,l,a,u,_]}class El extends je{constructor(e){super(),Ae(this,e,wl,kl,Ne,{})}}function Vt(s){let e,t,l,n=s[2]?"🏆":"💀",r,a,u,_=s[2]?"Victoire !":"Défaite",f,v,y,b,p,P="Retour au lobby",w,D;function m(h,k){return h[2]?Cl:Sl}let N=m(s),E=N(s);return{c(){e=I("div"),t=I("div"),l=I("div"),r=X(n),a=U(),u=I("h2"),f=X(_),v=U(),y=I("p"),E.c(),b=U(),p=I("button"),p.textContent=P,this.h()},l(h){e=A(h,"DIV",{class:!0});var k=C(e);t=A(k,"DIV",{class:!0});var o=C(t);l=A(o,"DIV",{class:!0});var V=C(l);r=Y(V,n),V.forEach(c),a=B(o),u=A(o,"H2",{class:!0});var Z=C(u);f=Y(Z,_),Z.forEach(c),v=B(o),y=A(o,"P",{class:!0});var x=C(y);E.l(x),x.forEach(c),b=B(o),p=A(o,"BUTTON",{class:!0,"data-svelte-h":!0}),ae(p)!=="svelte-1a6y9oo"&&(p.textContent=P),o.forEach(c),k.forEach(c),this.h()},h(){i(l,"class","modal-icon svelte-3ubry2"),i(u,"class","modal-title svelte-3ubry2"),i(y,"class","modal-body svelte-3ubry2"),i(p,"class","btn-back svelte-3ubry2"),i(t,"class","modal svelte-3ubry2"),i(e,"class","modal-backdrop svelte-3ubry2")},m(h,k){M(h,e,k),d(e,t),d(t,l),d(l,r),d(t,a),d(t,u),d(u,f),d(t,v),d(t,y),E.m(y,null),d(t,b),d(t,p),w||(D=ie(p,"click",s[4]),w=!0)},p(h,k){k&4&&n!==(n=h[2]?"🏆":"💀")&&K(r,n),k&4&&_!==(_=h[2]?"Victoire !":"Défaite")&&K(f,_),N===(N=m(h))&&E?E.p(h,k):(E.d(1),E=N(h),E&&(E.c(),E.m(y,null)))},d(h){h&&c(e),E.d(),w=!1,D()}}}function Sl(s){let e,t;return{c(){e=X(s[1]),t=X(" a remporté la bataille.")},l(l){e=Y(l,s[1]),t=Y(l," a remporté la bataille.")},m(l,n){M(l,e,n),M(l,t,n)},p(l,n){n&2&&K(e,l[1])},d(l){l&&(c(e),c(t))}}}function Cl(s){let e;return{c(){e=X("Félicitations, commandant. La base ennemie est détruite.")},l(t){e=Y(t,"Félicitations, commandant. La base ennemie est détruite.")},m(t,l){M(t,e,l)},p:ce,d(t){t&&c(e)}}}function Pl(s){let e,t,l,n,r,a,u,_,f,v,y,b,p,P,w,D;t=new sl({}),r=new el({}),u=new bl({}),v=new El({}),p=new hl({}),p.$on("send",s[3]);let m=s[0]&&Vt(s);return{c(){e=I("div"),Ye(t.$$.fragment),l=U(),n=I("div"),Ye(r.$$.fragment),a=U(),Ye(u.$$.fragment),_=U(),f=I("div"),Ye(v.$$.fragment),y=U(),b=I("div"),Ye(p.$$.fragment),P=U(),m&&m.c(),w=H(),this.h()},l(N){e=A(N,"DIV",{class:!0});var E=C(e);Ue(t.$$.fragment,E),l=B(E),n=A(E,"DIV",{class:!0});var h=C(n);Ue(r.$$.fragment,h),a=B(h),Ue(u.$$.fragment,h),h.forEach(c),_=B(E),f=A(E,"DIV",{class:!0});var k=C(f);Ue(v.$$.fragment,k),y=B(k),b=A(k,"DIV",{class:!0});var o=C(b);Ue(p.$$.fragment,o),o.forEach(c),k.forEach(c),E.forEach(c),P=B(N),m&&m.l(N),w=H(),this.h()},h(){i(n,"class","map-wrap svelte-3ubry2"),i(b,"class","voice-row svelte-3ubry2"),i(f,"class","bottom-panel svelte-3ubry2"),i(e,"class","game-layout svelte-3ubry2")},m(N,E){M(N,e,E),Be(t,e,null),d(e,l),d(e,n),Be(r,n,null),d(n,a),Be(u,n,null),d(e,_),d(e,f),Be(v,f,null),d(f,y),d(f,b),Be(p,b,null),M(N,P,E),m&&m.m(N,E),M(N,w,E),D=!0},p(N,[E]){N[0]?m?m.p(N,E):(m=Vt(N),m.c(),m.m(w.parentNode,w)):m&&(m.d(1),m=null)},i(N){D||(Oe(t.$$.fragment,N),Oe(r.$$.fragment,N),Oe(u.$$.fragment,N),Oe(v.$$.fragment,N),Oe(p.$$.fragment,N),D=!0)},o(N){Te(t.$$.fragment,N),Te(r.$$.fragment,N),Te(u.$$.fragment,N),Te(v.$$.fragment,N),Te(p.$$.fragment,N),D=!1},d(N){N&&(c(e),c(P),c(w)),Ve(t),Ve(r),Ve(u),Ve(v),Ve(p),m&&m.d(N)}}}function Nl(s,e,t){let l,n,r,a,u,_,f;se(s,Ge,w=>t(6,r=w)),se(s,Qe,w=>t(7,a=w)),se(s,ke,w=>t(8,u=w)),se(s,we,w=>t(9,_=w)),se(s,Le,w=>t(10,f=w));const v=Rt();let y=!1,b="";f||xe("/"),Yt(()=>{v.on("game_update",w=>{if(Le.set(w),_){const D=Object.values(w.players).find(m=>m.player_id===(_==null?void 0:_.owner));we.set((D==null?void 0:D.units[_.id])??null)}if(u){const D=Object.values(w.players).find(m=>m.player_id===(u==null?void 0:u.owner));ke.set((D==null?void 0:D.buildings[u.id])??null)}}),v.on("voice_result",w=>{Je.set(w.transcription),Xe.set(w.feedback_text),Ee.set("idle"),w.feedback_text&&Gt(w.feedback_text),setTimeout(()=>{Je.set(""),Xe.set("")},6e3)}),v.on("game_over",({winner_id:w,winner_name:D})=>{Ge.set(w),t(1,b=D),t(0,y=!0),Ee.set("idle")}),v.on("error",({message:w})=>{Xe.set(`⚠️ ${w}`),Ee.set("idle"),setTimeout(()=>Xe.set(""),3e3)})}),Xt(()=>{v.off("game_update"),v.off("voice_result"),v.off("game_over"),v.off("error")});function p(w){v.emit("voice_input",{audio_b64:w.detail.audio_b64,mime_type:w.detail.mime_type})}function P(){Le.set(null),Ge.set(null),t(0,y=!1),xe("/")}return s.$$.update=()=>{s.$$.dirty&128&&t(5,l=a),s.$$.dirty&96&&t(2,n=r===l)},[y,b,n,p,P,l,r,a]}class Vl extends je{constructor(e){super(),Ae(this,e,Nl,Pl,Ne,{})}}export{Vl as component,Dl as universal}; diff --git a/frontend/build/_app/immutable/nodes/4.Jx4UbTnq.js b/frontend/build/_app/immutable/nodes/4.Jx4UbTnq.js new file mode 100644 index 0000000000000000000000000000000000000000..c019118b94f372468f0d07a7f8b3a9019e2888f2 --- /dev/null +++ b/frontend/build/_app/immutable/nodes/4.Jx4UbTnq.js @@ -0,0 +1,2 @@ +import{s as Q,n as q,d as v,i as U,e as _,p as E,q as R,k as S,f as j,h as P,r as A,m as T,l as w,v as W,w as X,b as D,j as M,t as N,x as Y,y as x}from"../chunks/BWggeOnc.js";import{e as G,u as Z,b as $,d as ee}from"../chunks/B2mXDaCf.js";import{S as te,i as ne}from"../chunks/CbPkTnjI.js";function V(h,t,e){const n=h.slice();return n[14]=t[e],n}function z(h){let t,e;return{c(){t=w("p"),e=N(h[2]),this.h()},l(n){t=j(n,"P",{class:!0});var a=P(t);e=M(a,h[2]),a.forEach(v),this.h()},h(){E(t,"class","error svelte-rj6key")},m(n,a){U(n,t,a),_(t,e)},p(n,a){a&4&&D(e,n[2])},d(n){n&&v(t)}}}function le(h){let t,e=[],n=new Map,a=G(h[0]);const k=l=>l[14].unit+"/"+l[14].kind;for(let l=0;lcd backend && python -m scripts.generate_unit_sounds. + Les fichiers déjà présents sont conservés ; supprimer ici puis relancer le script pour régénérer.`,r,p,o=h[2]&&z(h);function m(i,g){return i[1]?re:i[0].length===0?se:le}let b=m(h),d=b(h);return{c(){t=T(),e=w("div"),n=w("h1"),n.textContent=a,k=T(),l=w("p"),l.innerHTML=s,r=T(),o&&o.c(),p=T(),d.c(),this.h()},l(i){R("svelte-1v4mu4b",document.head).forEach(v),t=S(i),e=j(i,"DIV",{class:!0});var c=P(e);n=j(c,"H1",{"data-svelte-h":!0}),A(n)!=="svelte-1xc1cix"&&(n.textContent=a),k=S(c),l=j(c,"P",{class:!0,"data-svelte-h":!0}),A(l)!=="svelte-1luqh0l"&&(l.innerHTML=s),r=S(c),o&&o.l(c),p=S(c),d.l(c),c.forEach(v),this.h()},h(){document.title="Admin — Sons unités",E(l,"class","hint svelte-rj6key"),E(e,"class","admin svelte-rj6key")},m(i,g){U(i,t,g),U(i,e,g),_(e,n),_(e,k),_(e,l),_(e,r),o&&o.m(e,null),_(e,p),d.m(e,null)},p(i,[g]){i[2]?o?o.p(i,g):(o=z(i),o.c(),o.m(e,p)):o&&(o.d(1),o=null),b===(b=m(i))&&d?d.p(i,g):(d.d(1),d=b(i),d&&(d.c(),d.m(e,null)))},i:q,o:q,d(i){i&&(v(t),v(e)),o&&o.d(),d.d()}}}function ie(h,t,e){let n=[],a=!0,k="",l=null,s=!1,r=null,p=null;const o=()=>typeof window<"u"?$():"";async function m(){p==null||p.abort(),p=new AbortController;const f=p.signal;e(1,a=!0),e(2,k="");try{const u=await fetch(`${o()}/api/sounds/units`,{signal:f});if(!u.ok)throw new Error(u.statusText);const C=await u.json();if(s||f.aborted)return;e(0,n=C.sounds??[])}catch(u){if(s||f.aborted)return;e(2,k=u instanceof Error?u.message:String(u)),e(0,n=[])}finally{s||e(1,a=!1)}}function b(f){return`${o()}/sounds/units/${f.unit}/${f.kind}.mp3`}function d(f){const u=`${f.unit}/${f.kind}`;if(l)return;r&&(r.pause(),r.src="",r=null),e(3,l=u);const C=new Audio(b(f));r=C,C.onended=()=>{s||e(3,l=null),r=null},C.onerror=()=>{s||e(3,l=null),r=null},C.play()}async function i(f){try{const u=await fetch(`${o()}/api/sounds/units/${f.unit}/${f.kind}`,{method:"DELETE"});if(!u.ok)throw new Error(u.status===404?"Déjà supprimé":u.statusText);if(s)return;await m()}catch(u){s||e(2,k=u instanceof Error?u.message:String(u))}}return W(m),X(()=>{s=!0,p==null||p.abort(),r&&(r.pause(),r.src="",r=null)}),[n,a,k,l,d,i,f=>d(f),f=>i(f)]}class fe extends te{constructor(t){super(),ne(this,t,ie,ae,Q,{})}}export{fe as component}; diff --git a/frontend/build/_app/immutable/nodes/5.CRReP7Rn.js b/frontend/build/_app/immutable/nodes/5.CRReP7Rn.js new file mode 100644 index 0000000000000000000000000000000000000000..23dbcd334c0606385102e6a8ec8451c8b7f7a501 --- /dev/null +++ b/frontend/build/_app/immutable/nodes/5.CRReP7Rn.js @@ -0,0 +1,2 @@ +import{s as le,n as V,d as m,b as q,i as L,e as _,y as F,p as c,q as se,k as U,f as S,h as j,r as H,j as B,m as R,l as P,t as D,v as ne,w as ie,z}from"../chunks/BWggeOnc.js";import{b as re,e as A,u as ee,d as te}from"../chunks/B2mXDaCf.js";import{S as ae,i as oe}from"../chunks/CbPkTnjI.js";function K(u,e,t){const l=u.slice();return l[19]=e[t],l}function Q(u,e,t){const l=u.slice();return l[19]=e[t],l}function X(u){let e,t;return{c(){e=P("p"),t=D(u[5]),this.h()},l(l){e=S(l,"P",{class:!0});var n=j(e);t=B(n,u[5]),n.forEach(m),this.h()},h(){c(e,"class","error svelte-1xp57ho")},m(l,n){L(l,e,n),_(e,t)},p(l,n){n&32&&q(t,l[5])},d(l){l&&m(e)}}}function Y(u){let e,t;return{c(){e=P("p"),t=D(u[7]),this.h()},l(l){e=S(l,"P",{class:!0});var n=j(e);t=B(n,u[7]),n.forEach(m),this.h()},h(){c(e,"class","error svelte-1xp57ho")},m(l,n){L(l,e,n),_(e,t)},p(l,n){n&128&&q(t,l[7])},d(l){l&&m(e)}}}function Z(u){let e,t;return{c(){e=P("p"),t=D(u[3]),this.h()},l(l){e=S(l,"P",{class:!0});var n=j(e);t=B(n,u[3]),n.forEach(m),this.h()},h(){c(e,"class","error svelte-1xp57ho")},m(l,n){L(l,e,n),_(e,t)},p(l,n){n&8&&q(t,l[3])},d(l){l&&m(e)}}}function ue(u){let e,t,l="Unités",n,b,s,i,o="Bâtiments",C;function k(h,E){return h[0].length===0?he:fe}let I=k(u),a=I(u);function y(h,E){return h[1].length===0?pe:_e}let v=y(u),d=v(u);return{c(){e=P("section"),t=P("h2"),t.textContent=l,n=R(),a.c(),b=R(),s=P("section"),i=P("h2"),i.textContent=o,C=R(),d.c(),this.h()},l(h){e=S(h,"SECTION",{class:!0});var E=j(e);t=S(E,"H2",{class:!0,"data-svelte-h":!0}),H(t)!=="svelte-d6deas"&&(t.textContent=l),n=U(E),a.l(E),E.forEach(m),b=U(h),s=S(h,"SECTION",{class:!0});var M=j(s);i=S(M,"H2",{class:!0,"data-svelte-h":!0}),H(i)!=="svelte-125b5ze"&&(i.textContent=o),C=U(M),d.l(M),M.forEach(m),this.h()},h(){c(t,"class","svelte-1xp57ho"),c(e,"class","svelte-1xp57ho"),c(i,"class","svelte-1xp57ho"),c(s,"class","svelte-1xp57ho")},m(h,E){L(h,e,E),_(e,t),_(e,n),a.m(e,null),L(h,b,E),L(h,s,E),_(s,i),_(s,C),d.m(s,null)},p(h,E){I===(I=k(h))&&a?a.p(h,E):(a.d(1),a=I(h),a&&(a.c(),a.m(e,null))),v===(v=y(h))&&d?d.p(h,E):(d.d(1),d=v(h),d&&(d.c(),d.m(s,null)))},d(h){h&&(m(e),m(b),m(s)),a.d(),d.d()}}}function ce(u){let e,t="Chargement…";return{c(){e=P("p"),e.textContent=t},l(l){e=S(l,"P",{"data-svelte-h":!0}),H(e)!=="svelte-5hnkvu"&&(e.textContent=t)},m(l,n){L(l,e,n)},p:V,d(l){l&&m(e)}}}function fe(u){let e,t=[],l=new Map,n=A(u[0]);const b=s=>s[19].id;for(let s=0;ss[19].id;for(let s=0;stypeof window<"u"?re():"";async function h(){y==null||y.abort(),y=new AbortController;const w=y.signal;t(2,b=!0),t(3,s="");try{const[g,G]=await Promise.all([fetch(`${d()}/api/sprites/units`,{signal:w}).then(f=>f.json()),fetch(`${d()}/api/sprites/buildings`,{signal:w}).then(f=>f.json())]);if(a)return;t(0,l=g.sprites??[]),t(1,n=G.sprites??[])}catch(g){if(a||w.aborted)return;t(3,s=g instanceof Error?g.message:String(g)),t(0,l=[]),t(1,n=[])}finally{a||t(2,b=!1)}}function E(w,g){const G=`${w}/${g}`,f=I[G],N=f?`?t=${f}`:"";return`${d()}/sprites/${w}/${g}.png${N}`}async function M(w,g){const G=`${w}/${g}`;t(6,C=G),t(7,k="");try{const f=w==="units"?`units/${g}`:`buildings/${g}`,N=await fetch(`${d()}/api/sprites/generate/${f}`,{method:"POST"}),O=await N.json().catch(()=>({}));if(!N.ok)throw new Error(O.detail||N.statusText);I[G]=Date.now()}catch(f){a||t(7,k=f instanceof Error?f.message:String(f))}finally{a||t(6,C=null)}}async function T(){v==null||v.abort(),v=new AbortController;const w=v.signal;t(4,i=!0),t(5,o="");try{const g=await fetch(`${d()}/api/sprites/generate`,{method:"POST",signal:w}),G=await g.json().catch(()=>({}));if(a||w.aborted)return;if(!g.ok)throw new Error(G.detail||g.statusText);await h()}catch(g){if(a||w.aborted)return;t(5,o=g instanceof Error?g.message:String(g))}finally{a||t(4,i=!1)}}return ne(h),ie(()=>{a=!0,y==null||y.abort(),v==null||v.abort()}),[l,n,b,s,i,o,C,k,E,M,T,w=>M("units",w.id),w=>M("buildings",w.id)]}class ke extends ae{constructor(e){super(),oe(this,e,me,de,le,{})}}export{ke as component}; diff --git a/frontend/build/_app/immutable/nodes/6.CN5y69hd.js b/frontend/build/_app/immutable/nodes/6.CN5y69hd.js new file mode 100644 index 0000000000000000000000000000000000000000..26491e6cbb3da0b6b100782aa1f6adbab796f61e --- /dev/null +++ b/frontend/build/_app/immutable/nodes/6.CN5y69hd.js @@ -0,0 +1 @@ +import{s as Ae,n as ue,d as o,C as me,x as xe,p as n,i as j,e as _,y as oe,E as Z,h as k,F as te,G as J,o as re,v as ml,w as vl,B as ot,b as le,j as $,t as x,f as M,l as D,A as ve,r as fe,k as F,m as G,D as be,Z as El,L as _t,J as We}from"../chunks/BWggeOnc.js";import{S as Me,i as De,d as je,t as Te,a as Ve,m as ze,e as Ue,b as Oe}from"../chunks/CbPkTnjI.js";import{g as dt}from"../chunks/B_0L5JHM.js";import{e as ie,b as Ke,g as Sl}from"../chunks/B2mXDaCf.js";import{b as nt,B as Pe,c as gl,d as bl,e as kl,f as yl,U as Nl,m as ct,g as Le,s as Ne,h as Ce,i as Cl,j as Il,k as Pl,l as Al,n as Ml,v as ye,o as pt,q as Be,t as rt,w as et}from"../chunks/JF702EPM.js";const Dl=!1,jl=!1,ji=Object.freeze(Object.defineProperty({__proto__:null,prerender:jl,ssr:Dl},Symbol.toStringTag,{value:"Module"})),wl=2;let Re=0;const at=[];function mt(){if(Re>=wl||at.length===0)return;const i=at.shift();i&&i()}function vt(i,e,t){if(typeof window>"u")return;const l=`${i}/sounds/units/${e}/${t}.mp3`,s=()=>{Re+=1;const r=new Audio(l);r.onended=()=>{Re-=1,mt()},r.onerror=()=>{Re-=1,mt()},r.play()};Re{a.data.size>0&&l.push(a.data)};let s;const r=new Promise(a=>{s=a});return t.onstop=()=>{i.getTracks().forEach(h=>h.stop());const a=new Blob(l,{type:t.mimeType||"audio/webm"});s({blob:a,mimeType:t.mimeType||"audio/webm"})},t.start(100),{stop:()=>{t.state!=="inactive"&&t.stop()},result:r}}async function Vl(i){return new Promise((e,t)=>{const l=new FileReader;l.onload=()=>{const s=l.result;e(s.split(",")[1])},l.onerror=t,l.readAsDataURL(i)})}function zl(i,e="fr-FR"){if(typeof window>"u"||!("speechSynthesis"in window))return;window.speechSynthesis.cancel();const t=new SpeechSynthesisUtterance(i);t.lang=e,t.rate=1.05,t.pitch=1,t.volume=1,window.speechSynthesis.speak(t)}function Ul(){const i=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus","audio/mp4"];for(const e of i)if(typeof MediaRecorder<"u"&&MediaRecorder.isTypeSupported(e))return e;return""}function gt(i,e,t){const l=i.slice();return l[36]=e[t],l}function tt(i){const e=i.slice(),t=e[36].split(",").map(Number);return e[39]=t[0],e[40]=t[1],e}function bt(i,e,t){const l=i.slice();l[36]=e[t];const s=l[36].split(",").map(Number);return l[39]=s[0],l[40]=s[1],l}function kt(i,e,t){const l=i.slice();return l[43]=e[t].u,l[44]=e[t].isOwn,l}function lt(i){var r;const e=i.slice(),t=e[44]?"#58a6ff":"#f85149";e[47]=t;const l=Nl[e[43].unit_type];e[48]=l;const s=e[43].id===((r=e[10])==null?void 0:r.id);return e[49]=s,e}function yt(i,e,t){const l=i.slice();return l[50]=e[t].b,l[44]=e[t].isOwn,l}function wt(i,e,t){const l=i.slice();return l[55]=e[t],l[57]=t,l}function it(i){const e=i.slice(),t=Pe[e[50].building_type];e[53]=t;const l=e[44]?"#1a3a6b":"#6b1a1a";e[47]=l;const s=e[44]?"#58a6ff":"#f85149";e[54]=s;const r=yl[e[50].building_type];return e[48]=r,e}function Et(i,e,t){const l=i.slice();return l[58]=e[t],l}function St(i,e,t){const l=i.slice();return l[55]=e[t],l[62]=t,l}function Nt(i,e,t){const l=i.slice();return l[55]=e[t],l[62]=t,l}function Ol(i){let e;return{c(){e=J("rect"),this.h()},l(t){e=Z(t,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0}),k(e).forEach(o),this.h()},h(){n(e,"x","0"),n(e,"y","0"),n(e,"width",de),n(e,"height",pe),n(e,"fill","#0b1a0b")},m(t,l){j(t,e,l)},p:ue,d(t){t&&o(e)}}}function ql(i){let e;return{c(){e=J("image"),this.h()},l(t){e=Z(t,"image",{href:!0,x:!0,y:!0,width:!0,height:!0,preserveAspectRatio:!0}),k(e).forEach(o),this.h()},h(){n(e,"href",i[8]),n(e,"x","0"),n(e,"y","0"),n(e,"width",de),n(e,"height",pe),n(e,"preserveAspectRatio","xMidYMid slice")},m(t,l){j(t,e,l)},p(t,l){l[0]&256&&n(e,"href",t[8])},d(t){t&&o(e)}}}function Ct(i){let e;return{c(){e=J("line"),this.h()},l(t){e=Z(t,"line",{x1:!0,y1:!0,x2:!0,y2:!0,stroke:!0,"stroke-width":!0}),k(e).forEach(o),this.h()},h(){n(e,"x1",i[62]),n(e,"y1",0),n(e,"x2",i[62]),n(e,"y2",pe),n(e,"stroke","rgba(255,255,255,0.025)"),n(e,"stroke-width","0.02")},m(t,l){j(t,e,l)},p:ue,d(t){t&&o(e)}}}function It(i){let e;return{c(){e=J("line"),this.h()},l(t){e=Z(t,"line",{x1:!0,y1:!0,x2:!0,y2:!0,stroke:!0,"stroke-width":!0}),k(e).forEach(o),this.h()},h(){n(e,"x1",0),n(e,"y1",i[62]),n(e,"x2",de),n(e,"y2",i[62]),n(e,"stroke","rgba(255,255,255,0.025)"),n(e,"stroke-width","0.02")},m(t,l){j(t,e,l)},p:ue,d(t){t&&o(e)}}}function Bl(i){let e,t,l,s,r,a,h;return{c(){e=J("g"),t=J("circle"),s=J("text"),r=x("⛽"),this.h()},l(f){e=Z(f,"g",{transform:!0});var c=k(e);t=Z(c,"circle",{r:!0,fill:!0,opacity:!0}),k(t).forEach(o),s=Z(c,"text",{"text-anchor":!0,"dominant-baseline":!0,"font-size":!0,fill:!0});var p=k(s);r=$(p,"⛽"),p.forEach(o),c.forEach(o),this.h()},h(){n(t,"r","0.35"),n(t,"fill",l=i[58].has_refinery?"#2e7d52":"#1b5e35"),n(t,"opacity","0.85"),n(s,"text-anchor","middle"),n(s,"dominant-baseline","central"),n(s,"font-size","0.35"),n(s,"fill",a=i[58].has_refinery?"#69f0ae":"#4caf50"),n(e,"transform",h="translate("+(i[58].x+.5)+","+(i[58].y+.5)+")")},m(f,c){j(f,e,c),_(e,t),_(e,s),_(s,r)},p(f,c){c[0]&128&&l!==(l=f[58].has_refinery?"#2e7d52":"#1b5e35")&&n(t,"fill",l),c[0]&128&&a!==(a=f[58].has_refinery?"#69f0ae":"#4caf50")&&n(s,"fill",a),c[0]&128&&h!==(h="translate("+(f[58].x+.5)+","+(f[58].y+.5)+")")&&n(e,"transform",h)},d(f){f&&o(e)}}}function Rl(i){let e,t,l,s;return{c(){e=J("g"),t=J("polygon"),l=J("polygon"),this.h()},l(r){e=Z(r,"g",{transform:!0});var a=k(e);t=Z(a,"polygon",{points:!0,fill:!0,opacity:!0}),k(t).forEach(o),l=Z(a,"polygon",{points:!0,fill:!0,opacity:!0}),k(l).forEach(o),a.forEach(o),this.h()},h(){n(t,"points","0,-0.38 0.33,0.19 -0.33,0.19"),n(t,"fill","#4fc3f7"),n(t,"opacity","0.9"),n(l,"points","0,-0.22 0.19,0.11 -0.19,0.11"),n(l,"fill","#b3e5fc"),n(l,"opacity","0.7"),n(e,"transform",s="translate("+(i[58].x+.5)+","+(i[58].y+.5)+")")},m(r,a){j(r,e,a),_(e,t),_(e,l)},p(r,a){a[0]&128&&s!==(s="translate("+(r[58].x+.5)+","+(r[58].y+.5)+")")&&n(e,"transform",s)},d(r){r&&o(e)}}}function Pt(i){let e;function t(r,a){if(r[58].resource_type==="mineral"&&r[58].amount>0)return Rl;if(r[58].resource_type==="geyser")return Bl}let l=t(i),s=l&&l(i);return{c(){s&&s.c(),e=te()},l(r){s&&s.l(r),e=te()},m(r,a){s&&s.m(r,a),j(r,e,a)},p(r,a){l===(l=t(r))&&s?s.p(r,a):(s&&s.d(1),s=l&&l(r),s&&(s.c(),s.m(e.parentNode,e)))},d(r){r&&o(e),s&&s.d(r)}}}function At(i){let e,t,l,s,r,a,h,f,c,p,u=i[48]+"",v,m,A,R,V,O,T,C,E,y,b,U,L,S,H,N=i[50].status==="constructing"&&Mt(i),B=i[50].production_queue.length>0&&Dt(i);return{c(){e=J("g"),t=J("rect"),N&&N.c(),p=J("text"),v=x(u),O=J("rect"),y=J("rect"),B&&B.c(),this.h()},l(P){e=Z(P,"g",{opacity:!0});var I=k(e);t=Z(I,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,stroke:!0,"stroke-width":!0,rx:!0}),k(t).forEach(o),N&&N.l(I),p=Z(I,"text",{x:!0,y:!0,"text-anchor":!0,"dominant-baseline":!0,"font-size":!0,"font-weight":!0,fill:!0,"font-family":!0});var q=k(p);v=$(q,u),q.forEach(o),O=Z(I,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,rx:!0}),k(O).forEach(o),y=Z(I,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,rx:!0}),k(y).forEach(o),B&&B.l(I),I.forEach(o),this.h()},h(){var P;n(t,"x",l=i[50].x+.06),n(t,"y",s=i[50].y+.06),n(t,"width",r=i[53].w-.12),n(t,"height",a=i[53].h-.12),n(t,"fill",h=i[47]),n(t,"stroke",f=i[54]),n(t,"stroke-width",c=i[50].id===((P=i[9])==null?void 0:P.id)?.12:.06),n(t,"rx","0.15"),n(p,"x",m=i[50].x+i[53].w/2),n(p,"y",A=i[50].y+i[53].h/2),n(p,"text-anchor","middle"),n(p,"dominant-baseline","central"),n(p,"font-size",R=i[53].w>=4?.5:.42),n(p,"font-weight","700"),n(p,"fill",V=i[54]),n(p,"font-family","monospace"),n(O,"x",T=i[50].x+.1),n(O,"y",C=i[50].y+i[53].h-.22),n(O,"width",E=i[53].w-.2),n(O,"height","0.12"),n(O,"fill","rgba(0,0,0,0.5)"),n(O,"rx","0.06"),n(y,"x",b=i[50].x+.1),n(y,"y",U=i[50].y+i[53].h-.22),n(y,"width",L=(i[53].w-.2)*(i[50].hp/i[50].max_hp)),n(y,"height","0.12"),n(y,"fill",S=Ze(i[50].hp,i[50].max_hp)),n(y,"rx","0.06"),n(e,"opacity",H=Ht(i[50].status))},m(P,I){j(P,e,I),_(e,t),N&&N.m(e,null),_(e,p),_(p,v),_(e,O),_(e,y),B&&B.m(e,null)},p(P,I){var q;I[0]&64&&l!==(l=P[50].x+.06)&&n(t,"x",l),I[0]&64&&s!==(s=P[50].y+.06)&&n(t,"y",s),I[0]&64&&r!==(r=P[53].w-.12)&&n(t,"width",r),I[0]&64&&a!==(a=P[53].h-.12)&&n(t,"height",a),I[0]&64&&h!==(h=P[47])&&n(t,"fill",h),I[0]&64&&f!==(f=P[54])&&n(t,"stroke",f),I[0]&576&&c!==(c=P[50].id===((q=P[9])==null?void 0:q.id)?.12:.06)&&n(t,"stroke-width",c),P[50].status==="constructing"?N?N.p(P,I):(N=Mt(P),N.c(),N.m(e,p)):N&&(N.d(1),N=null),I[0]&64&&u!==(u=P[48]+"")&&le(v,u),I[0]&64&&m!==(m=P[50].x+P[53].w/2)&&n(p,"x",m),I[0]&64&&A!==(A=P[50].y+P[53].h/2)&&n(p,"y",A),I[0]&64&&R!==(R=P[53].w>=4?.5:.42)&&n(p,"font-size",R),I[0]&64&&V!==(V=P[54])&&n(p,"fill",V),I[0]&64&&T!==(T=P[50].x+.1)&&n(O,"x",T),I[0]&64&&C!==(C=P[50].y+P[53].h-.22)&&n(O,"y",C),I[0]&64&&E!==(E=P[53].w-.2)&&n(O,"width",E),I[0]&64&&b!==(b=P[50].x+.1)&&n(y,"x",b),I[0]&64&&U!==(U=P[50].y+P[53].h-.22)&&n(y,"y",U),I[0]&64&&L!==(L=(P[53].w-.2)*(P[50].hp/P[50].max_hp))&&n(y,"width",L),I[0]&64&&S!==(S=Ze(P[50].hp,P[50].max_hp))&&n(y,"fill",S),P[50].production_queue.length>0?B?B.p(P,I):(B=Dt(P),B.c(),B.m(e,null)):B&&(B.d(1),B=null),I[0]&64&&H!==(H=Ht(P[50].status))&&n(e,"opacity",H)},d(P){P&&o(e),N&&N.d(),B&&B.d()}}}function Mt(i){let e,t,l,s,r,a;return{c(){e=J("rect"),this.h()},l(h){e=Z(h,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0,stroke:!0,"stroke-width":!0,"stroke-dasharray":!0,rx:!0,opacity:!0}),k(e).forEach(o),this.h()},h(){n(e,"x",t=i[50].x+.06),n(e,"y",l=i[50].y+.06),n(e,"width",s=i[53].w-.12),n(e,"height",r=i[53].h-.12),n(e,"fill","none"),n(e,"stroke",a=i[54]),n(e,"stroke-width","0.07"),n(e,"stroke-dasharray","0.25 0.15"),n(e,"rx","0.15"),n(e,"opacity","0.6")},m(h,f){j(h,e,f)},p(h,f){f[0]&64&&t!==(t=h[50].x+.06)&&n(e,"x",t),f[0]&64&&l!==(l=h[50].y+.06)&&n(e,"y",l),f[0]&64&&s!==(s=h[53].w-.12)&&n(e,"width",s),f[0]&64&&r!==(r=h[53].h-.12)&&n(e,"height",r),f[0]&64&&a!==(a=h[54])&&n(e,"stroke",a)},d(h){h&&o(e)}}}function Dt(i){let e,t=ie({length:Math.min(i[50].production_queue.length,5)}),l=[];for(let s=0;s.6?"#3fb950":t>.3?"#d29922":"#f85149"}function Ht(i){return i==="destroyed"?.2:i==="constructing"?.55:1}function Fl(i,e,t){let l,s,r,a,h,f,c,p,u,v,m;re(i,ct,w=>t(22,p=w)),re(i,Le,w=>t(23,u=w)),re(i,Ne,w=>t(9,v=w)),re(i,Ce,w=>t(10,m=w));const A={scv:6,marine:6,medic:6,goliath:8,tank:8,wraith:9},R={command_center:10,supply_depot:7,barracks:7,engineering_bay:7,refinery:7,factory:7,armory:7,starport:7};function V(w){return w.unit_type==="tank"&&w.is_sieged?Yl:A[w.unit_type]}function O(w){return R[w.building_type]}let T=0,C=0,E=Xl,y=Hl,b,U=null;function L(w,K){const W=b.getBoundingClientRect();return[T+(w-W.left)*(E/W.width),C+(K-W.top)*(y/W.height)]}function S(w){w.touches.length===1&&(U={kind:"pan",startX:w.touches[0].clientX,startY:w.touches[0].clientY,lastX:w.touches[0].clientX,lastY:w.touches[0].clientY,moved:!1})}function H(w){if(U&&U.kind==="pan"&&w.touches.length===1){const K=b.getBoundingClientRect(),W=E/K.width,Y=y/K.height,X=(w.touches[0].clientX-U.lastX)*W,ge=(w.touches[0].clientY-U.lastY)*Y,we=Math.hypot(w.touches[0].clientX-U.startX,w.touches[0].clientY-U.startY)>6;t(0,T=qe(T-X,0,de-E)),t(1,C=qe(C-ge,0,pe-y)),U={...U,lastX:w.touches[0].clientX,lastY:w.touches[0].clientY,moved:we}}}function N(w){if((U==null?void 0:U.kind)==="pan"&&!U.moved){const[K,W]=L(U.startX,U.startY);g(K,W)}w.touches.length===0&&(U=null)}let B=!1,P=0,I=0;function q(w){B=!0,P=w.clientX,I=w.clientY}function ne(w){if(!B)return;const K=b.getBoundingClientRect(),W=E/K.width,Y=y/K.height;t(0,T=qe(T-(w.clientX-P)*W,0,de-E)),t(1,C=qe(C-(w.clientY-I)*Y,0,pe-y)),P=w.clientX,I=w.clientY}function ee(){B=!1}function g(w,K){const W=u;if(W){for(const Y of Object.values(W.players))for(const X of Object.values(Y.units))if(Math.hypot(X.x-w,X.y-K)<.7){Ce.set(X),Ne.set(null);return}for(const Y of Object.values(W.players))for(const X of Object.values(Y.buildings)){const ge=Pe[X.building_type];if(w>=X.x&&w=X.y&&K{d=nt.subscribe(w=>{w&&(t(0,T=qe(w.cx-E/2,0,de-E)),t(1,C=qe(w.cy-y/2,0,pe-y)),nt.set(null))})}),vl(()=>{d==null||d()});function Q(w,K){const W=Math.floor(w),Y=Math.floor(K);return W<0||W>=de||Y<0||Y>=pe?!1:c.has(`${W},${Y}`)}function ae(w){ot[w?"unshift":"push"](()=>{b=w,t(4,b)})}return i.$$.update=()=>{if(i.$$.dirty[0]&8388608&&t(21,s=u),i.$$.dirty[0]&4194304&&t(20,r=p),i.$$.dirty[0]&2097152&&t(7,a=(s==null?void 0:s.game_map.resources)??[]),i.$$.dirty[0]&3145728&&t(6,h=s?Object.values(s.players).flatMap(w=>Object.values(w.buildings).map(K=>({b:K,isOwn:w.player_id===r}))):[]),i.$$.dirty[0]&3145728&&t(5,f=s?Object.values(s.players).flatMap(w=>Object.values(w.units).map(K=>({u:K,isOwn:w.player_id===r}))):[]),i.$$.dirty[0]&3145728&&t(3,c=(()=>{const w=new Set;if(!s||!r)return w;const K=s.players[r];if(!K)return w;for(const W of Object.values(K.units)){const Y=V(W);Xt(W.x,W.y,Y).forEach(X=>w.add(X))}for(const W of Object.values(K.buildings)){if(W.status==="destroyed")continue;const Y=Pe[W.building_type],X=W.x+Y.w/2,ge=W.y+Y.h/2,we=O(W);Xt(X,ge,we).forEach(Ie=>w.add(Ie))}return w})()),i.$$.dirty[0]&12){const w=new Set(z);c.forEach(K=>w.add(K)),w.size!==z.size&&t(2,z=w)}i.$$.dirty[0]&3&&gl.set({vx:T,vy:C,vw:E,vh:y}),i.$$.dirty[0]&8&&bl.set(c),i.$$.dirty[0]&4&&kl.set(z)},t(8,l=typeof window<"u"?`${Ke()}/static/MAP.png`:""),[T,C,z,c,b,f,h,a,l,v,m,E,y,S,H,N,q,ne,ee,Q,r,s,p,u,ae]}class Gl extends Me{constructor(e){super(),De(this,e,Fl,Ll,Ae,{},null,[-1,-1,-1])}}function Ft(i,e,t){const l=i.slice();return l[10]=e[t],l}function st(i){const e=i.slice(),t=e[10].split(",").map(Number);return e[13]=t[0],e[14]=t[1],e}function Gt(i,e,t){const l=i.slice();l[10]=e[t];const s=l[10].split(",").map(Number);return l[13]=s[0],l[14]=s[1],l}function Wl(i){let e;return{c(){e=J("rect"),this.h()},l(t){e=Z(t,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0}),k(e).forEach(o),this.h()},h(){n(e,"x","0"),n(e,"y","0"),n(e,"width",Ee),n(e,"height",Se),n(e,"fill","#0b1a0b")},m(t,l){j(t,e,l)},p:ue,d(t){t&&o(e)}}}function Kl(i){let e;return{c(){e=J("image"),this.h()},l(t){e=Z(t,"image",{href:!0,x:!0,y:!0,width:!0,height:!0,preserveAspectRatio:!0}),k(e).forEach(o),this.h()},h(){n(e,"href",i[4]),n(e,"x","0"),n(e,"y","0"),n(e,"width",Ee),n(e,"height",Se),n(e,"preserveAspectRatio","xMidYMid slice")},m(t,l){j(t,e,l)},p(t,l){l&16&&n(e,"href",t[4])},d(t){t&&o(e)}}}function Wt(i){let e,t,l;return{c(){e=J("rect"),this.h()},l(s){e=Z(s,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0}),k(e).forEach(o),this.h()},h(){n(e,"x",t=i[13]),n(e,"y",l=i[14]),n(e,"width","1"),n(e,"height","1"),n(e,"fill","black")},m(s,r){j(s,e,r)},p(s,r){r&2&&t!==(t=s[13])&&n(e,"x",t),r&2&&l!==(l=s[14])&&n(e,"y",l)},d(s){s&&o(e)}}}function Kt(i){let e,t,l;return{c(){e=J("rect"),this.h()},l(s){e=Z(s,"rect",{x:!0,y:!0,width:!0,height:!0,fill:!0}),k(e).forEach(o),this.h()},h(){n(e,"x",t=i[13]),n(e,"y",l=i[14]),n(e,"width","1"),n(e,"height","1"),n(e,"fill","white")},m(s,r){j(s,e,r)},p(s,r){r&2&&t!==(t=s[13])&&n(e,"x",t),r&2&&l!==(l=s[14])&&n(e,"y",l)},d(s){s&&o(e)}}}function Zt(i){let e=!i[2].has(i[10]),t,l=e&&Kt(st(i));return{c(){l&&l.c(),t=te()},l(s){l&&l.l(s),t=te()},m(s,r){l&&l.m(s,r),j(s,t,r)},p(s,r){r&6&&(e=!s[2].has(s[10])),e?l?l.p(st(s),r):(l=Kt(st(s)),l.c(),l.m(t.parentNode,t)):l&&(l.d(1),l=null)},d(s){s&&o(t),l&&l.d(s)}}}function Zl(i){let e,t,l,s,r,a,h,f,c,p,u,v,m,A,R,V,O;function T(S,H){return S[4]?Kl:Wl}let C=T(i),E=C(i),y=ie(Array.from(i[1])),b=[];for(let S=0;St(6,h=m)),re(i,bl,m=>t(7,f=m)),re(i,gl,m=>t(8,c=m));let p;function u(m){m.preventDefault(),m.stopPropagation();const R=m.currentTarget.getBoundingClientRect(),V=Math.max(0,Math.min(1,(m.clientX-R.left)/R.width)),O=Math.max(0,Math.min(1,(m.clientY-R.top)/R.height)),T=V*Ee,C=O*Se;nt.set({cx:T,cy:C})}function v(m){ot[m?"unshift":"push"](()=>{p=m,t(0,p)})}return i.$$.update=()=>{i.$$.dirty&256&&t(3,s=c),i.$$.dirty&128&&t(2,r=f),i.$$.dirty&64&&t(1,a=h)},t(4,l=typeof window<"u"?`${Ke()}/static/MAP.png`:""),[p,a,r,s,l,u,h,f,c,v]}class Ql extends Me{constructor(e){super(),De(this,e,Jl,Zl,Ae,{})}}function Jt(i,e,t){const l=i.slice();l[15]=e[t];const s=Math.round((1-l[15].minRemaining/l[15].maxTicks)*100);return l[16]=s,l}function Qt(i,e,t){const l=i.slice();l[19]=e[t];const s=Math.round((1-l[19].remaining/l[19].maxTicks)*100);return l[16]=s,l}function $t(i){let e,t,l,s=ie(i[0]),r=[];for(let c=0;c0&&i[1]&&el(),h=ie(i[3]),f=[];for(let c=0;c0&&c[1]?a||(a=el(),a.c(),a.m(e,l)):a&&(a.d(1),a=null),p&8){h=ie(c[3]);let u;for(u=0;u1&&tl(i);return{c(){e=D("div"),t=D("div"),l=D("span"),r=x(s),a=G(),u&&u.c(),h=G(),f=D("div"),c=D("div"),p=G(),this.h()},l(v){e=M(v,"DIV",{class:!0});var m=k(e);t=M(m,"DIV",{class:!0});var A=k(t);l=M(A,"SPAN",{class:!0});var R=k(l);r=$(R,s),R.forEach(o),a=F(A),u&&u.l(A),A.forEach(o),h=F(m),f=M(m,"DIV",{class:!0});var V=k(f);c=M(V,"DIV",{class:!0,style:!0}),k(c).forEach(o),V.forEach(o),p=F(m),m.forEach(o),this.h()},h(){n(l,"class","prod-icon svelte-55rzsd"),n(t,"class","prod-icon-wrap svelte-55rzsd"),n(c,"class","prod-bar-fill svelte-55rzsd"),be(c,"width",i[16]+"%"),n(f,"class","prod-bar-wrap svelte-55rzsd"),n(e,"class","prod-card svelte-55rzsd")},m(v,m){j(v,e,m),_(e,t),_(t,l),_(l,r),_(t,a),u&&u.m(t,null),_(e,h),_(e,f),_(f,c),_(e,p)},p(v,m){m&8&&s!==(s=v[15].icon+"")&&le(r,s),v[15].count>1?u?u.p(v,m):(u=tl(v),u.c(),u.m(t,null)):u&&(u.d(1),u=null),m&8&&be(c,"width",v[16]+"%")},d(v){v&&o(e),u&&u.d()}}}function $l(i){var ge,we,Ie;let e,t,l,s,r,a="💎",h,f,c=(((ge=i[2])==null?void 0:ge.minerals)??0)+"",p,u,v,m,A="⛽",R,V,O=(((we=i[2])==null?void 0:we.gas)??0)+"",T,C,E,y,b="👥",U,L,S=i[7].used+"",H,N,B=i[7].max+"",P,I,q,ne,ee,g,z,d,Q,ae=(((Ie=i[8])==null?void 0:Ie.player_name)??"…")+"",w,K,W,Y,X=i[4]&&$t(i);return{c(){e=D("div"),t=D("div"),l=D("div"),s=D("span"),r=D("span"),r.textContent=a,h=G(),f=D("span"),p=x(c),u=G(),v=D("span"),m=D("span"),m.textContent=A,R=G(),V=D("span"),T=x(O),C=G(),E=D("span"),y=D("span"),y.textContent=b,U=G(),L=D("span"),H=x(S),N=x("/"),P=x(B),I=G(),q=D("div"),ne=x(i[6]),ee=x(":"),g=x(i[5]),z=G(),d=D("div"),Q=D("span"),w=x(ae),K=G(),W=D("span"),Y=G(),X&&X.c(),this.h()},l(se){e=M(se,"DIV",{class:!0});var ce=k(e);t=M(ce,"DIV",{class:!0});var he=k(t);l=M(he,"DIV",{class:!0});var _e=k(l);s=M(_e,"SPAN",{class:!0});var ke=k(s);r=M(ke,"SPAN",{class:!0,"data-svelte-h":!0}),fe(r)!=="svelte-s41lk"&&(r.textContent=a),h=F(ke),f=M(ke,"SPAN",{class:!0});var ut=k(f);p=$(ut,c),ut.forEach(o),ke.forEach(o),u=F(_e),v=M(_e,"SPAN",{class:!0});var Ye=k(v);m=M(Ye,"SPAN",{class:!0,"data-svelte-h":!0}),fe(m)!=="svelte-13wjbyu"&&(m.textContent=A),R=F(Ye),V=M(Ye,"SPAN",{class:!0});var ft=k(V);T=$(ft,O),ft.forEach(o),Ye.forEach(o),C=F(_e),E=M(_e,"SPAN",{class:!0});var Xe=k(E);y=M(Xe,"SPAN",{class:!0,"data-svelte-h":!0}),fe(y)!=="svelte-qf8m2b"&&(y.textContent=b),U=F(Xe),L=M(Xe,"SPAN",{class:!0});var He=k(L);H=$(He,S),N=$(He,"/"),P=$(He,B),He.forEach(o),Xe.forEach(o),_e.forEach(o),I=F(he),q=M(he,"DIV",{class:!0});var Fe=k(q);ne=$(Fe,i[6]),ee=$(Fe,":"),g=$(Fe,i[5]),Fe.forEach(o),z=F(he),d=M(he,"DIV",{class:!0});var Ge=k(d);Q=M(Ge,"SPAN",{class:!0});var ht=k(Q);w=$(ht,ae),ht.forEach(o),K=F(Ge),W=M(Ge,"SPAN",{class:!0}),k(W).forEach(o),Ge.forEach(o),he.forEach(o),Y=F(ce),X&&X.l(ce),ce.forEach(o),this.h()},h(){n(r,"class","res-icon svelte-55rzsd"),n(f,"class","res-val mono svelte-55rzsd"),n(s,"class","res mineral svelte-55rzsd"),n(m,"class","res-icon svelte-55rzsd"),n(V,"class","res-val mono svelte-55rzsd"),n(v,"class","res gas svelte-55rzsd"),n(y,"class","res-icon svelte-55rzsd"),n(L,"class","res-val mono svelte-55rzsd"),n(E,"class","res supply svelte-55rzsd"),ve(E,"supply-critical",i[7].max-i[7].used<=2),n(l,"class","resources own svelte-55rzsd"),n(q,"class","timer mono svelte-55rzsd"),n(Q,"class","enemy-name svelte-55rzsd"),n(W,"class","enemy-dot svelte-55rzsd"),n(d,"class","enemy-label svelte-55rzsd"),n(t,"class","resource-bar svelte-55rzsd"),n(e,"class","resource-bar-wrap svelte-55rzsd")},m(se,ce){j(se,e,ce),_(e,t),_(t,l),_(l,s),_(s,r),_(s,h),_(s,f),_(f,p),_(l,u),_(l,v),_(v,m),_(v,R),_(v,V),_(V,T),_(l,C),_(l,E),_(E,y),_(E,U),_(E,L),_(L,H),_(L,N),_(L,P),_(t,I),_(t,q),_(q,ne),_(q,ee),_(q,g),_(t,z),_(t,d),_(d,Q),_(Q,w),_(d,K),_(d,W),_(e,Y),X&&X.m(e,null)},p(se,[ce]){var he,_e,ke;ce&4&&c!==(c=(((he=se[2])==null?void 0:he.minerals)??0)+"")&&le(p,c),ce&4&&O!==(O=(((_e=se[2])==null?void 0:_e.gas)??0)+"")&&le(T,O),ce&128&&S!==(S=se[7].used+"")&&le(H,S),ce&128&&B!==(B=se[7].max+"")&&le(P,B),ce&128&&ve(E,"supply-critical",se[7].max-se[7].used<=2),ce&64&&le(ne,se[6]),ce&32&&le(g,se[5]),ce&256&&ae!==(ae=(((ke=se[8])==null?void 0:ke.player_name)??"…")+"")&&le(w,ae),se[4]?X?X.p(se,ce):(X=$t(se),X.c(),X.m(e,null)):X&&(X.d(1),X=null)},i:ue,o:ue,d(se){se&&o(e),X&&X.d()}}}function xl(i,e,t){let l,s,r,a,h,f,c,p,u,v,m,A,R,V,O;return re(i,Le,T=>t(11,A=T)),re(i,Pl,T=>t(12,R=T)),re(i,Al,T=>t(13,V=T)),re(i,Ml,T=>t(14,O=T)),i.$$.update=()=>{i.$$.dirty&16384&&t(2,l=O),i.$$.dirty&8192&&t(8,s=V),i.$$.dirty&4096&&t(7,r=R),i.$$.dirty&2048&&t(10,a=(A==null?void 0:A.tick)??0),i.$$.dirty&1024&&t(9,h=Math.floor(a/4)),i.$$.dirty&512&&t(6,f=Math.floor(h/60).toString().padStart(2,"0")),i.$$.dirty&512&&t(5,c=(h%60).toString().padStart(2,"0")),i.$$.dirty&4&&t(3,p=(()=>{const T={};for(const C of Object.values((l==null?void 0:l.buildings)??{}))for(const E of C.production_queue)T[E.unit_type]||(T[E.unit_type]={count:0,minRemaining:E.ticks_remaining,maxTicks:E.max_ticks}),T[E.unit_type].count++,E.ticks_remaining({type:C,icon:Cl[C]??"?",...E})).sort((C,E)=>C.minRemaining-E.minRemaining)})()),i.$$.dirty&8&&t(1,u=p.length>0),i.$$.dirty&4&&t(0,v=Object.values((l==null?void 0:l.buildings)??{}).filter(T=>T.status==="constructing").map(T=>({id:T.id,type:T.building_type,icon:Il[T.building_type]??"🏗️",remaining:T.construction_ticks_remaining,maxTicks:T.construction_max_ticks||1})).sort((T,C)=>T.remaining-C.remaining)),i.$$.dirty&3&&t(4,m=u||v.length>0)},[v,u,l,p,m,c,f,r,s,h,a,A,R,V,O]}class ei extends Me{constructor(e){super(),De(this,e,xl,$l,Ae,{})}}function ti(i){let e,t="🎙️";return{c(){e=D("span"),e.textContent=t,this.h()},l(l){e=M(l,"SPAN",{class:!0,"data-svelte-h":!0}),fe(e)!=="svelte-e6vbro"&&(e.textContent=t),this.h()},h(){n(e,"class","mic-icon svelte-i113ea")},m(l,s){j(l,e,s)},d(l){l&&o(e)}}}function li(i){let e;return{c(){e=D("span"),this.h()},l(t){e=M(t,"SPAN",{class:!0}),k(e).forEach(o),this.h()},h(){n(e,"class","spinner svelte-i113ea")},m(t,l){j(t,e,l)},d(t){t&&o(e)}}}function ii(i){let e,t,l,s,r,a="🔴";return{c(){e=D("span"),t=G(),l=D("span"),s=G(),r=D("span"),r.textContent=a,this.h()},l(h){e=M(h,"SPAN",{class:!0}),k(e).forEach(o),t=F(h),l=M(h,"SPAN",{class:!0}),k(l).forEach(o),s=F(h),r=M(h,"SPAN",{class:!0,"data-svelte-h":!0}),fe(r)!=="svelte-1yq9ug3"&&(r.textContent=a),this.h()},h(){n(e,"class","pulse-ring svelte-i113ea"),n(l,"class","pulse-ring ring2 svelte-i113ea"),n(r,"class","mic-icon svelte-i113ea")},m(h,f){j(h,e,f),j(h,t,f),j(h,l,f),j(h,s,f),j(h,r,f)},d(h){h&&(o(e),o(t),o(l),o(s),o(r))}}}function si(i){let e;return{c(){e=x("Hold to speak")},l(t){e=$(t,"Hold to speak")},m(t,l){j(t,e,l)},d(t){t&&o(e)}}}function ni(i){let e;return{c(){e=x("Processing…")},l(t){e=$(t,"Processing…")},m(t,l){j(t,e,l)},d(t){t&&o(e)}}}function ri(i){let e;return{c(){e=x("Listening…")},l(t){e=$(t,"Listening…")},m(t,l){j(t,e,l)},d(t){t&&o(e)}}}function ai(i){let e,t,l,s,r,a;function h(m,A){return m[0]==="recording"?ii:m[0]==="processing"?li:ti}let f=h(i),c=f(i);function p(m,A){return m[0]==="recording"?ri:m[0]==="processing"?ni:si}let u=p(i),v=u(i);return{c(){e=D("button"),c.c(),t=G(),l=D("span"),v.c(),this.h()},l(m){e=M(m,"BUTTON",{class:!0,"aria-label":!0});var A=k(e);c.l(A),t=F(A),l=M(A,"SPAN",{class:!0});var R=k(l);v.l(R),R.forEach(o),A.forEach(o),this.h()},h(){n(l,"class","btn-label svelte-i113ea"),n(e,"class","voice-btn svelte-i113ea"),e.disabled=s=i[0]==="processing",n(e,"aria-label","Hold to speak"),ve(e,"recording",i[0]==="recording"),ve(e,"processing",i[0]==="processing")},m(m,A){j(m,e,A),c.m(e,null),_(e,t),_(e,l),v.m(l,null),r||(a=[oe(e,"mousedown",i[1]),oe(e,"mouseup",i[2]),oe(e,"mouseleave",i[2]),oe(e,"touchstart",i[1],{passive:!1}),oe(e,"touchend",i[2],{passive:!1}),oe(e,"touchcancel",i[2])],r=!0)},p(m,[A]){f!==(f=h(m))&&(c.d(1),c=f(m),c&&(c.c(),c.m(e,t))),u!==(u=p(m))&&(v.d(1),v=u(m),v&&(v.c(),v.m(l,null))),A&1&&s!==(s=m[0]==="processing")&&(e.disabled=s),A&1&&ve(e,"recording",m[0]==="recording"),A&1&&ve(e,"processing",m[0]==="processing")},i:ue,o:ue,d(m){m&&o(e),c.d(),v.d(),r=!1,xe(a)}}}function oi(i,e,t){let l;re(i,ye,f=>t(0,l=f));const s=El();let r=null;async function a(f){if(f.preventDefault(),l==="idle")try{const{stop:c,result:p}=await Tl();r=c,ye.set("recording"),p.then(async({blob:u,mimeType:v})=>{const m=await Vl(u);s("send",{audio_b64:m,mime_type:v})})}catch{ye.set("idle"),console.error("Microphone not available")}}function h(f){f.preventDefault(),l==="recording"&&(r==null||r(),r=null,ye.set("processing"))}return[l,a,h]}class ci extends Me{constructor(e){super(),De(this,e,oi,ai,Ae,{})}}function il(i,e,t){const l=i.slice();return l[10]=e[t],l[12]=t,l}function ui(i){const e=i.slice(),t=yl[e[0].building_type];return e[9]=t,e}function sl(i){let e,t,l,s,r="✕",a,h,f;function c(m,A){if(m[1])return fi;if(m[0])return nl}function p(m,A){return A===nl?ui(m):m}let u=c(i),v=u&&u(p(i,u));return{c(){e=D("div"),t=G(),l=D("div"),s=D("button"),s.textContent=r,a=G(),v&&v.c(),this.h()},l(m){e=M(m,"DIV",{class:!0}),k(e).forEach(o),t=F(m),l=M(m,"DIV",{class:!0});var A=k(l);s=M(A,"BUTTON",{class:!0,"aria-label":!0,"data-svelte-h":!0}),fe(s)!=="svelte-aj4bc"&&(s.textContent=r),a=F(A),v&&v.l(A),A.forEach(o),this.h()},h(){n(e,"class","panel-backdrop svelte-15lj9ka"),n(s,"class","close-btn svelte-15lj9ka"),n(s,"aria-label","Close"),n(l,"class","panel svelte-15lj9ka"),ve(l,"is-enemy",!i[2])},m(m,A){j(m,e,A),j(m,t,A),j(m,l,A),_(l,s),_(l,a),v&&v.m(l,null),h||(f=[oe(e,"click",i[4]),oe(s,"click",i[4])],h=!0)},p(m,A){u===(u=c(m))&&v?v.p(p(m,u),A):(v&&v.d(1),v=u&&u(p(m,u)),v&&(v.c(),v.m(l,null))),A&4&&ve(l,"is-enemy",!m[2])},d(m){m&&(o(e),o(t),o(l)),v&&v.d(),h=!1,xe(f)}}}function nl(i){let e,t,l,s,r=i[0].building_type.replace(/_/g," ").toUpperCase()+"",a,h,f,c=i[2]?"Ally":"Enemy",p,u,v,m,A="HP",R,V,O,T,C,E=Math.ceil(i[0].hp)+"",y,b,U=i[0].max_hp+"",L,S,H,N,B=i[0].status==="constructing"&&rl(i),P=i[0].production_queue.length>0&&al(i);return{c(){e=D("div"),t=D("span"),l=G(),s=D("span"),a=x(r),h=G(),f=D("span"),p=x(c),u=G(),v=D("div"),m=D("span"),m.textContent=A,R=G(),V=D("div"),O=D("div"),T=G(),C=D("span"),y=x(E),b=x(" / "),L=x(U),S=G(),B&&B.c(),H=G(),P&&P.c(),N=te(),this.h()},l(I){e=M(I,"DIV",{class:!0});var q=k(e);t=M(q,"SPAN",{class:!0}),k(t).forEach(o),l=F(q),s=M(q,"SPAN",{class:!0});var ne=k(s);a=$(ne,r),ne.forEach(o),h=F(q),f=M(q,"SPAN",{class:!0});var ee=k(f);p=$(ee,c),ee.forEach(o),q.forEach(o),u=F(I),v=M(I,"DIV",{class:!0});var g=k(v);m=M(g,"SPAN",{class:!0,"data-svelte-h":!0}),fe(m)!=="svelte-4ij1my"&&(m.textContent=A),R=F(g),V=M(g,"DIV",{class:!0});var z=k(V);O=M(z,"DIV",{class:!0,style:!0}),k(O).forEach(o),z.forEach(o),T=F(g),C=M(g,"SPAN",{class:!0});var d=k(C);y=$(d,E),b=$(d," / "),L=$(d,U),d.forEach(o),g.forEach(o),S=F(I),B&&B.l(I),H=F(I),P&&P.l(I),N=te(),this.h()},h(){n(t,"class","owner-dot svelte-15lj9ka"),ve(t,"own",i[2]),n(s,"class","unit-name svelte-15lj9ka"),n(f,"class","owner-label svelte-15lj9ka"),n(e,"class","panel-header svelte-15lj9ka"),n(m,"class","stat-label svelte-15lj9ka"),n(O,"class","hp-bar svelte-15lj9ka"),be(O,"width",Je(i[0].hp,i[0].max_hp)+"%"),be(O,"background",Qe(i[0].hp,i[0].max_hp)),n(V,"class","hp-bar-wrap svelte-15lj9ka"),n(C,"class","stat-val svelte-15lj9ka"),n(v,"class","stat-row svelte-15lj9ka")},m(I,q){j(I,e,q),_(e,t),_(e,l),_(e,s),_(s,a),_(e,h),_(e,f),_(f,p),j(I,u,q),j(I,v,q),_(v,m),_(v,R),_(v,V),_(V,O),_(v,T),_(v,C),_(C,y),_(C,b),_(C,L),j(I,S,q),B&&B.m(I,q),j(I,H,q),P&&P.m(I,q),j(I,N,q)},p(I,q){q&4&&ve(t,"own",I[2]),q&1&&r!==(r=I[0].building_type.replace(/_/g," ").toUpperCase()+"")&&le(a,r),q&4&&c!==(c=I[2]?"Ally":"Enemy")&&le(p,c),q&1&&be(O,"width",Je(I[0].hp,I[0].max_hp)+"%"),q&1&&be(O,"background",Qe(I[0].hp,I[0].max_hp)),q&1&&E!==(E=Math.ceil(I[0].hp)+"")&&le(y,E),q&1&&U!==(U=I[0].max_hp+"")&&le(L,U),I[0].status==="constructing"?B?B.p(I,q):(B=rl(I),B.c(),B.m(H.parentNode,H)):B&&(B.d(1),B=null),I[0].production_queue.length>0?P?P.p(I,q):(P=al(I),P.c(),P.m(N.parentNode,N)):P&&(P.d(1),P=null)},d(I){I&&(o(e),o(u),o(v),o(S),o(H),o(N)),B&&B.d(I),P&&P.d(I)}}}function fi(i){let e,t,l,s,r=i[1].unit_type.toUpperCase()+"",a,h,f,c=i[2]?"Ally":"Enemy",p,u,v,m=pt[i[1].unit_type]+"",A,R,V,O,T="HP",C,E,y,b,U,L=Math.ceil(i[1].hp)+"",S,H,N=i[1].max_hp+"",B,P,I,q,ne="Status",ee,g,z=fl(i[1].status)+"",d,Q,ae,w,K=i[1].is_sieged&&cl(),W=i[1].is_cloaked&&ul();return{c(){e=D("div"),t=D("span"),l=G(),s=D("span"),a=x(r),h=G(),f=D("span"),p=x(c),u=G(),v=D("p"),A=x(m),R=G(),V=D("div"),O=D("span"),O.textContent=T,C=G(),E=D("div"),y=D("div"),b=G(),U=D("span"),S=x(L),H=x(" / "),B=x(N),P=G(),I=D("div"),q=D("span"),q.textContent=ne,ee=G(),g=D("span"),d=x(z),Q=G(),K&&K.c(),ae=G(),W&&W.c(),w=te(),this.h()},l(Y){e=M(Y,"DIV",{class:!0});var X=k(e);t=M(X,"SPAN",{class:!0}),k(t).forEach(o),l=F(X),s=M(X,"SPAN",{class:!0});var ge=k(s);a=$(ge,r),ge.forEach(o),h=F(X),f=M(X,"SPAN",{class:!0});var we=k(f);p=$(we,c),we.forEach(o),X.forEach(o),u=F(Y),v=M(Y,"P",{class:!0});var Ie=k(v);A=$(Ie,m),Ie.forEach(o),R=F(Y),V=M(Y,"DIV",{class:!0});var se=k(V);O=M(se,"SPAN",{class:!0,"data-svelte-h":!0}),fe(O)!=="svelte-4ij1my"&&(O.textContent=T),C=F(se),E=M(se,"DIV",{class:!0});var ce=k(E);y=M(ce,"DIV",{class:!0,style:!0}),k(y).forEach(o),ce.forEach(o),b=F(se),U=M(se,"SPAN",{class:!0});var he=k(U);S=$(he,L),H=$(he," / "),B=$(he,N),he.forEach(o),se.forEach(o),P=F(Y),I=M(Y,"DIV",{class:!0});var _e=k(I);q=M(_e,"SPAN",{class:!0,"data-svelte-h":!0}),fe(q)!=="svelte-1nqewxk"&&(q.textContent=ne),ee=F(_e),g=M(_e,"SPAN",{class:!0});var ke=k(g);d=$(ke,z),ke.forEach(o),_e.forEach(o),Q=F(Y),K&&K.l(Y),ae=F(Y),W&&W.l(Y),w=te(),this.h()},h(){n(t,"class","owner-dot svelte-15lj9ka"),ve(t,"own",i[2]),n(s,"class","unit-name svelte-15lj9ka"),n(f,"class","owner-label svelte-15lj9ka"),n(e,"class","panel-header svelte-15lj9ka"),n(v,"class","description svelte-15lj9ka"),n(O,"class","stat-label svelte-15lj9ka"),n(y,"class","hp-bar svelte-15lj9ka"),be(y,"width",Je(i[1].hp,i[1].max_hp)+"%"),be(y,"background",Qe(i[1].hp,i[1].max_hp)),n(E,"class","hp-bar-wrap svelte-15lj9ka"),n(U,"class","stat-val svelte-15lj9ka"),n(V,"class","stat-row svelte-15lj9ka"),n(q,"class","stat-label svelte-15lj9ka"),n(g,"class","stat-val status svelte-15lj9ka"),n(I,"class","stat-row svelte-15lj9ka")},m(Y,X){j(Y,e,X),_(e,t),_(e,l),_(e,s),_(s,a),_(e,h),_(e,f),_(f,p),j(Y,u,X),j(Y,v,X),_(v,A),j(Y,R,X),j(Y,V,X),_(V,O),_(V,C),_(V,E),_(E,y),_(V,b),_(V,U),_(U,S),_(U,H),_(U,B),j(Y,P,X),j(Y,I,X),_(I,q),_(I,ee),_(I,g),_(g,d),j(Y,Q,X),K&&K.m(Y,X),j(Y,ae,X),W&&W.m(Y,X),j(Y,w,X)},p(Y,X){X&4&&ve(t,"own",Y[2]),X&2&&r!==(r=Y[1].unit_type.toUpperCase()+"")&&le(a,r),X&4&&c!==(c=Y[2]?"Ally":"Enemy")&&le(p,c),X&2&&m!==(m=pt[Y[1].unit_type]+"")&&le(A,m),X&2&&be(y,"width",Je(Y[1].hp,Y[1].max_hp)+"%"),X&2&&be(y,"background",Qe(Y[1].hp,Y[1].max_hp)),X&2&&L!==(L=Math.ceil(Y[1].hp)+"")&&le(S,L),X&2&&N!==(N=Y[1].max_hp+"")&&le(B,N),X&2&&z!==(z=fl(Y[1].status)+"")&&le(d,z),Y[1].is_sieged?K||(K=cl(),K.c(),K.m(ae.parentNode,ae)):K&&(K.d(1),K=null),Y[1].is_cloaked?W||(W=ul(),W.c(),W.m(w.parentNode,w)):W&&(W.d(1),W=null)},d(Y){Y&&(o(e),o(u),o(v),o(R),o(V),o(P),o(I),o(Q),o(ae),o(w)),K&&K.d(Y),W&&W.d(Y)}}}function rl(i){let e,t,l="Building",s,r,a=$e(i[0].construction_ticks_remaining)+"",h,f;return{c(){e=D("div"),t=D("span"),t.textContent=l,s=G(),r=D("span"),h=x(a),f=x(" remaining"),this.h()},l(c){e=M(c,"DIV",{class:!0});var p=k(e);t=M(p,"SPAN",{class:!0,"data-svelte-h":!0}),fe(t)!=="svelte-13d4gh2"&&(t.textContent=l),s=F(p),r=M(p,"SPAN",{class:!0});var u=k(r);h=$(u,a),f=$(u," remaining"),u.forEach(o),p.forEach(o),this.h()},h(){n(t,"class","stat-label svelte-15lj9ka"),n(r,"class","stat-val svelte-15lj9ka"),n(e,"class","stat-row svelte-15lj9ka")},m(c,p){j(c,e,p),_(e,t),_(e,s),_(e,r),_(r,h),_(r,f)},p(c,p){p&1&&a!==(a=$e(c[0].construction_ticks_remaining)+"")&&le(h,a)},d(c){c&&o(e)}}}function al(i){let e,t="Production Queue",l,s,r=ie(i[0].production_queue),a=[];for(let h=0;h.6?"var(--success)":t>.3?"var(--warning)":"var(--danger)"}function fl(i){return{idle:"Idle",moving:"Moving",attacking:"Attacking",mining_minerals:"Mining minerals",mining_gas:"Mining gas",building:"Building",healing:"Healing",sieged:"Siege mode",patrolling:"Patrolling"}[i]??i}function $e(i){return(i/4).toFixed(0)+"s"}function di(i,e,t){let l,s,r,a,h,f,c,p;re(i,ct,v=>t(6,f=v)),re(i,Ne,v=>t(7,c=v)),re(i,Ce,v=>t(8,p=v));function u(){Ce.set(null),Ne.set(null)}return i.$$.update=()=>{i.$$.dirty&256&&t(1,l=p),i.$$.dirty&128&&t(0,s=c),i.$$.dirty&64&&t(5,r=f),i.$$.dirty&3&&t(3,a=!!l||!!s),i.$$.dirty&35&&t(2,h=l?l.owner===r:s?s.owner===r:!1)},[s,l,h,a,u,r,f,c,p]}class pi extends Me{constructor(e){super(),De(this,e,di,_i,Ae,{})}}function hl(i){let e;function t(r,a){return r[0]&&!r[2]?vi:mi}let l=t(i),s=l(i);return{c(){e=D("div"),s.c(),this.h()},l(r){e=M(r,"DIV",{class:!0});var a=k(e);s.l(a),a.forEach(o),this.h()},h(){n(e,"class","overlay svelte-1rkl0l5")},m(r,a){j(r,e,a),s.m(e,null)},p(r,a){l===(l=t(r))&&s?s.p(r,a):(s.d(1),s=l(r),s&&(s.c(),s.m(e,null)))},d(r){r&&o(e),s.d()}}}function mi(i){let e,t,l=i[2]&&_l(i),s=i[1]&&dl(i);return{c(){l&&l.c(),e=G(),s&&s.c(),t=te()},l(r){l&&l.l(r),e=F(r),s&&s.l(r),t=te()},m(r,a){l&&l.m(r,a),j(r,e,a),s&&s.m(r,a),j(r,t,a)},p(r,a){r[2]?l?l.p(r,a):(l=_l(r),l.c(),l.m(e.parentNode,e)):l&&(l.d(1),l=null),r[1]?s?s.p(r,a):(s=dl(r),s.c(),s.m(t.parentNode,t)):s&&(s.d(1),s=null)},d(r){r&&(o(e),o(t)),l&&l.d(r),s&&s.d(r)}}}function vi(i){let e,t='';return{c(){e=D("div"),e.innerHTML=t,this.h()},l(l){e=M(l,"DIV",{class:!0,"data-svelte-h":!0}),fe(e)!=="svelte-1fwsd0q"&&(e.innerHTML=t),this.h()},h(){n(e,"class","processing-dots svelte-1rkl0l5")},m(l,s){j(l,e,s)},p:ue,d(l){l&&o(e)}}}function _l(i){let e,t,l,s;return{c(){e=D("p"),t=x('"'),l=x(i[2]),s=x('"'),this.h()},l(r){e=M(r,"P",{class:!0});var a=k(e);t=$(a,'"'),l=$(a,i[2]),s=$(a,'"'),a.forEach(o),this.h()},h(){n(e,"class","transcription svelte-1rkl0l5")},m(r,a){j(r,e,a),_(e,t),_(e,l),_(e,s)},p(r,a){a&4&&le(l,r[2])},d(r){r&&o(e)}}}function dl(i){let e,t;return{c(){e=D("p"),t=x(i[1]),this.h()},l(l){e=M(l,"P",{class:!0});var s=k(e);t=$(s,i[1]),s.forEach(o),this.h()},h(){n(e,"class","feedback svelte-1rkl0l5")},m(l,s){j(l,e,s),_(e,t)},p(l,s){s&2&&le(t,l[1])},d(l){l&&o(e)}}}function gi(i){let e,t=(i[2]||i[1]||i[0])&&hl(i);return{c(){t&&t.c(),e=te()},l(l){t&&t.l(l),e=te()},m(l,s){t&&t.m(l,s),j(l,e,s)},p(l,[s]){l[2]||l[1]||l[0]?t?t.p(l,s):(t=hl(l),t.c(),t.m(e.parentNode,e)):t&&(t.d(1),t=null)},i:ue,o:ue,d(l){l&&o(e),t&&t.d(l)}}}function bi(i,e,t){let l,s,r,a,h,f;return re(i,ye,c=>t(3,a=c)),re(i,Be,c=>t(4,h=c)),re(i,rt,c=>t(5,f=c)),i.$$.update=()=>{i.$$.dirty&32&&t(2,l=f),i.$$.dirty&16&&t(1,s=h),i.$$.dirty&8&&t(0,r=a==="processing")},[r,s,l,a,h,f]}class ki extends Me{constructor(e){super(),De(this,e,bi,gi,Ae,{})}}function yi(i){let e;return{c(){e=x("➤")},l(t){e=$(t,"➤")},m(t,l){j(t,e,l)},d(t){t&&o(e)}}}function wi(i){let e;return{c(){e=D("span"),this.h()},l(t){e=M(t,"SPAN",{class:!0}),k(e).forEach(o),this.h()},h(){n(e,"class","send-spinner svelte-1chqw79")},m(t,l){j(t,e,l)},d(t){t&&o(e)}}}function pl(i){let e,t,l,s=i[4]?"🏆":"💀",r,a,h,f=i[4]?"Victory!":"Defeat",c,p,u,v,m,A="Back to Lobby",R,V;function O(E,y){return E[4]?Si:Ei}let T=O(i),C=T(i);return{c(){e=D("div"),t=D("div"),l=D("div"),r=x(s),a=G(),h=D("h2"),c=x(f),p=G(),u=D("p"),C.c(),v=G(),m=D("button"),m.textContent=A,this.h()},l(E){e=M(E,"DIV",{class:!0});var y=k(e);t=M(y,"DIV",{class:!0});var b=k(t);l=M(b,"DIV",{class:!0});var U=k(l);r=$(U,s),U.forEach(o),a=F(b),h=M(b,"H2",{class:!0});var L=k(h);c=$(L,f),L.forEach(o),p=F(b),u=M(b,"P",{class:!0});var S=k(u);C.l(S),S.forEach(o),v=F(b),m=M(b,"BUTTON",{class:!0,"data-svelte-h":!0}),fe(m)!=="svelte-iditkz"&&(m.textContent=A),b.forEach(o),y.forEach(o),this.h()},h(){n(l,"class","modal-icon svelte-1chqw79"),n(h,"class","modal-title svelte-1chqw79"),n(u,"class","modal-body svelte-1chqw79"),n(m,"class","btn-back svelte-1chqw79"),n(t,"class","modal svelte-1chqw79"),n(e,"class","modal-backdrop svelte-1chqw79")},m(E,y){j(E,e,y),_(e,t),_(t,l),_(l,r),_(t,a),_(t,h),_(h,c),_(t,p),_(t,u),C.m(u,null),_(t,v),_(t,m),R||(V=oe(m,"click",i[9]),R=!0)},p(E,y){y&16&&s!==(s=E[4]?"🏆":"💀")&&le(r,s),y&16&&f!==(f=E[4]?"Victory!":"Defeat")&&le(c,f),T===(T=O(E))&&C?C.p(E,y):(C.d(1),C=T(E),C&&(C.c(),C.m(u,null)))},d(E){E&&o(e),C.d(),R=!1,V()}}}function Ei(i){let e,t;return{c(){e=x(i[1]),t=x(" won the battle.")},l(l){e=$(l,i[1]),t=$(l," won the battle.")},m(l,s){j(l,e,s),j(l,t,s)},p(l,s){s&2&&le(e,l[1])},d(l){l&&(o(e),o(t))}}}function Si(i){let e;return{c(){e=x("Congratulations, Commander. The enemy base has been destroyed.")},l(t){e=$(t,"Congratulations, Commander. The enemy base has been destroyed.")},m(t,l){j(t,e,l)},p:ue,d(t){t&&o(e)}}}function Ni(i){let e,t,l,s,r,a,h,f,c,p,u,v,m,A,R,V,O,T,C,E,y,b,U="or",L,S,H,N,B,P,I;t=new ei({}),r=new Gl({}),h=new Ql({}),c=new pi({}),v=new ki({});function q(z,d){return z[5]==="processing"?wi:yi}let ne=q(i),ee=ne(i);S=new ci({}),S.$on("send",i[6]);let g=i[0]&&pl(i);return{c(){e=D("div"),Oe(t.$$.fragment),l=G(),s=D("div"),Oe(r.$$.fragment),a=G(),Oe(h.$$.fragment),f=G(),Oe(c.$$.fragment),p=G(),u=D("div"),Oe(v.$$.fragment),m=G(),A=D("div"),R=D("div"),V=D("input"),T=G(),C=D("button"),ee.c(),y=G(),b=D("div"),b.textContent=U,L=G(),Oe(S.$$.fragment),H=G(),g&&g.c(),N=te(),this.h()},l(z){e=M(z,"DIV",{class:!0});var d=k(e);Ue(t.$$.fragment,d),l=F(d),s=M(d,"DIV",{class:!0});var Q=k(s);Ue(r.$$.fragment,Q),a=F(Q),Ue(h.$$.fragment,Q),f=F(Q),Ue(c.$$.fragment,Q),Q.forEach(o),p=F(d),u=M(d,"DIV",{class:!0});var ae=k(u);Ue(v.$$.fragment,ae),m=F(ae),A=M(ae,"DIV",{class:!0});var w=k(A);R=M(w,"DIV",{class:!0});var K=k(R);V=M(K,"INPUT",{class:!0,type:!0,placeholder:!0,maxlength:!0}),T=F(K),C=M(K,"BUTTON",{class:!0,"aria-label":!0});var W=k(C);ee.l(W),W.forEach(o),K.forEach(o),y=F(w),b=M(w,"DIV",{class:!0,"aria-hidden":!0,"data-svelte-h":!0}),fe(b)!=="svelte-mdk7ug"&&(b.textContent=U),L=F(w),Ue(S.$$.fragment,w),w.forEach(o),ae.forEach(o),d.forEach(o),H=F(z),g&&g.l(z),N=te(),this.h()},h(){n(s,"class","map-wrap svelte-1chqw79"),n(V,"class","text-cmd svelte-1chqw79"),n(V,"type","text"),n(V,"placeholder","Type a command… (Enter to send)"),V.disabled=O=i[5]==="processing",n(V,"maxlength",300),n(C,"class","text-send-btn svelte-1chqw79"),C.disabled=E=i[5]==="processing"||!i[2].trim(),n(C,"aria-label","Send command"),n(R,"class","text-input-wrap svelte-1chqw79"),n(b,"class","divider svelte-1chqw79"),n(b,"aria-hidden","true"),n(A,"class","controls-row svelte-1chqw79"),n(u,"class","bottom-panel svelte-1chqw79"),n(e,"class","game-layout svelte-1chqw79")},m(z,d){j(z,e,d),ze(t,e,null),_(e,l),_(e,s),ze(r,s,null),_(s,a),ze(h,s,null),_(s,f),ze(c,s,null),_(e,p),_(e,u),ze(v,u,null),_(u,m),_(u,A),_(A,R),_(R,V),i[13](V),_t(V,i[2]),_(R,T),_(R,C),ee.m(C,null),_(A,y),_(A,b),_(A,L),ze(S,A,null),j(z,H,d),g&&g.m(z,d),j(z,N,d),B=!0,P||(I=[oe(V,"input",i[14]),oe(V,"keydown",i[8]),oe(C,"click",i[7])],P=!0)},p(z,[d]){(!B||d&32&&O!==(O=z[5]==="processing"))&&(V.disabled=O),d&4&&V.value!==z[2]&&_t(V,z[2]),ne!==(ne=q(z))&&(ee.d(1),ee=ne(z),ee&&(ee.c(),ee.m(C,null))),(!B||d&36&&E!==(E=z[5]==="processing"||!z[2].trim()))&&(C.disabled=E),z[0]?g?g.p(z,d):(g=pl(z),g.c(),g.m(N.parentNode,N)):g&&(g.d(1),g=null)},i(z){B||(Ve(t.$$.fragment,z),Ve(r.$$.fragment,z),Ve(h.$$.fragment,z),Ve(c.$$.fragment,z),Ve(v.$$.fragment,z),Ve(S.$$.fragment,z),B=!0)},o(z){Te(t.$$.fragment,z),Te(r.$$.fragment,z),Te(h.$$.fragment,z),Te(c.$$.fragment,z),Te(v.$$.fragment,z),Te(S.$$.fragment,z),B=!1},d(z){z&&(o(e),o(H),o(N)),je(t),je(r),je(h),je(c),je(v),i[13](null),ee.d(),je(S),g&&g.d(z),P=!1,xe(I)}}}function Ci(i,e,t){let l,s,r,a,h,f,c,p;re(i,et,b=>t(11,r=b)),re(i,ct,b=>t(12,a=b)),re(i,ye,b=>t(5,h=b)),re(i,Ne,b=>t(15,f=b)),re(i,Ce,b=>t(16,c=b)),re(i,Le,b=>t(17,p=b));const u=Sl();let v=!1,m="";p||dt("/"),ml(()=>{u.on("game_update",b=>{const{sound_events:U,...L}=b;if(Le.set(L),U!=null&&U.length){const S=Ke();for(const H of U)(H.kind==="move_ack"||H.kind==="death"||H.kind==="fire")&&vt(S,H.unit_type,H.kind)}if(c){const S=Object.values(L.players).find(H=>H.player_id===(c==null?void 0:c.owner));Ce.set((S==null?void 0:S.units[c.id])??null)}if(f){const S=Object.values(L.players).find(H=>H.player_id===(f==null?void 0:f.owner));Ne.set((S==null?void 0:S.buildings[f.id])??null)}}),u.on("voice_result",b=>{var U;if(rt.set(b.transcription),Be.set(b.feedback_text),ye.set("idle"),We().then(()=>V==null?void 0:V.focus()),(U=b.sound_events)!=null&&U.length){const L=Ke();for(const S of b.sound_events)(S.kind==="move_ack"||S.kind==="death"||S.kind==="fire")&&vt(L,S.unit_type,S.kind)}b.feedback_text&&zl(b.feedback_text),setTimeout(()=>{rt.set(""),Be.set("")},6e3)}),u.on("game_over",({winner_id:b,winner_name:U})=>{et.set(b),t(1,m=U),t(0,v=!0),ye.set("idle"),We().then(()=>V==null?void 0:V.focus())}),u.on("error",({message:b})=>{Be.set(`⚠️ ${b}`),ye.set("idle"),We().then(()=>V==null?void 0:V.focus()),setTimeout(()=>Be.set(""),3e3)})}),vl(()=>{u.off("game_update"),u.off("voice_result"),u.off("game_over"),u.off("error")});function A(b){u.emit("voice_input",{audio_b64:b.detail.audio_b64,mime_type:b.detail.mime_type})}let R="",V=null;async function O(){const b=R.trim();!b||h==="processing"||(u.emit("text_input",{text:b}),t(2,R=""),ye.set("processing"),await We(),V==null||V.focus())}function T(b){b.key==="Enter"&&!b.shiftKey&&(b.preventDefault(),O())}function C(){Le.set(null),et.set(null),t(0,v=!1),dt("/")}function E(b){ot[b?"unshift":"push"](()=>{V=b,t(3,V)})}function y(){R=this.value,t(2,R)}return i.$$.update=()=>{i.$$.dirty&4096&&t(10,l=a),i.$$.dirty&3072&&t(4,s=r===l)},[v,m,R,V,s,h,A,O,T,C,l,r,a,E,y]}class Ti extends Me{constructor(e){super(),De(this,e,Ci,Ni,Ae,{})}}export{Ti as component,ji as universal}; diff --git a/frontend/build/_app/version.json b/frontend/build/_app/version.json index de14fd64ad4a7684e44719c98a3d9c494a2dad81..b9d322fa1f9ca68e0056d50a0dcece5f08d3a86a 100644 --- a/frontend/build/_app/version.json +++ b/frontend/build/_app/version.json @@ -1 +1 @@ -{"version":"1772279345188"} \ No newline at end of file +{"version":"1772350203926"} \ No newline at end of file diff --git a/frontend/build/index.html b/frontend/build/index.html index 4d64b0c133587fe66c2cc0d8bf593533ada8fa64..8833335263d6441468c6a6c4a265b0b76cc0610f 100644 --- a/frontend/build/index.html +++ b/frontend/build/index.html @@ -8,27 +8,27 @@ - VoiceStrike - - - - - + ChatCraft + + + + +
-{#if transcription || feedback || processing} -
- {#if processing && !transcription} -
- -
- {:else} - {#if transcription} -

"{transcription}"

- {/if} - {#if feedback} - - {/if} +
+ {#if processing && !transcription} +
+ +
+ {:else} + {#if transcription} +

"{transcription}"

{/if} -
-{/if} + {#if feedback} + + {/if} + {/if} +
diff --git a/frontend/src/lib/components/Map.svelte b/frontend/src/lib/components/Map.svelte index c5317b6d06413c2c0f15fd977ed6ae8b9c9ade81..8304ea633a3d8ae60c5b5924173b43128575b45f 100644 --- a/frontend/src/lib/components/Map.svelte +++ b/frontend/src/lib/components/Map.svelte @@ -1,14 +1,58 @@ @@ -287,81 +515,85 @@ {#each resources as res} {#if res.resource_type === 'mineral' && res.amount > 0} - - - - + {:else if res.resource_type === 'geyser'} - - - - + {/if} {/each} - {#each allBuildings as { b, isOwn }} - {#if b.status !== 'destroyed' && (isOwn || isVisible(b.x + BUILDING_SIZES[b.building_type].w / 2, b.y + BUILDING_SIZES[b.building_type].h / 2))} + {#each allBuildings as { b, isOwn, colorBg, colorBorder }} + {#if b.status !== 'destroyed' && (isOwn || isVisible(b.x, b.y))} {@const size = BUILDING_SIZES[b.building_type]} - {@const color = isOwn ? '#1a3a6b' : '#6b1a1a'} - {@const border = isOwn ? '#58a6ff' : '#f85149'} - {@const label = BUILDING_LABELS[b.building_type]} + {@const color = colorBg} + {@const border = colorBorder} + {@const bx = b.x - size.w / 2} + {@const by = b.y - size.h / 2} - + + {#if b.id === $selectedBuilding?.id} + + {/if} + + - + + + + {#if b.status === 'constructing'} {/if} - - = 4 ? 0.5 : 0.42} - font-weight="700" - fill={border} - font-family="monospace" - >{label} - 0} {#each { length: Math.min(b.production_queue.length, 5) } as _, qi} + + + + + + + {#each allUnits as { u, isOwn } (u.id)} + {@const op = beamOpacities[u.id] ?? 0} + {#if op > 0 && (isOwn || isVisible(u.x, u.y))} + {@const beam = UNIT_BEAM[u.unit_type]} + {@const isSiegedTank = u.unit_type === 'tank' && u.is_sieged} + {@const bw = isSiegedTank ? beam.width * 1.6 : beam.width} + {@const srcRs = unitRenderStates[u.id]} + {@const srcX = srcRs?.renderX ?? u.x} + {@const srcY = srcRs?.renderY ?? u.y} + {@const targetPos = u.attack_target_id + ? (() => { const t = allUnits.find(a => a.u.id === u.attack_target_id); const trs = unitRenderStates[u.attack_target_id]; return t ? { x: trs?.renderX ?? t.u.x, y: trs?.renderY ?? t.u.y } : null; })() + : u.attack_target_building_id + ? (buildingPosMap[u.attack_target_building_id] ?? null) + : null} + {#if targetPos} + + + + + {/if} + {/if} + {/each} + - {#each allUnits as { u, isOwn }} + {#each allUnits as { u, isOwn, color } (u.id)} {#if isOwn || isVisible(u.x, u.y)} - {@const color = isOwn ? '#58a6ff' : '#f85149'} - {@const label = UNIT_LABELS[u.unit_type]} + {@const rs = unitRenderStates[u.id]} + {@const rx = rs?.renderX ?? u.x} + {@const ry = rs?.renderY ?? u.y} + {@const angle = rs?.angle ?? 0} {@const isSelected = u.id === $selectedUnit?.id} - + {#if isSelected} - + {/if} - + + + + - - + - - {label} - - - {#if u.hp < u.max_hp} - - - {/if} {#if u.is_sieged} - + {/if} {#if u.is_cloaked} - + {/if} {/if} {/each} + + {#if showWalkable} + {#each walkablePolygons as pts} + + {/each} + {/if} + + + {#if showNavPoints} + {#each navPoints as [nx, ny]} + + {/each} + {/if} + @@ -465,8 +749,10 @@ {/each} + {#if !isObserver} + {/if} diff --git a/frontend/src/lib/components/ResourceBar.svelte b/frontend/src/lib/components/ResourceBar.svelte index cae161be2a153e74d5193a94d9599966248b3764..19f48c0a89d079cb2425e4139a281fd09cda2f84 100644 --- a/frontend/src/lib/components/ResourceBar.svelte +++ b/frontend/src/lib/components/ResourceBar.svelte @@ -1,6 +1,9 @@ + + @@ -75,8 +104,8 @@ flex-direction: column; align-items: center; gap: 4px; - width: 80px; - height: 80px; + width: 60px; + height: 60px; border-radius: 50%; background: var(--surface2); border: 2px solid var(--border); @@ -99,9 +128,9 @@ } .mic-icon { - font-size: 1.8rem; + font-size: 1.4rem; line-height: 1; - margin-top: 14px; + margin-top: 10px; } .btn-label { diff --git a/frontend/src/lib/map.png b/frontend/src/lib/map.png index 6246d213d984516d27e95228f74eeab2148ac89d..dd2af5a8ad11e52559b6a90e8f0d91747e816c90 100644 --- a/frontend/src/lib/map.png +++ b/frontend/src/lib/map.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e1dd4669b6e077749e485ea0d617dd3c237add4cf8efda5c6b1d648fcb89c1b -size 2541069 +oid sha256:159a12ea4f0494c96c220884ea192b308a8f2d1e79ac84e930d3a0eac0204b17 +size 27822345 diff --git a/frontend/src/lib/socket.ts b/frontend/src/lib/socket.ts index 2385b9b2c52821aa6b0bc966451d5e61e265cfd8..52853b921f12298be06ebc4afb35f8a1f8b48192 100644 --- a/frontend/src/lib/socket.ts +++ b/frontend/src/lib/socket.ts @@ -4,9 +4,12 @@ let _socket: Socket | null = null; export function backendUrl(): string { if (typeof window === 'undefined') return 'http://localhost:8000'; - // In production (HF Spaces), backend is at the same origin. - // VITE_BACKEND_URL overrides for local dev. - return import.meta.env.VITE_BACKEND_URL ?? window.location.origin; + // VITE_BACKEND_URL overrides (e.g. for dev: http://localhost:8000). + // In dev, use backend origin so /static and /sprites are served correctly (proxy can return HTML otherwise). + if (import.meta.env.VITE_BACKEND_URL) return import.meta.env.VITE_BACKEND_URL; + const origin = window.location.origin; + if (origin === 'http://localhost:5173' || origin === 'http://127.0.0.1:5173') return 'http://localhost:8000'; + return origin; } export function getSocket(): Socket { diff --git a/frontend/src/lib/stores/game.ts b/frontend/src/lib/stores/game.ts index 5992973af1cdfcab30a3248b0ca0509747dac359..a592635274b5aca7ac877056ce0841a40b916fdc 100644 --- a/frontend/src/lib/stores/game.ts +++ b/frontend/src/lib/stores/game.ts @@ -23,6 +23,14 @@ export const mapCenterRequest = writable<{ cx: number; cy: number } | null>(null export const voiceStatus = writable<'idle' | 'recording' | 'processing'>('idle'); export const lastTranscription = writable(''); export const lastFeedback = writable(''); +export const feedbackLevel = writable<'ok' | 'warning' | 'error'>('ok'); +/** + * Progressive map texture URL. + * Starts null → set to MAP_quarter blob → MAP_half blob → MAP full blob + * by the landing-page preloader. Map.svelte falls back to the direct URL + * if this is still null (e.g. user navigated directly to /game). + */ +export const mapTextureUrl = writable(null); // ── Derived ────────────────────────────────────────────────────────────────── diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 66bf96020daba3dbb88a3edd78a37a1c655b5ff8..ef76ffb07b084860b124cf8a0ec008299ba89f60 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -32,6 +32,7 @@ export interface Unit { path_waypoints?: number[][]; attack_target_id: string | null; attack_target_building_id: string | null; + attack_cooldown: number; } export interface ProductionItem { @@ -108,7 +109,8 @@ export interface UnitSoundEvent { export interface VoiceResult { transcription: string; feedback_text: string; - results: Array<{ action_type: string; success: boolean; message: string }>; + feedback_level: 'ok' | 'warning' | 'error'; + results: Array<{ action_type: string; success: boolean; data?: Record }>; sound_events?: UnitSoundEvent[]; } @@ -161,7 +163,7 @@ export const UNIT_SUPPLY_COST: Record = { marine: 1, medic: 1, goliath: 2, - tank: 2, + tank: 3, wraith: 2, }; @@ -190,6 +192,6 @@ export const UNIT_DESCRIPTIONS: Record = { marine: 'Marine — Basic infantry. Anti-ground and anti-air.', medic: 'Medic — Heals adjacent infantry.', goliath: 'Goliath — Heavy vehicle. Anti-ground and anti-air.', - tank: 'Siege Tank — Artillery vehicle. Siege mode: +5 range, +20 damage.', - wraith: 'Wraith — Aerial vessel. Can cloak.', + tank: 'Siege Tank — Heavy artillery. High ground damage, no air attack.', + wraith: 'Wraith — Aerial vessel. Fast air unit, attacks ground and air.', }; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index e012caa9fa5a22a86d4c557f6f0517249993a909..e33e3ecd112ed3c408582760077ba4137d16b38e 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,20 +1,95 @@
- -
- -

Real-time strategy — voice controlled

+
+ Powered by + + +
- + +
{#if errorMsg}
{errorMsg}
{/if} {#if phase === 'home'} -
+
e.key === 'Enter' && quickMatch()} /> -
- -
- - - -
+
+ {playingCount} match{playingCount !== 1 ? 's' : ''} playing right now + - - {#if showJoinInput} -
- e.key === 'Enter' && joinRoom()} - /> - -
- {/if}
{:else if phase === 'queue'}
+
+

Looking for an opponent…

{#if botOfferAvailable} -
🤖
-

No opponent found

-

Play against the AI bot?

- + {:else if botCountdown > 0} +

Bot available in {botCountdown}s

{:else} -
-

Looking for an opponent…

- {#if botCountdown > 0} -

Bot available in {botCountdown}s

- {:else} -

Game starts as soon as another player joins

- {/if} +

Waiting for another player…

{/if}
@@ -285,70 +350,189 @@ {/if} {/if} -
+{#if !mapReady} +
+
+ {mapLabel} {mapProgress < 100 ? `${mapProgress}%` : ''} +
+{/if} + + + + + diff --git a/frontend/src/routes/admin/+layout.svelte b/frontend/src/routes/admin/+layout.svelte new file mode 100644 index 0000000000000000000000000000000000000000..31d2fb59e59904778df3c7fbda9991ea884c2e91 --- /dev/null +++ b/frontend/src/routes/admin/+layout.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/src/routes/admin/compiled-map/+page.svelte b/frontend/src/routes/admin/compiled-map/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..2ec6c42fce43d966a6abbc6a556600a61b0d9024 --- /dev/null +++ b/frontend/src/routes/admin/compiled-map/+page.svelte @@ -0,0 +1,225 @@ + + + + Admin — Carte compilée & navpoints + + +
+

Carte compilée & navpoints

+

+ Visualisation de la carte compilée (exterior, trous) et des points de navigation utilisés pour le pathfinding. + Données chargées depuis /static/compiled_map.json. +

+ + + {#if error} +

{error}

+ {/if} + + {#if loading} +

Chargement…

+ {:else if data} +
+ Exterior : {data.exterior.length} points + Trous : {data.holes.length} + Navpoints : {data.nav_points.length} +
+
+ + + +
+
+ Carte + + {#if showExterior && data.exterior.length >= 3} + + {/if} + {#if showHoles} + {#each data.holes as hole} + {#if hole.length >= 3} + + {/if} + {/each} + {/if} + {#if showNavPoints} + {#each data.nav_points as pt} + {@const [vx, vy] = navToView(pt)} + + {/each} + {/if} + +
+ {:else} +

Aucune donnée.

+ {/if} +
+ + diff --git a/frontend/src/routes/admin/compiled-map/+page.ts b/frontend/src/routes/admin/compiled-map/+page.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3d15781a772c9c833d435893cc10dc9999f63c2 --- /dev/null +++ b/frontend/src/routes/admin/compiled-map/+page.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/frontend/src/routes/admin/map/+page.svelte b/frontend/src/routes/admin/map/+page.svelte index 95ffe207a4a98155096db1682665dfc99771e9ba..6c7e0d68cd003750bbccc3da4f9f459aa160bf86 100644 --- a/frontend/src/routes/admin/map/+page.svelte +++ b/frontend/src/routes/admin/map/+page.svelte @@ -19,13 +19,14 @@ let error = ''; let saveStatus = ''; let saving = false; - let savingPositions = false; let destroyed = false; let svgEl: SVGSVGElement; let containerEl: HTMLDivElement; /** Point d'un polygone marchable en cours de drag */ let draggingPoint: { polygonIndex: number; pointIndex: number } | null = null; + /** Position de départ ou d'expansion en cours de drag */ + let draggingPosition: { type: 'start' | 'expansion'; index: number } | null = null; /** Mode ajout : chaque clic sur la carte ajoute un point */ let addMode = false; /** Mode clic = placer position de départ (1 ou 2) ou expansion */ @@ -35,8 +36,6 @@ /** Points du polygone en cours de dessin */ let pointsInProgress: [number, number][] = []; let baseUrl = ''; - /** Rapport largeur/hauteur de l'image pour aligner le SVG à la même échelle */ - let mapAspectRatio = 1; function screenToSvg(svg: SVGSVGElement, clientX: number, clientY: number): [number, number] { const pt = svg.createSVGPoint(); @@ -127,14 +126,31 @@ saving = true; saveStatus = ''; try { - const r = await fetch(`${baseUrl}/api/map/walkable`, { + // Save walkable polygon + const rw = await fetch(`${baseUrl}/api/map/walkable`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ polygons }), }); - const data = await r.json().catch(() => ({})); - if (!r.ok) throw new Error(data.detail || r.statusText); - saveStatus = 'Sauvegardé.'; + const dw = await rw.json().catch(() => ({})); + if (!rw.ok) throw new Error(dw.detail || rw.statusText); + + // Save positions if at least 2 starting positions are defined + if (startingPositions.length >= 2) { + const rp = await fetch(`${baseUrl}/api/map/positions`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + starting_positions: startingPositions, + expansion_positions: expansionPositions, + }), + }); + const dp = await rp.json().catch(() => ({})); + if (!rp.ok) throw new Error(dp.detail || rp.statusText); + saveStatus = `Sauvegardé — ${startingPositions.length} départs, ${expansionPositions.length} expansions.`; + } else { + saveStatus = 'Zone marchable sauvegardée. Ajoutez au moins 2 positions de départ pour les enregistrer.'; + } } catch (e) { saveStatus = e instanceof Error ? e.message : String(e); } finally { @@ -153,16 +169,35 @@ } function onPointerMove(e: PointerEvent) { - if (!svgEl || draggingPoint === null) return; + if (!svgEl) return; const [x, y] = screenToSvg(svgEl, e.clientX, e.clientY); const pt: [number, number] = [clamp(x, 0, 100), clamp(y, 0, 100)]; - const { polygonIndex, pointIndex } = draggingPoint; - polygons[polygonIndex][pointIndex] = pt; - polygons = polygons; + if (draggingPoint !== null) { + const { polygonIndex, pointIndex } = draggingPoint; + polygons[polygonIndex][pointIndex] = pt; + polygons = polygons; + } else if (draggingPosition !== null) { + const { type, index } = draggingPosition; + if (type === 'start') { + startingPositions[index] = { x: pt[0], y: pt[1] }; + startingPositions = startingPositions; + } else { + expansionPositions[index] = { x: pt[0], y: pt[1] }; + expansionPositions = expansionPositions; + } + } } function onPointerUp() { draggingPoint = null; + draggingPosition = null; + } + + function onPositionDown(e: PointerEvent, type: 'start' | 'expansion', index: number) { + e.preventDefault(); + e.stopPropagation(); + (e.currentTarget as Element).setPointerCapture?.(e.pointerId); + draggingPosition = { type, index }; } function enterAddMode() { @@ -225,43 +260,11 @@ expansionPositions = expansionPositions.filter((_, j) => j !== i); } - async function savePositions() { - if (startingPositions.length !== 3) { - saveStatus = 'Il faut exactement 3 positions de départ (2 seront tirées au sort par partie).'; - return; - } - savingPositions = true; - saveStatus = ''; - try { - const r = await fetch(`${baseUrl}/api/map/positions`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - starting_positions: startingPositions, - expansion_positions: expansionPositions, - }), - }); - const data = await r.json().catch(() => ({})); - if (!r.ok) throw new Error(data.detail || r.statusText); - saveStatus = `Positions enregistrées. ${data.minerals_count ?? 0} minerais et ${data.geysers_count ?? 0} geysers générés.`; - } catch (e) { - saveStatus = e instanceof Error ? e.message : String(e); - } finally { - if (!destroyed) savingPositions = false; - } - } function removeWalkablePolygon(polygonIndex: number) { polygons = polygons.filter((_, i) => i !== polygonIndex); } - function onMapImageLoad(e: Event) { - const img = e.target as HTMLImageElement; - if (img?.naturalWidth && img.naturalHeight) { - mapAspectRatio = img.naturalWidth / img.naturalHeight; - } - } - onMount(() => { if (typeof window === 'undefined') return; baseUrl = backendUrl(); @@ -287,6 +290,7 @@

Carte & zone marchable

+

« Mode ajout » : clique sur la carte pour placer les points, puis « Terminer ». Sinon : glisse les points, double-clic sur un point = supprimer, double-clic sur une arête = ajouter un point.

@@ -301,17 +305,16 @@ {#if loading}

Chargement…

{:else} -
+
Carte de combat @@ -348,15 +351,33 @@ {#each startingPositions as pos, i} - - Départ {i + 1} + Départ {i + 1} + onPositionDown(e, 'start', i)} + /> {/each} {#each expansionPositions as pos, i} - - Exp. {i + 1} + Exp. {i + 1} + onPositionDown(e, 'expansion', i)} + /> {/each} @@ -431,24 +452,27 @@ {/if}

Positions de jeu (départ + expansions)

-

3 positions de départ : à chaque partie, 2 sont tirées au sort pour les 2 joueurs. Les minerais (et geysers) sont générés autour de chaque position.

+

+ 2 ou 3 positions de départ (si 3, 2 sont tirées au sort par partie). Les minerais et geysers sont placés automatiquement autour de chaque position. + Les positions sont sauvegardées avec le bouton « Enregistrer tout » ci-dessus. +

    - {#each [1, 2, 3] as i} -
  • Départ {i} : {startingPositions[i - 1] ? `(${startingPositions[i - 1].x.toFixed(1)}, ${startingPositions[i - 1].y.toFixed(1)})` : '—'}
  • + {#each startingPositions as pos, i} +
  • Départ {i + 1} : ({pos.x.toFixed(1)}, {pos.y.toFixed(1)})
  • {/each} + {#if startingPositions.length === 0} +
  • Aucune position de départ — clique sur « Définir positions de jeu »
  • + {/if} {#each expansionPositions as pos, i}
  • Expansion {i + 1} : ({pos.x.toFixed(1)}, {pos.y.toFixed(1)})
  • {/each}
-
{#if polygons.length > 0}
    @@ -470,6 +494,16 @@ padding: 1rem; } + .nav { + margin-bottom: 0.5rem; + } + .nav a { + color: var(--accent, #58a6ff); + text-decoration: none; + } + .nav a:hover { + text-decoration: underline; + } .hint { color: var(--text-muted, #6b7280); font-size: 0.9rem; @@ -479,18 +513,15 @@ .map-container { position: relative; width: 100%; - max-height: 70vh; background: #1f2937; border-radius: 8px; overflow: hidden; } .map-bg { - position: absolute; - inset: 0; + display: block; width: 100%; - height: 100%; - object-fit: contain; + height: auto; pointer-events: none; } @@ -516,11 +547,23 @@ cursor: grabbing; } - .map-overlay .location, - .map-overlay .game-position { + .map-overlay .location { + pointer-events: none; + } + + .map-overlay .game-position .position-label { pointer-events: none; } + .map-overlay .game-position .position-point { + pointer-events: auto; + cursor: grab; + } + + .map-overlay .game-position .position-point.dragging { + cursor: grabbing; + } + .map-overlay .map-click-layer { pointer-events: none; cursor: default; @@ -590,10 +633,12 @@ padding: 0.15rem 0.4rem; font-size: 0.75rem; } - .positions-save { - margin-top: 0.5rem; + .game-positions-section .positions-list .empty { + color: var(--text-muted, #6b7280); + font-style: italic; } + .add-mode-hint { font-size: 0.9rem; color: var(--text-muted, #6b7280); diff --git a/frontend/src/routes/admin/sprites/+page.svelte b/frontend/src/routes/admin/sprites/+page.svelte index d1fad8311637d7e0ce65405b71472013a2ac66d7..21da7058f83e81d163950c0ae984dbab39535e38 100644 --- a/frontend/src/routes/admin/sprites/+page.svelte +++ b/frontend/src/routes/admin/sprites/+page.svelte @@ -9,6 +9,8 @@ let unitSprites: SpriteItem[] = []; let buildingSprites: SpriteItem[] = []; + let resourceSprites: SpriteItem[] = []; + let uiIcons: SpriteItem[] = []; let loading = true; let error = ''; let generating = false; @@ -16,6 +18,8 @@ let regeneratingId: string | null = null; let regenError = ''; let lastRegenerated: Record = {}; + /** Cache-buster: mis à jour à chaque load() pour éviter le cache navigateur sur les sprites */ + let loadTimestamp = 0; let destroyed = false; let loadAbortController: AbortController | null = null; @@ -30,13 +34,18 @@ loading = true; error = ''; try { - const [u, b] = await Promise.all([ + const [u, b, res, icons] = await Promise.all([ fetch(`${base()}/api/sprites/units`, { signal }).then((r) => r.json()), fetch(`${base()}/api/sprites/buildings`, { signal }).then((r) => r.json()), + fetch(`${base()}/api/sprites/resources`, { signal }).then((r) => r.json()), + fetch(`${base()}/api/icons`, { signal }).then((r) => r.json()), ]); if (destroyed) return; unitSprites = u.sprites ?? []; buildingSprites = b.sprites ?? []; + resourceSprites = res.sprites ?? []; + uiIcons = icons.icons ?? []; + loadTimestamp = Date.now(); } catch (e) { if (destroyed || signal.aborted) return; error = e instanceof Error ? e.message : String(e); @@ -47,20 +56,41 @@ } } - function spriteUrl(kind: 'units' | 'buildings', id: string) { + function iconUrl(id: string) { + const key = `icons/${id}`; + const t = lastRegenerated[key] ?? loadTimestamp; + return `${base()}/sprites/icons/${id}.png?t=${t}`; + } + + async function generateIcon(id: string) { + const key = `icons/${id}`; + regeneratingId = key; + regenError = ''; + try { + const r = await fetch(`${base()}/api/icons/generate/${id}`, { method: 'POST' }); + const data = await r.json().catch(() => ({})); + if (!r.ok) throw new Error(data.detail || r.statusText); + lastRegenerated[key] = Date.now(); + await load(); + } catch (e) { + if (!destroyed) regenError = e instanceof Error ? e.message : String(e); + } finally { + if (!destroyed) regeneratingId = null; + } + } + + function spriteUrl(kind: 'units' | 'buildings' | 'resources', id: string) { const key = `${kind}/${id}`; - const t = lastRegenerated[key]; - const q = t ? `?t=${t}` : ''; - return `${base()}/sprites/${kind}/${id}.png${q}`; + const t = lastRegenerated[key] ?? loadTimestamp; + return `${base()}/sprites/${kind}/${id}.png?t=${t}`; } - async function regenerateOne(kind: 'units' | 'buildings', id: string) { + async function regenerateOne(kind: 'units' | 'buildings' | 'resources', id: string) { const key = `${kind}/${id}`; regeneratingId = key; regenError = ''; try { - const path = kind === 'units' ? `units/${id}` : `buildings/${id}`; - const r = await fetch(`${base()}/api/sprites/generate/${path}`, { method: 'POST' }); + const r = await fetch(`${base()}/api/sprites/generate/${kind}/${id}`, { method: 'POST' }); const data = await r.json().catch(() => ({})); if (!r.ok) throw new Error(data.detail || r.statusText); lastRegenerated[key] = Date.now(); @@ -143,7 +173,13 @@
      {#each unitSprites as s (s.id)}
    • - {s.name} + {#key 'units-' + s.id + '-' + (lastRegenerated['units/' + s.id] ?? 0)} + {s.name} + {/key} {s.name} +
    • + {/each} +
    + {:else} +

    Aucun sprite. Lance la génération ci-dessous.

    + {/if} +
    + {#each [{ id: 'mineral', label: '💎 Générer Mineral' }, { id: 'geyser', label: '🟣 Générer Geyser' }] as res} + + {/each} +
    + + +
    +

    Icônes UI

    +

    Icônes symboliques flat pour l'interface (ressources, supply…)

    + {#if uiIcons.length > 0} +
      + {#each uiIcons as s (s.id)} +
    • + {#key 'icons-' + s.id + '-' + (lastRegenerated['icons/' + s.id] ?? 0)} + {s.name} + {/key} + {s.name} + +
    • + {/each} +
    + {:else} +

    Aucune icône. Lance la génération ci-dessous.

    + {/if} +
    + {#each [ + { id: 'mineral', label: '💎 Icône Minerals' }, + { id: 'gas', label: '🟣 Icône Gas' }, + { id: 'supply', label: '🏠 Icône Supply' }, + ] as ic} + + {/each} +
    +
    {/if}
@@ -246,7 +375,7 @@ flex-direction: column; align-items: center; padding: 0.5rem; - background: #f5f5f5; + background: #e85c1a; border-radius: 8px; } .sprite-grid img { @@ -280,4 +409,9 @@ opacity: 0.6; cursor: not-allowed; } + .resource-gen-btns { + display: flex; + gap: 0.5rem; + margin-top: 0.75rem; + } diff --git a/frontend/src/routes/game/+page.svelte b/frontend/src/routes/game/+page.svelte index 4ca828ada369be0016c41258d465a8ed3ee08a2c..caf02939ba7dca66b1b958c30bc0862106bcf4df 100644 --- a/frontend/src/routes/game/+page.svelte +++ b/frontend/src/routes/game/+page.svelte @@ -5,10 +5,9 @@ import { playUnitSound } from '$lib/unitSounds'; import { gameState, myPlayerId, winnerId, - voiceStatus, lastTranscription, lastFeedback, + voiceStatus, lastTranscription, lastFeedback, feedbackLevel, selectedUnit, selectedBuilding, } from '$lib/stores/game'; - import { speakText } from '$lib/voice'; import type { GameState, VoiceResult } from '$lib/types'; import Map from '$lib/components/Map.svelte'; @@ -17,6 +16,7 @@ import VoiceButton from '$lib/components/VoiceButton.svelte'; import UnitPanel from '$lib/components/UnitPanel.svelte'; import FeedbackOverlay from '$lib/components/FeedbackOverlay.svelte'; + import HelpModal from '$lib/components/HelpModal.svelte'; const socket = getSocket(); @@ -59,6 +59,8 @@ voiceStatus.set('idle'); tick().then(() => textInputEl?.focus()); + feedbackLevel.set(data.feedback_level ?? 'ok'); + if (data.sound_events?.length) { const base = backendUrl(); for (const e of data.sound_events) { @@ -67,14 +69,12 @@ } } } - if (data.feedback_text) { - speakText(data.feedback_text); - } // Auto-clear feedback after 6 seconds setTimeout(() => { lastTranscription.set(''); lastFeedback.set(''); + feedbackLevel.set('ok'); }, 6000); }); @@ -88,9 +88,10 @@ socket.on('error', ({ message }: { message: string }) => { lastFeedback.set(`⚠️ ${message}`); + feedbackLevel.set('error'); voiceStatus.set('idle'); tick().then(() => textInputEl?.focus()); - setTimeout(() => lastFeedback.set(''), 3000); + setTimeout(() => { lastFeedback.set(''); feedbackLevel.set('ok'); }, 3000); }); }); @@ -137,6 +138,8 @@ $: myId = $myPlayerId; $: isWinner = $winnerId === myId; + + let helpOpen = false;
@@ -150,12 +153,14 @@ + +
- +
-
+
+ + {#if showGameOver}