fcode 2.2.0__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. fcode/__init__.py +3 -0
  2. fcode/_types.py +655 -0
  3. fcode/api.py +146 -0
  4. fcode/auth.py +49 -0
  5. fcode/cli.py +127 -0
  6. fcode/commands/__init__.py +0 -0
  7. fcode/commands/init.py +29 -0
  8. fcode/commands/ladder.py +74 -0
  9. fcode/commands/login.py +135 -0
  10. fcode/commands/logout.py +21 -0
  11. fcode/commands/map_editor.py +91 -0
  12. fcode/commands/match_detail.py +173 -0
  13. fcode/commands/match_group.py +163 -0
  14. fcode/commands/matches.py +127 -0
  15. fcode/commands/run.py +186 -0
  16. fcode/commands/starter.py +119 -0
  17. fcode/commands/status.py +112 -0
  18. fcode/commands/submission.py +260 -0
  19. fcode/commands/team_group.py +73 -0
  20. fcode/commands/teams.py +73 -0
  21. fcode/commands/test.py +51 -0
  22. fcode/commands/test_matches.py +59 -0
  23. fcode/commands/test_run.py +125 -0
  24. fcode/commands/watch.py +123 -0
  25. fcode/compat.py +36 -0
  26. fcode/config.py +47 -0
  27. fcode/data/__init__.py +0 -0
  28. fcode/data/docs/spec.md +290 -0
  29. fcode/data/maps/__init__.py +0 -0
  30. fcode/data/maps/atoll.map26 +0 -0
  31. fcode/data/maps/aurora.map26 +0 -0
  32. fcode/data/maps/crossfire.map26 +0 -0
  33. fcode/data/maps/duel.map26 +0 -0
  34. fcode/data/maps/fjord.map26 +0 -0
  35. fcode/data/maps/hive.map26 +0 -0
  36. fcode/data/maps/longship.map26 +0 -0
  37. fcode/data/maps/pinch.map26 +0 -0
  38. fcode/data/maps/quarry.map26 +0 -0
  39. fcode/data/maps/runestone.map26 +0 -0
  40. fcode/data/maps/skerry.map26 +0 -0
  41. fcode/data/maps/sprint.map26 +0 -0
  42. fcode/data/maps/strait.map26 +0 -0
  43. fcode/data/maps/twins.map26 +0 -0
  44. fcode/data/maps/vault.map26 +0 -0
  45. fcode/data/starter_bot.py +452 -0
  46. fcode/data/visualiser/.directory +9 -0
  47. fcode/data/visualiser/Builderbot_med.png +0 -0
  48. fcode/data/visualiser/armoured_conveyor_gold.png +0 -0
  49. fcode/data/visualiser/armoured_conveyor_silver.png +0 -0
  50. fcode/data/visualiser/assets/app-BiRa_5u6.css +1 -0
  51. fcode/data/visualiser/assets/app-CK52NxpR.js +86 -0
  52. fcode/data/visualiser/assets/main-COIx42iq.js +1 -0
  53. fcode/data/visualiser/assets/map-editor-BDLYjgel.js +1 -0
  54. fcode/data/visualiser/axionite_ore.png +0 -0
  55. fcode/data/visualiser/axionite_processed.png +0 -0
  56. fcode/data/visualiser/axionite_raw.png +0 -0
  57. fcode/data/visualiser/barrier_gold.png +0 -0
  58. fcode/data/visualiser/barrier_silver.png +0 -0
  59. fcode/data/visualiser/base_gold.png +0 -0
  60. fcode/data/visualiser/base_silver.png +0 -0
  61. fcode/data/visualiser/battlecodetitle-orange.webp +0 -0
  62. fcode/data/visualiser/bg-light.png +0 -0
  63. fcode/data/visualiser/bg.png +0 -0
  64. fcode/data/visualiser/breach_e_gold.png +0 -0
  65. fcode/data/visualiser/breach_e_silver.png +0 -0
  66. fcode/data/visualiser/breach_n_gold.png +0 -0
  67. fcode/data/visualiser/breach_n_silver.png +0 -0
  68. fcode/data/visualiser/breach_ne_gold.png +0 -0
  69. fcode/data/visualiser/breach_ne_silver.png +0 -0
  70. fcode/data/visualiser/breach_nw_gold.png +0 -0
  71. fcode/data/visualiser/breach_nw_silver.png +0 -0
  72. fcode/data/visualiser/breach_s_gold.png +0 -0
  73. fcode/data/visualiser/breach_s_silver.png +0 -0
  74. fcode/data/visualiser/breach_se_gold.png +0 -0
  75. fcode/data/visualiser/breach_se_silver.png +0 -0
  76. fcode/data/visualiser/breach_sw_gold.png +0 -0
  77. fcode/data/visualiser/breach_sw_silver.png +0 -0
  78. fcode/data/visualiser/bridge_gold.png +0 -0
  79. fcode/data/visualiser/bridge_silver.png +0 -0
  80. fcode/data/visualiser/bridge_stand_gold.png +0 -0
  81. fcode/data/visualiser/bridge_stand_silver.png +0 -0
  82. fcode/data/visualiser/builderbot_back_gold.png +0 -0
  83. fcode/data/visualiser/builderbot_back_silver.png +0 -0
  84. fcode/data/visualiser/builderbot_front_gold.png +0 -0
  85. fcode/data/visualiser/builderbot_front_silver.png +0 -0
  86. fcode/data/visualiser/builderbot_side_gold.png +0 -0
  87. fcode/data/visualiser/builderbot_side_silver.png +0 -0
  88. fcode/data/visualiser/conveyor_gold.png +0 -0
  89. fcode/data/visualiser/conveyor_silver.png +0 -0
  90. fcode/data/visualiser/dimetric/Dimetric_Tile_Cube.png +0 -0
  91. fcode/data/visualiser/dimetric/Dimetric_Tile_Floor_Footprint.png +0 -0
  92. fcode/data/visualiser/dimetric/barrier_team1.png +0 -0
  93. fcode/data/visualiser/dimetric/barrier_team2.png +0 -0
  94. fcode/data/visualiser/dimetric/base_team1.png +0 -0
  95. fcode/data/visualiser/dimetric/base_team2.png +0 -0
  96. fcode/data/visualiser/dimetric/bg.png +0 -0
  97. fcode/data/visualiser/dimetric/bridge_stand_team1.png +0 -0
  98. fcode/data/visualiser/dimetric/bridge_stand_team2.png +0 -0
  99. fcode/data/visualiser/dimetric/bridge_team1.png +0 -0
  100. fcode/data/visualiser/dimetric/bridge_team2.png +0 -0
  101. fcode/data/visualiser/dimetric/builderbot_e_team1.png +0 -0
  102. fcode/data/visualiser/dimetric/builderbot_e_team2.png +0 -0
  103. fcode/data/visualiser/dimetric/builderbot_n_team1.png +0 -0
  104. fcode/data/visualiser/dimetric/builderbot_n_team2.png +0 -0
  105. fcode/data/visualiser/dimetric/builderbot_ne_team1.png +0 -0
  106. fcode/data/visualiser/dimetric/builderbot_ne_team2.png +0 -0
  107. fcode/data/visualiser/dimetric/builderbot_nw_team1.png +0 -0
  108. fcode/data/visualiser/dimetric/builderbot_nw_team2.png +0 -0
  109. fcode/data/visualiser/dimetric/builderbot_s_team1.png +0 -0
  110. fcode/data/visualiser/dimetric/builderbot_s_team2.png +0 -0
  111. fcode/data/visualiser/dimetric/builderbot_se_team1.png +0 -0
  112. fcode/data/visualiser/dimetric/builderbot_se_team2.png +0 -0
  113. fcode/data/visualiser/dimetric/builderbot_sw_team1.png +0 -0
  114. fcode/data/visualiser/dimetric/builderbot_sw_team2.png +0 -0
  115. fcode/data/visualiser/dimetric/builderbot_w_team1.png +0 -0
  116. fcode/data/visualiser/dimetric/builderbot_w_team2.png +0 -0
  117. fcode/data/visualiser/dimetric/conveyor_ne_team1.png +0 -0
  118. fcode/data/visualiser/dimetric/conveyor_ne_team2.png +0 -0
  119. fcode/data/visualiser/dimetric/conveyor_nw_team1.png +0 -0
  120. fcode/data/visualiser/dimetric/conveyor_nw_team2.png +0 -0
  121. fcode/data/visualiser/dimetric/conveyor_se_team1.png +0 -0
  122. fcode/data/visualiser/dimetric/conveyor_se_team2.png +0 -0
  123. fcode/data/visualiser/dimetric/conveyor_sw_team1.png +0 -0
  124. fcode/data/visualiser/dimetric/conveyor_sw_team2.png +0 -0
  125. fcode/data/visualiser/dimetric/gunner_e_team1.png +0 -0
  126. fcode/data/visualiser/dimetric/gunner_e_team2.png +0 -0
  127. fcode/data/visualiser/dimetric/gunner_n_team1.png +0 -0
  128. fcode/data/visualiser/dimetric/gunner_n_team2.png +0 -0
  129. fcode/data/visualiser/dimetric/gunner_ne_team1.png +0 -0
  130. fcode/data/visualiser/dimetric/gunner_ne_team2.png +0 -0
  131. fcode/data/visualiser/dimetric/gunner_nw_team1.png +0 -0
  132. fcode/data/visualiser/dimetric/gunner_nw_team2.png +0 -0
  133. fcode/data/visualiser/dimetric/gunner_s_team1.png +0 -0
  134. fcode/data/visualiser/dimetric/gunner_s_team2.png +0 -0
  135. fcode/data/visualiser/dimetric/gunner_se_team1.png +0 -0
  136. fcode/data/visualiser/dimetric/gunner_se_team2.png +0 -0
  137. fcode/data/visualiser/dimetric/gunner_sw_team1.png +0 -0
  138. fcode/data/visualiser/dimetric/gunner_sw_team2.png +0 -0
  139. fcode/data/visualiser/dimetric/gunner_w_team1.png +0 -0
  140. fcode/data/visualiser/dimetric/gunner_w_team2.png +0 -0
  141. fcode/data/visualiser/dimetric/harvester_team1.png +0 -0
  142. fcode/data/visualiser/dimetric/harvester_team2.png +0 -0
  143. fcode/data/visualiser/dimetric/launcher_team1_ne.png +0 -0
  144. fcode/data/visualiser/dimetric/launcher_team1_nw.png +0 -0
  145. fcode/data/visualiser/dimetric/launcher_team1_se.png +0 -0
  146. fcode/data/visualiser/dimetric/launcher_team1_sw.png +0 -0
  147. fcode/data/visualiser/dimetric/launcher_team2_ne.png +0 -0
  148. fcode/data/visualiser/dimetric/launcher_team2_nw.png +0 -0
  149. fcode/data/visualiser/dimetric/launcher_team2_se.png +0 -0
  150. fcode/data/visualiser/dimetric/launcher_team2_sw.png +0 -0
  151. fcode/data/visualiser/dimetric/natural_wall.png +0 -0
  152. fcode/data/visualiser/dimetric/natural_wall_ne.png +0 -0
  153. fcode/data/visualiser/dimetric/natural_wall_nw.png +0 -0
  154. fcode/data/visualiser/dimetric/natural_wall_se.png +0 -0
  155. fcode/data/visualiser/dimetric/natural_wall_sw.png +0 -0
  156. fcode/data/visualiser/dimetric/road_team1.png +0 -0
  157. fcode/data/visualiser/dimetric/road_team2.png +0 -0
  158. fcode/data/visualiser/dimetric/sentinel_e_team1.png +0 -0
  159. fcode/data/visualiser/dimetric/sentinel_e_team2.png +0 -0
  160. fcode/data/visualiser/dimetric/sentinel_n_team1.png +0 -0
  161. fcode/data/visualiser/dimetric/sentinel_n_team2.png +0 -0
  162. fcode/data/visualiser/dimetric/sentinel_ne_team1.png +0 -0
  163. fcode/data/visualiser/dimetric/sentinel_ne_team2.png +0 -0
  164. fcode/data/visualiser/dimetric/sentinel_nw_team1.png +0 -0
  165. fcode/data/visualiser/dimetric/sentinel_nw_team2.png +0 -0
  166. fcode/data/visualiser/dimetric/sentinel_s_team1.png +0 -0
  167. fcode/data/visualiser/dimetric/sentinel_s_team2.png +0 -0
  168. fcode/data/visualiser/dimetric/sentinel_se_team1.png +0 -0
  169. fcode/data/visualiser/dimetric/sentinel_se_team2.png +0 -0
  170. fcode/data/visualiser/dimetric/sentinel_sw_team1.png +0 -0
  171. fcode/data/visualiser/dimetric/sentinel_sw_team2.png +0 -0
  172. fcode/data/visualiser/dimetric/sentinel_w_team1.png +0 -0
  173. fcode/data/visualiser/dimetric/sentinel_w_team2.png +0 -0
  174. fcode/data/visualiser/dimetric/splitter_ne_team1.png +0 -0
  175. fcode/data/visualiser/dimetric/splitter_ne_team2.png +0 -0
  176. fcode/data/visualiser/dimetric/splitter_nw_team1.png +0 -0
  177. fcode/data/visualiser/dimetric/splitter_nw_team2.png +0 -0
  178. fcode/data/visualiser/dimetric/splitter_se_team1.png +0 -0
  179. fcode/data/visualiser/dimetric/splitter_se_team2.png +0 -0
  180. fcode/data/visualiser/dimetric/splitter_sw_team1.png +0 -0
  181. fcode/data/visualiser/dimetric/splitter_sw_team2.png +0 -0
  182. fcode/data/visualiser/dimetric/titanium.png +0 -0
  183. fcode/data/visualiser/dimetric/titanium_ore.png +0 -0
  184. fcode/data/visualiser/fonts/HKGrotesk-Regular.otf +0 -0
  185. fcode/data/visualiser/foundry_gold.png +0 -0
  186. fcode/data/visualiser/foundry_silver.png +0 -0
  187. fcode/data/visualiser/gunner_n_gold.png +0 -0
  188. fcode/data/visualiser/gunner_n_silver.png +0 -0
  189. fcode/data/visualiser/gunner_ne_gold.png +0 -0
  190. fcode/data/visualiser/gunner_ne_silver.png +0 -0
  191. fcode/data/visualiser/gunner_nw_gold.png +0 -0
  192. fcode/data/visualiser/gunner_nw_silver.png +0 -0
  193. fcode/data/visualiser/gunner_s_gold.png +0 -0
  194. fcode/data/visualiser/gunner_s_silver.png +0 -0
  195. fcode/data/visualiser/gunner_se_gold.png +0 -0
  196. fcode/data/visualiser/gunner_se_silver.png +0 -0
  197. fcode/data/visualiser/gunner_sw_gold.png +0 -0
  198. fcode/data/visualiser/gunner_sw_silver.png +0 -0
  199. fcode/data/visualiser/gunner_w_gold.png +0 -0
  200. fcode/data/visualiser/gunner_w_silver.png +0 -0
  201. fcode/data/visualiser/harvester_gold.png +0 -0
  202. fcode/data/visualiser/harvester_silver.png +0 -0
  203. fcode/data/visualiser/index.html +20 -0
  204. fcode/data/visualiser/launcher_gold.png +0 -0
  205. fcode/data/visualiser/launcher_silver.png +0 -0
  206. fcode/data/visualiser/map-editor.html +20 -0
  207. fcode/data/visualiser/marker_gold.png +0 -0
  208. fcode/data/visualiser/marker_silver.png +0 -0
  209. fcode/data/visualiser/natural_wall.jpg +0 -0
  210. fcode/data/visualiser/road_gold.png +0 -0
  211. fcode/data/visualiser/road_silver.png +0 -0
  212. fcode/data/visualiser/sentinel_e_gold.png +0 -0
  213. fcode/data/visualiser/sentinel_e_silver.png +0 -0
  214. fcode/data/visualiser/sentinel_n_gold.png +0 -0
  215. fcode/data/visualiser/sentinel_n_silver.png +0 -0
  216. fcode/data/visualiser/sentinel_ne_gold.png +0 -0
  217. fcode/data/visualiser/sentinel_ne_silver.png +0 -0
  218. fcode/data/visualiser/sentinel_nw_gold.png +0 -0
  219. fcode/data/visualiser/sentinel_nw_silver.png +0 -0
  220. fcode/data/visualiser/sentinel_s_gold.png +0 -0
  221. fcode/data/visualiser/sentinel_s_silver.png +0 -0
  222. fcode/data/visualiser/sentinel_se_gold.png +0 -0
  223. fcode/data/visualiser/sentinel_se_silver.png +0 -0
  224. fcode/data/visualiser/sentinel_sw_gold.png +0 -0
  225. fcode/data/visualiser/sentinel_sw_silver.png +0 -0
  226. fcode/data/visualiser/splitter.png +0 -0
  227. fcode/data/visualiser/splitter_e_gold.png +0 -0
  228. fcode/data/visualiser/splitter_e_silver.png +0 -0
  229. fcode/data/visualiser/splitter_n_gold.png +0 -0
  230. fcode/data/visualiser/splitter_n_silver.png +0 -0
  231. fcode/data/visualiser/splitter_s_gold.png +0 -0
  232. fcode/data/visualiser/splitter_s_silver.png +0 -0
  233. fcode/data/visualiser/splitter_w_gold.png +0 -0
  234. fcode/data/visualiser/splitter_w_silver.png +0 -0
  235. fcode/data/visualiser/template02.png +0 -0
  236. fcode/data/visualiser/titanium.png +0 -0
  237. fcode/data/visualiser/titanium_ore.png +0 -0
  238. fcode/fcode_engine.cp312-win_amd64.pyd +0 -0
  239. fcode/py.typed +0 -0
  240. fcode/version_check.py +72 -0
  241. fcode-2.2.0.dist-info/METADATA +7 -0
  242. fcode-2.2.0.dist-info/RECORD +245 -0
  243. fcode-2.2.0.dist-info/WHEEL +4 -0
  244. fcode-2.2.0.dist-info/entry_points.txt +2 -0
  245. fcode-2.2.0.dist-info/sboms/fcode_engine.cyclonedx.json +1963 -0
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,452 @@
1
+ """Starter bot — economy-first strategy with light defense.
2
+
3
+ Each unit gets its own Player instance; the engine calls run() once per round.
4
+ Use ct.get_entity_type() to branch on what kind of unit you are.
5
+
6
+ Strategy:
7
+ 1. Core spawns builder bots and publishes its position via the store
8
+ 2. Builder bots explore, build harvesters on ore, and lay conveyors toward
9
+ the core so titanium flows back automatically
10
+ 3. Once the economy is running (3+ harvesters), builder bots place gunners
11
+ near the core for defense
12
+ 4. Gunners auto-fire at the closest visible enemy
13
+
14
+ Entity types used: Core, Builder Bot, Harvester, Conveyor, Gunner
15
+
16
+ Communication store slots:
17
+ 0 SLOT_CORE_X Core X position (written by core on round 1)
18
+ 1 SLOT_CORE_Y Core Y position
19
+ 2 SLOT_HARVESTER_COUNT Total harvesters built by the team
20
+ 3 SLOT_ORE_LOCATION Packed (x, y) of an uncovered ore tile
21
+ Slots 4-15 are free — use them for your own coordination logic.
22
+
23
+ Ideas for improvement:
24
+ - Build full conveyor chains from distant harvesters back to the core
25
+ - Feed ammo to gunners via conveyors so they can actually fire
26
+ - Add sentinels or launchers for stronger defense
27
+ - Explore the map systematically instead of picking random targets
28
+ - Use more store slots to coordinate roles between builder bots
29
+ """
30
+
31
+ import random
32
+
33
+ from fcode import Controller, Direction, EntityType, Environment, GameConstants, Position
34
+
35
+ # All directions except CENTRE — useful for movement and spawning
36
+ DIRECTIONS = [d for d in Direction if d != Direction.CENTRE]
37
+
38
+ # Cardinal directions only — conveyors and splitters can only face these
39
+ CARDINALS = [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]
40
+
41
+ # --- Communication store slot assignments ---
42
+ # The store has 16 slots (indices 0-15), each holding a u32 value.
43
+ # Writes are buffered: a write_store() call becomes visible to all units
44
+ # at the start of the *next* round.
45
+ SLOT_CORE_X = 0
46
+ SLOT_CORE_Y = 1
47
+ SLOT_HARVESTER_COUNT = 2
48
+ SLOT_ORE_LOCATION = 3
49
+
50
+ # How many builder bots the core will spawn over the course of the game
51
+ MAX_BUILDERS = 5
52
+
53
+ # How many harvesters we want before switching builder bots to defense duty
54
+ TARGET_HARVESTERS = 3
55
+
56
+
57
+ def pack_pos(pos: Position) -> int:
58
+ """Encode a position into a single u32 for the communication store.
59
+
60
+ We offset by +1 so that position (0, 0) doesn't encode as 0,
61
+ which we reserve to mean "no data".
62
+ """
63
+ return ((pos.x + 1) << 16) | (pos.y + 1)
64
+
65
+
66
+ def unpack_pos(val: int) -> Position | None:
67
+ """Decode a position from the communication store. Returns None if empty (0)."""
68
+ if val == 0:
69
+ return None
70
+ return Position((val >> 16) - 1, (val & 0xFFFF) - 1)
71
+
72
+
73
+ def nearest_cardinal(d: Direction) -> Direction:
74
+ """Snap any direction to the nearest cardinal direction.
75
+
76
+ Conveyors and splitters can only face cardinal directions (N/E/S/W),
77
+ so we use this to convert an arbitrary direction into a valid facing.
78
+ """
79
+ return {
80
+ Direction.NORTH: Direction.NORTH,
81
+ Direction.NORTHEAST: Direction.NORTH,
82
+ Direction.EAST: Direction.EAST,
83
+ Direction.SOUTHEAST: Direction.EAST,
84
+ Direction.SOUTH: Direction.SOUTH,
85
+ Direction.SOUTHWEST: Direction.SOUTH,
86
+ Direction.WEST: Direction.WEST,
87
+ Direction.NORTHWEST: Direction.WEST,
88
+ Direction.CENTRE: Direction.NORTH,
89
+ }[d]
90
+
91
+
92
+ class Player:
93
+ def __init__(self):
94
+ # Core tracks how many builder bots it has spawned
95
+ self.num_spawned = 0
96
+
97
+ # Builder bot navigation state
98
+ self.target: Position | None = None # where we're trying to walk to
99
+ self.last_pos: Position | None = None # position last round (for stuck detection)
100
+ self.stuck = 0 # consecutive rounds without moving
101
+
102
+ # Cached core position (read from the store once, then reused)
103
+ self.core_pos: Position | None = None
104
+
105
+ def run(self, ct: Controller) -> None:
106
+ """Entry point called by the engine every round for each unit.
107
+
108
+ We check what type of entity we are and dispatch to the right handler.
109
+ Each entity type (core, builder bot, gunner) has its own run logic.
110
+ """
111
+ etype = ct.get_entity_type()
112
+ if etype == EntityType.CORE:
113
+ self._run_core(ct)
114
+ elif etype == EntityType.BUILDER_BOT:
115
+ self._run_builder(ct)
116
+ elif etype == EntityType.GUNNER:
117
+ self._run_gunner(ct)
118
+
119
+ # ------------------------------------------------------------------
120
+ # Core
121
+ # ------------------------------------------------------------------
122
+
123
+ def _run_core(self, ct: Controller) -> None:
124
+ """Core logic: publish our position so builder bots can orient toward us,
125
+ then try to spawn a builder bot each round until we hit the cap.
126
+ """
127
+ # Write our position into the store every round so newly spawned bots
128
+ # can read it. Store writes are buffered — they become visible next round.
129
+ pos = ct.get_position()
130
+ ct.write_store(SLOT_CORE_X, pos.x)
131
+ ct.write_store(SLOT_CORE_Y, pos.y)
132
+
133
+ # Don't spawn more than MAX_BUILDERS total
134
+ if self.num_spawned >= MAX_BUILDERS:
135
+ return
136
+
137
+ # Check we can afford a builder bot before trying
138
+ ti = ct.get_global_resources()
139
+ cost = ct.get_builder_bot_cost()
140
+ if ti < cost:
141
+ return
142
+
143
+ # Try to spawn on a random adjacent tile (shuffle to avoid bias)
144
+ dirs = list(DIRECTIONS)
145
+ random.shuffle(dirs)
146
+ for d in dirs:
147
+ spawn_pos = pos.add(d)
148
+ if ct.can_spawn(spawn_pos):
149
+ ct.spawn_builder(spawn_pos)
150
+ self.num_spawned += 1
151
+ return
152
+
153
+ # ------------------------------------------------------------------
154
+ # Builder bot
155
+ # ------------------------------------------------------------------
156
+
157
+ def _run_builder(self, ct: Controller) -> None:
158
+ """Builder bot logic, executed each round. Priority order:
159
+ 1. Build a harvester if adjacent to uncovered ore
160
+ 2. Build a gunner if we already have enough harvesters
161
+ 3. Heal any damaged friendly buildings nearby
162
+ 4. Move toward the next target (ore or exploration)
163
+ 5. Broadcast any visible ore to teammates via the store
164
+ """
165
+ pos = ct.get_position()
166
+
167
+ # On our first turn, read the core's position from the store.
168
+ # We cache it so we don't have to read every round.
169
+ if self.core_pos is None:
170
+ self._read_core_pos(ct)
171
+
172
+ # Stuck detection: if we haven't moved in 3 rounds, we'll pick a new
173
+ # target in _move_toward_target to avoid getting permanently stuck.
174
+ if self.last_pos == pos:
175
+ self.stuck += 1
176
+ else:
177
+ self.stuck = 0
178
+ self.last_pos = pos
179
+
180
+ # --- Build phase (requires action cooldown == 0) ---
181
+ # Try to build a harvester first; if none available, consider a gunner
182
+ if ct.get_action_cooldown() == 0:
183
+ if not self._try_build_harvester(ct):
184
+ # Only build gunners once our economy is up (enough harvesters)
185
+ harvester_count = ct.read_store(SLOT_HARVESTER_COUNT)
186
+ if harvester_count >= TARGET_HARVESTERS:
187
+ self._try_build_gunner(ct)
188
+
189
+ # If we still have an action left (didn't build anything), heal nearby
190
+ if ct.get_action_cooldown() == 0:
191
+ self._try_heal(ct)
192
+
193
+ # --- Move phase (requires move cooldown == 0) ---
194
+ self._move_toward_target(ct)
195
+
196
+ # --- Communication phase (no cooldown needed) ---
197
+ # Share any visible ore location with teammates
198
+ self._share_ore(ct)
199
+
200
+ def _read_core_pos(self, ct: Controller) -> None:
201
+ """Read the core's position from the communication store.
202
+
203
+ The core writes its position on round 1, so on round 1 these will
204
+ still be 0. We skip storing (0, 0) unless the core really is there.
205
+ """
206
+ x = ct.read_store(SLOT_CORE_X)
207
+ y = ct.read_store(SLOT_CORE_Y)
208
+ if x > 0 or y > 0:
209
+ self.core_pos = Position(x, y)
210
+
211
+ def _try_build_harvester(self, ct: Controller) -> bool:
212
+ """Try to build a harvester on an adjacent ore tile.
213
+
214
+ Harvesters can only be placed on ore tiles (Environment.ORE_TITANIUM).
215
+ can_build_harvester() checks for ore, empty tile, and sufficient resources.
216
+ After building, we also try to place a conveyor next to it to start
217
+ routing the titanium back toward the core.
218
+ """
219
+ ti = ct.get_global_resources()
220
+ cost = ct.get_harvester_cost()
221
+ if ti < cost:
222
+ return False
223
+
224
+ pos = ct.get_position()
225
+ for d in Direction:
226
+ build_pos = pos.add(d)
227
+ if ct.can_build_harvester(build_pos):
228
+ ct.build_harvester(build_pos)
229
+ # Update the shared harvester counter so other bots know
230
+ count = ct.read_store(SLOT_HARVESTER_COUNT)
231
+ ct.write_store(SLOT_HARVESTER_COUNT, count + 1)
232
+ # Try to add a conveyor next to the harvester to route resources
233
+ self._try_build_conveyor_toward_core(ct, build_pos)
234
+ # Clear our target so we move on instead of lingering near this
235
+ # harvester. Without this the bot would try to walk onto the
236
+ # (non-passable) harvester tile for 3 rounds before stuck
237
+ # detection finally kicks in.
238
+ self.target = None
239
+ return True
240
+ return False
241
+
242
+ def _try_build_conveyor_toward_core(self, ct: Controller, harvester_pos: Position) -> None:
243
+ """Place a conveyor adjacent to a harvester, facing toward the core.
244
+
245
+ Harvesters output titanium stacks to adjacent buildings. By placing a
246
+ conveyor on the core-facing side, resources will start flowing in the
247
+ right direction. For a complete supply chain you'd need more conveyors
248
+ linking all the way back to the core.
249
+ """
250
+ if self.core_pos is None:
251
+ return
252
+ ti = ct.get_global_resources()
253
+ cost = ct.get_conveyor_cost()
254
+ if ti < cost:
255
+ return
256
+
257
+ # Figure out which cardinal direction points from the harvester toward the core
258
+ toward_core = harvester_pos.direction_to(self.core_pos)
259
+ if toward_core == Direction.CENTRE:
260
+ return # harvester is right on top of the core (unlikely)
261
+ facing = nearest_cardinal(toward_core)
262
+
263
+ # Place the conveyor one step toward the core from the harvester
264
+ conv_pos = harvester_pos.add(facing)
265
+ if ct.can_build_conveyor(conv_pos, facing):
266
+ ct.build_conveyor(conv_pos, facing)
267
+
268
+ def _try_build_gunner(self, ct: Controller) -> bool:
269
+ """Build a gunner turret on an adjacent tile, facing away from the core.
270
+
271
+ Gunners fire in a straight line in their facing direction, so pointing
272
+ them outward from the core gives them the best chance of hitting
273
+ approaching enemies. We only build if we're close to the core
274
+ (within ~4 tiles) so gunners end up defending the base.
275
+
276
+ Note: gunners need ammo delivered via conveyors to fire. This starter
277
+ bot doesn't set up ammo supply — that's an exercise for the player!
278
+ """
279
+ ti = ct.get_global_resources()
280
+ cost = ct.get_gunner_cost()
281
+ if ti < cost:
282
+ return False
283
+
284
+ pos = ct.get_position()
285
+ # Only build gunners when we're reasonably close to the core
286
+ if self.core_pos is None or pos.distance_squared(self.core_pos) > 18:
287
+ return False
288
+
289
+ # Face the gunner away from the core (toward incoming enemies)
290
+ facing = pos.direction_to(self.core_pos).opposite()
291
+ if facing == Direction.CENTRE:
292
+ facing = random.choice(DIRECTIONS)
293
+
294
+ # Try each adjacent tile for a valid gunner placement
295
+ for d in Direction:
296
+ build_pos = pos.add(d)
297
+ if ct.can_build_gunner(build_pos, facing):
298
+ ct.build_gunner(build_pos, facing)
299
+ return True
300
+ return False
301
+
302
+ def _try_heal(self, ct: Controller) -> None:
303
+ """Heal a damaged friendly building or bot on an adjacent tile.
304
+
305
+ Healing costs 1 titanium and restores 4 HP. can_heal() checks that
306
+ there's actually a damaged friendly entity on the tile.
307
+ """
308
+ pos = ct.get_position()
309
+ for d in Direction:
310
+ check = pos.add(d)
311
+ if ct.can_heal(check):
312
+ ct.heal(check)
313
+ return
314
+
315
+ # ------------------------------------------------------------------
316
+ # Builder bot — movement
317
+ # ------------------------------------------------------------------
318
+
319
+ def _move_toward_target(self, ct: Controller) -> None:
320
+ """Navigate toward our current target, picking a new one if needed.
321
+
322
+ Target priority: visible ore > ore shared via store > random position.
323
+ If we've been stuck for 3+ rounds, we give up on the current target
324
+ and pick a fresh one.
325
+ """
326
+ if ct.get_move_cooldown() != 0:
327
+ return
328
+
329
+ pos = ct.get_position()
330
+
331
+ # Pick a new target if we don't have one, reached it, or are stuck
332
+ if self.target is None or pos == self.target or self.stuck >= 3:
333
+ self.target = self._pick_target(ct)
334
+ self.stuck = 0
335
+ if self.target is None:
336
+ return
337
+
338
+ # Get the direction toward our target
339
+ desired = pos.direction_to(self.target)
340
+ if desired == Direction.CENTRE:
341
+ return
342
+
343
+ # Try the ideal direction first, then slightly left/right of it.
344
+ # If all three fail (e.g. boxed in by harvesters), try every remaining
345
+ # direction so the bot can escape instead of sitting stuck for 3 rounds.
346
+ primary = [desired, desired.rotate_left(), desired.rotate_right()]
347
+ for d in primary:
348
+ if self._try_move(ct, d):
349
+ return
350
+ remaining = [d for d in DIRECTIONS if d not in primary]
351
+ random.shuffle(remaining)
352
+ for d in remaining:
353
+ if self._try_move(ct, d):
354
+ return
355
+
356
+ def _try_move(self, ct: Controller, d: Direction) -> bool:
357
+ """Try to move one step in direction d, laying conveyor infra along the way.
358
+
359
+ Builder bots can walk on any tile that isn't a wall or occupied by another
360
+ builder bot. If the destination is empty and we know where our core is, we
361
+ lay a conveyor pointing toward it so it doubles as part of the resource
362
+ network -- this is optional now, not required for movement.
363
+ """
364
+ if d == Direction.CENTRE:
365
+ return False
366
+ pos = ct.get_position()
367
+ next_pos = pos.add(d)
368
+
369
+ if ct.is_tile_empty(next_pos) and self.core_pos is not None:
370
+ toward_core = next_pos.direction_to(self.core_pos)
371
+ cardinal = nearest_cardinal(toward_core)
372
+ if ct.can_build_conveyor(next_pos, cardinal):
373
+ ct.build_conveyor(next_pos, cardinal)
374
+
375
+ if ct.can_move(d):
376
+ ct.move(d)
377
+ return True
378
+ return False
379
+
380
+ def _pick_target(self, ct: Controller) -> Position:
381
+ """Choose the next position to navigate toward.
382
+
383
+ Priority:
384
+ 1. Closest visible ore tile without a harvester on it — go build one
385
+ 2. If economy is established (enough harvesters), head back toward
386
+ the core so we can build gunners for defense
387
+ 3. Ore location shared by a teammate via the store — head that way
388
+ 4. Random map position — pure exploration
389
+ """
390
+ pos = ct.get_position()
391
+
392
+ # 1. Scan visible tiles for the nearest uncovered ore
393
+ best_ore = None
394
+ best_dist = float("inf")
395
+ for tile in ct.get_nearby_tiles():
396
+ if ct.get_tile_env(tile) != Environment.ORE_TITANIUM:
397
+ continue
398
+ # Skip ore that already has a building (harvester or otherwise) on it
399
+ if ct.get_tile_building_id(tile) is not None:
400
+ continue
401
+ d = pos.distance_squared(tile)
402
+ if d < best_dist:
403
+ best_dist = d
404
+ best_ore = tile
405
+ if best_ore is not None:
406
+ return best_ore
407
+
408
+ # 2. Once we have enough harvesters, head back to the core so we can
409
+ # build gunners nearby. _try_build_gunner requires being within
410
+ # distance² 18 of the core, so we navigate there.
411
+ harvester_count = ct.read_store(SLOT_HARVESTER_COUNT)
412
+ if harvester_count >= TARGET_HARVESTERS and self.core_pos is not None:
413
+ if pos.distance_squared(self.core_pos) > 8:
414
+ return self.core_pos
415
+
416
+ # 3. Check the store for an ore location shared by a teammate
417
+ shared = unpack_pos(ct.read_store(SLOT_ORE_LOCATION))
418
+ if shared is not None and pos.distance_squared(shared) > 4:
419
+ return shared
420
+
421
+ # 4. No known ore — pick a random position to explore
422
+ w, h = ct.get_map_width(), ct.get_map_height()
423
+ return Position(random.randrange(w), random.randrange(h))
424
+
425
+ def _share_ore(self, ct: Controller) -> None:
426
+ """Broadcast a visible uncovered ore tile to teammates via the store.
427
+
428
+ Any builder bot that sees ore without a building on it writes the
429
+ location to SLOT_ORE_LOCATION. Other bots can read this to navigate
430
+ toward ore they haven't seen yet.
431
+ """
432
+ pos = ct.get_position()
433
+ for tile in ct.get_nearby_tiles():
434
+ if ct.get_tile_env(tile) == Environment.ORE_TITANIUM:
435
+ if ct.get_tile_building_id(tile) is None:
436
+ ct.write_store(SLOT_ORE_LOCATION, pack_pos(tile))
437
+ return
438
+
439
+ # ------------------------------------------------------------------
440
+ # Gunner
441
+ # ------------------------------------------------------------------
442
+
443
+ def _run_gunner(self, ct: Controller) -> None:
444
+ """Gunner logic: fire at the first enemy in our line of sight.
445
+
446
+ get_gunner_target() returns the closest entity along the gunner's
447
+ facing direction, or None if the line is clear. We only fire if
448
+ can_fire() confirms we have ammo and cooldown is ready.
449
+ """
450
+ target = ct.get_gunner_target()
451
+ if target is not None and ct.can_fire(target):
452
+ ct.fire(target)
@@ -0,0 +1,9 @@
1
+ [Dolphin]
2
+ PreviewsShown=false
3
+ Timestamp=2026,3,13,20,23,23.118
4
+ Version=4
5
+ ViewMode=1
6
+ VisibleRoles=Details_text,Details_type,Details_size,Details_creationtime,Details_modificationtime,CustomizedDetails
7
+
8
+ [Settings]
9
+ HiddenFilesShown=true
Binary file
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:root,:host{--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-green-400:oklch(79.2% .209 151.711);--color-green-600:oklch(62.7% .194 149.214);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-gray-200:oklch(92.8% .006 264.531);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--animate-spin:spin 1s linear infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"HKGrotesk", monospace;--default-mono-font-family:"HKGrotesk", monospace}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{box-sizing:border-box}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:HKGrotesk,monospace;font-size:13px}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.inset-\[9px\]{inset:9px}.inset-\[22px\]{inset:22px}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing) * 3)}.top-full{top:100%}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-1\/2{left:50%}.left-3{left:calc(var(--spacing) * 3)}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing) * 2)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-\[0\.15rem\]{margin-top:.15rem}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.aspect-square{aspect-ratio:1}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-12{height:calc(var(--spacing) * 12)}.h-24{height:calc(var(--spacing) * 24)}.h-\[6px\]{height:6px}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-\[30vh\]{max-height:30vh}.max-h-\[40vh\]{max-height:40vh}.max-h-full{max-height:100%}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[22rem\]{min-height:22rem}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-58{width:calc(var(--spacing) * 58)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-\[min\(90vw\,48rem\)\]{width:min(90vw,48rem)}.w-fit{width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.max-w-\[3rem\]{max-width:3rem}.max-w-\[4rem\]{max-width:4rem}.max-w-\[40\%\]{max-width:40%}.max-w-\[55\%\]{max-width:55%}.max-w-full{max-width:100%}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[4rem\]{min-width:4rem}.min-w-\[32rem\]{min-width:32rem}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-\[calc\(-50\%-2px\)\]{--tw-translate-y: calc(-50% - 2px) ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-45{rotate:45deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.resize{resize:both}.\[appearance\:none\]{appearance:none}.\[appearance\:textfield\]{appearance:textfield}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\[1fr_2rem_2rem\]{grid-template-columns:1fr 2rem 2rem}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.grid-cols-\[minmax\(0\,1fr\)_2\.75rem_2\.75rem\]{grid-template-columns:minmax(0,1fr) 2.75rem 2.75rem}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-none{overscroll-behavior:none}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:calc(var(--radius) + 8px)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[\#6eaaff\]\/30{border-color:#6eaaff4d}.border-\[\#7a6a50\]{border-color:#7a6a50}.border-\[\#ffbf40\]{border-color:#ffbf40}.border-\[\#ffbf40\]\/30{border-color:#ffbf404d}.border-\[\#ffbf40\]\/40{border-color:#ffbf4066}.border-\[var\(--vis-border\)\]{border-color:var(--vis-border)}.border-\[var\(--vis-border-2\)\]{border-color:var(--vis-border-2)}.border-black\/20{border-color:#0003}@supports (color:color-mix(in lab,red,red)){.border-black\/20{border-color:color-mix(in oklab,var(--color-black) 20%,transparent)}}.border-transparent{border-color:#0000}.bg-\[\#ffbf40\]{background-color:#ffbf40}.bg-\[\#ffbf40\]\/15{background-color:#ffbf4026}.bg-\[var\(--vis-bg\)\]{background-color:var(--vis-bg)}.bg-\[var\(--vis-bg-badge\)\]{background-color:var(--vis-bg-badge)}.bg-\[var\(--vis-bg-card\)\]{background-color:var(--vis-bg-card)}.bg-\[var\(--vis-bg-code\)\]{background-color:var(--vis-bg-code)}.bg-\[var\(--vis-bg-elevated\)\]{background-color:var(--vis-bg-elevated)}.bg-\[var\(--vis-bg-panel\)\]{background-color:var(--vis-bg-panel)}.bg-\[var\(--vis-bg-panel-80\)\]{background-color:var(--vis-bg-panel-80)}.bg-\[var\(--vis-bg-winner\)\]{background-color:var(--vis-bg-winner)}.bg-\[var\(--vis-border\)\]{background-color:var(--vis-border)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab,red,red)){.bg-black\/40{background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-red-100{background-color:var(--color-red-100)}.bg-transparent{background-color:#0000}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-1{padding-left:calc(var(--spacing) * 1)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:HKGrotesk,monospace}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#6eaaff\]{color:#6eaaff}.text-\[\#ff6b6b\]{color:#ff6b6b}.text-\[\#ffbf40\]{color:#ffbf40}.text-\[var\(--vis-text-1\)\]{color:var(--vis-text-1)}.text-\[var\(--vis-text-2\)\]{color:var(--vis-text-2)}.text-\[var\(--vis-text-3\)\]{color:var(--vis-text-3)}.text-\[var\(--vis-text-4\)\]{color:var(--vis-text-4)}.text-black{color:var(--color-black)}.text-green-600{color:var(--color-green-600)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-white\/70{color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.text-white\/70{color:color-mix(in oklab,var(--color-white) 70%,transparent)}}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.placeholder-\[var\(--vis-text-4\)\]::placeholder{color:var(--vis-text-4)}.accent-\[\#ffbf40\]{accent-color:#ffbf40}.opacity-25{opacity:.25}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black\/30{--tw-shadow-color:#0000004d}@supports (color:color-mix(in lab,red,red)){.shadow-black\/30{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 30%, transparent) var(--tw-shadow-alpha), transparent)}}.ring-\[\#ffbf40\]{--tw-ring-color:#ffbf40}.ring-\[\#ffbf40\]\/30{--tw-ring-color:oklab(84.2534% .0257247 .151766/.3)}.ring-\[\#ffbf40\]\/40{--tw-ring-color:oklab(84.2534% .0257247 .151766/.4)}.ring-\[\#ffbf40\]\/70{--tw-ring-color:oklab(84.2534% .0257247 .151766/.7)}.ring-black\/10{--tw-ring-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.ring-black\/10{--tw-ring-color:color-mix(in oklab, var(--color-black) 10%, transparent)}}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hover\:border-\[\#ffbf40\]:hover{border-color:#ffbf40}.hover\:border-\[\#ffbf40\]\/30:hover{border-color:#ffbf404d}.hover\:bg-\[\#ffa800\]:hover{background-color:#ffa800}.hover\:bg-\[var\(--vis-bg-elevated\)\]:hover{background-color:var(--vis-bg-elevated)}.hover\:bg-\[var\(--vis-bg-elevated-hover\)\]:hover{background-color:var(--vis-bg-elevated-hover)}.hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:text-\[\#8ec2ff\]:hover{color:#8ec2ff}.hover\:text-\[\#ffa800\]:hover{color:#ffa800}.hover\:text-\[\#ffbf40\]:hover{color:#ffbf40}.hover\:text-\[var\(--vis-text-1\)\]:hover{color:var(--vis-text-1)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-white:hover{color:var(--color-white)}.hover\:ring-1:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-\[\#ffbf40\]\/50:hover{--tw-ring-color:oklab(84.2534% .0257247 .151766/.5)}}.focus\:border-\[\#ffbf40\]:focus{border-color:#ffbf40}.focus\:border-\[\#ffbf40\]\/50:focus{border-color:#ffbf4080}.focus\:ring-\[\#ffbf40\]:focus{--tw-ring-color:#ffbf40}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media(hover:hover){.disabled\:hover\:bg-\[var\(--vis-bg-elevated\)\]:disabled:hover{background-color:var(--vis-bg-elevated)}.disabled\:hover\:text-\[var\(--vis-text-2\)\]:disabled:hover{color:var(--vis-text-2)}.disabled\:hover\:ring-0:disabled:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.data-\[side\=bottom\]\:top-1[data-side=bottom]{top:calc(var(--spacing) * 1)}.data-\[side\=inline-end\]\:top-1\/2\![data-side=inline-end]{top:50%!important}.data-\[side\=inline-end\]\:-left-1[data-side=inline-end]{left:calc(var(--spacing) * -1)}.data-\[side\=inline-end\]\:-translate-y-1\/2[data-side=inline-end]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=inline-start\]\:top-1\/2\![data-side=inline-start]{top:50%!important}.data-\[side\=inline-start\]\:-right-1[data-side=inline-start]{right:calc(var(--spacing) * -1)}.data-\[side\=inline-start\]\:-translate-y-1\/2[data-side=inline-start]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=left\]\:top-1\/2\![data-side=left]{top:50%!important}.data-\[side\=left\]\:-right-1[data-side=left]{right:calc(var(--spacing) * -1)}.data-\[side\=left\]\:-translate-y-1\/2[data-side=left]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=right\]\:top-1\/2\![data-side=right]{top:50%!important}.data-\[side\=right\]\:-left-1[data-side=right]{left:calc(var(--spacing) * -1)}.data-\[side\=right\]\:-translate-y-1\/2[data-side=right]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=top\]\:-bottom-2\.5[data-side=top]{bottom:calc(var(--spacing) * -2.5)}@media(min-width:40rem){.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:pb-3{padding-bottom:calc(var(--spacing) * 3)}.sm\:pb-6{padding-bottom:calc(var(--spacing) * 6)}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media(min-width:48rem){.md\:order-none{order:0}.md\:w-auto{width:auto}.md\:min-w-0{min-width:calc(var(--spacing) * 0)}.md\:flex-1{flex:1}.md\:justify-start{justify-content:flex-start}}@media(min-width:64rem){.lg\:h-full{height:100%}.lg\:overflow-y-auto{overflow-y:auto}}@media(min-width:80rem){.xl\:gap-4{gap:calc(var(--spacing) * 4)}.xl\:px-6{padding-inline:calc(var(--spacing) * 6)}}.dark\:border-white\/20:is(.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.dark\:border-white\/20:is(.dark *){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.dark\:bg-\[\#0a0a0a\]:is(.dark *){background-color:#0a0a0a}.dark\:bg-\[\#0d0d0d\]:is(.dark *){background-color:#0d0d0d}.dark\:bg-\[\#1c1c1c\]:is(.dark *){background-color:#1c1c1c}.dark\:bg-\[\#1c1c1c\]\/40:is(.dark *){background-color:#1c1c1c66}.dark\:bg-\[\#1c1c1c\]\/50:is(.dark *){background-color:#1c1c1c80}.dark\:bg-\[\#2a2a2a\]:is(.dark *){background-color:#2a2a2a}.dark\:bg-\[\#3a1520\]:is(.dark *){background-color:#3a1520}.dark\:bg-\[\#111111\]:is(.dark *){background-color:#111}.dark\:bg-\[\#182032\]:is(.dark *){background-color:#182032}.dark\:text-\[\#484848\]:is(.dark *){color:#484848}.dark\:text-\[\#ff6b6b\]:is(.dark *){color:#ff6b6b}.dark\:text-green-400:is(.dark *){color:var(--color-green-400)}.dark\:text-red-400:is(.dark *){color:var(--color-red-400)}.dark\:ring-white\/15:is(.dark *){--tw-ring-color:#ffffff26}@supports (color:color-mix(in lab,red,red)){.dark\:ring-white\/15:is(.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}@media(hover:hover){.dark\:hover\:bg-\[\#1c1c1c\]:is(.dark *):hover{background-color:#1c1c1c}.dark\:hover\:bg-\[\#1c1c1c\]\/50:is(.dark *):hover{background-color:#1c1c1c80}.dark\:hover\:bg-\[\#1d2740\]:is(.dark *):hover{background-color:#1d2740}.dark\:hover\:bg-\[\#4a1a28\]:is(.dark *):hover{background-color:#4a1a28}.dark\:hover\:bg-\[\#262626\]:is(.dark *):hover{background-color:#262626}.dark\:hover\:text-\[\#ff8888\]:is(.dark *):hover{color:#f88}}.\[\&\:\:-moz-range-thumb\]\:h-3::-moz-range-thumb{height:calc(var(--spacing) * 3)}.\[\&\:\:-moz-range-thumb\]\:w-3::-moz-range-thumb{width:calc(var(--spacing) * 3)}.\[\&\:\:-moz-range-thumb\]\:rounded-full::-moz-range-thumb{border-radius:3.40282e38px}.\[\&\:\:-moz-range-thumb\]\:border-0::-moz-range-thumb{border-style:var(--tw-border-style);border-width:0}.\[\&\:\:-moz-range-thumb\]\:bg-\[\#ffbf40\]::-moz-range-thumb{background-color:#ffbf40}.\[\&\:\:-webkit-inner-spin-button\]\:appearance-none::-webkit-inner-spin-button{appearance:none}.\[\&\:\:-webkit-outer-spin-button\]\:appearance-none::-webkit-outer-spin-button{appearance:none}.\[\&\:\:-webkit-slider-thumb\]\:h-3::-webkit-slider-thumb{height:calc(var(--spacing) * 3)}.\[\&\:\:-webkit-slider-thumb\]\:w-3::-webkit-slider-thumb{width:calc(var(--spacing) * 3)}.\[\&\:\:-webkit-slider-thumb\]\:cursor-pointer::-webkit-slider-thumb{cursor:pointer}.\[\&\:\:-webkit-slider-thumb\]\:\[appearance\:none\]::-webkit-slider-thumb{appearance:none}.\[\&\:\:-webkit-slider-thumb\]\:rounded-full::-webkit-slider-thumb{border-radius:3.40282e38px}.\[\&\:\:-webkit-slider-thumb\]\:bg-\[\#ffbf40\]::-webkit-slider-thumb{background-color:#ffbf40}}@font-face{font-family:HKGrotesk;src:url(/fonts/HKGrotesk-Regular.otf)format("opentype");font-weight:400;font-style:normal;font-display:swap}:root{--radius:0rem}#root{width:100%;height:100dvh}body,#root,main{min-width:0;max-width:100vw;overflow-x:hidden}main>*{min-width:0}#root canvas{max-width:100%;max-height:100%;display:block;width:100%!important;height:100%!important}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}