Compare commits

...
Sign in to create a new pull request.

41 commits

Author SHA1 Message Date
28591233f3 ss 2026-08-08 15:31:31 +09:00
091afc2b5c asdfg 2026-07-29 20:52:11 +09:00
f6d7412744 a 2026-07-20 14:36:59 +09:00
d163ceb291 e 2026-07-18 22:15:26 +09:00
f657b6a4a4 y 2026-07-18 13:06:02 +09:00
61718e2981 h 2026-07-17 12:25:23 +09:00
2cc2f003df 1.9 2026-07-16 22:12:03 +09:00
fc7c94bd69 1.8 2026-07-15 16:27:56 +09:00
63e10af865 p 2026-07-15 14:44:29 +09:00
d14550dad6 rr 2026-07-11 21:13:39 +09:00
2636d580a8 aaa 2026-07-10 16:40:06 +09:00
f1c3af2b89 2026-07-09 16:08:11 +09:00
8f9f5b5100 duu 2026-07-05 18:01:36 +09:00
88f618cadf 1.6 2026-07-03 20:57:02 +09:00
ee22e1a929 qqq 2026-07-03 00:45:22 +09:00
acf93ec6df 1.5 2026-07-01 22:59:49 +09:00
c9dd7182aa hm 2026-07-01 16:55:49 +09:00
4d3fc2cd47 hm 2026-07-01 14:41:05 +09:00
7455d630fc 1.4 2026-06-30 22:30:37 +09:00
ad2cc76429 aa 2026-06-30 13:40:36 +09:00
f941200504 h 2026-06-30 10:57:06 +09:00
e00bfd8879 too 2026-06-29 21:02:04 +09:00
4fdc567e08 removed unused processesseessess 2026-06-29 16:21:58 +09:00
348f355eb2 a 2026-06-28 23:34:08 +09:00
0e432c62ae hm 2026-06-28 23:07:40 +09:00
cefa2ace5a v1.2 2026-06-28 16:59:53 +09:00
f500279abd -- 2026-06-28 13:40:41 +09:00
5ed977788c hm 2026-06-27 21:50:05 +09:00
129c07183a stable 2026-06-27 19:22:38 +09:00
0f1cb2fe6c published 2026-06-26 22:35:26 +09:00
077d4ab9ac stable 2026-06-26 19:04:32 +09:00
86226ccc94 stable 2026-06-26 12:46:32 +09:00
fbdac9ebf1 not baaad 2026-06-25 22:30:35 +09:00
16074232aa hhmm 2026-06-25 17:43:24 +09:00
434f07cc4e not bad 2026-06-25 14:51:58 +09:00
2741b93dea lag r 2026-06-24 21:20:53 +09:00
d875df8600 hmm? 2026-06-24 18:49:17 +09:00
9ef1b8dbb1 hmm 2026-06-24 17:39:44 +09:00
d5c661bc22 stable 2026-06-24 13:48:22 +09:00
8bd4405fe8 desire 2026-06-23 22:39:20 +09:00
54aff02658 hmm 2026-06-23 18:11:42 +09:00
283 changed files with 52675 additions and 9160 deletions

View file

View file

@ -0,0 +1,6 @@
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule mod_access_compat.c>
Deny from all
</IfModule>

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,5 @@
AddType application/manifest+json .webmanifest
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 0 seconds"
@ -8,22 +10,35 @@
ExpiresByType image/webp "access plus 30 days"
ExpiresByType audio/mpeg "access plus 30 days"
ExpiresByType audio/wav "access plus 30 days"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "achievement_api\.php$">
Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
Header set Pragma "no-cache"
Header set Expires "0"
</FilesMatch>
<FilesMatch "\.html?$">
Header set Cache-Control "no-cache, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</FilesMatch>
<FilesMatch "\.(css|js|json|webmanifest|ico|png|webp|mp3|wav)$">
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
<FilesMatch "service-worker\.js$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</FilesMatch>
<FilesMatch "\.(css|js|json|png|webp|mp3|wav)$">
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json text/plain
</IfModule>
# Never serve hidden server-data paths.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule (^|/)\. - [R=404,L]
</IfModule>

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}

View file

@ -1,123 +0,0 @@
# Patch notes
## 15.20.16
- Added canonical item visual, food, and disease registries; wired food decay/visual helpers and mystery-drug disease sampling to registry data.
- Generated the tool palette from `TOOL_CATEGORIES` at runtime and removed duplicated category markup from `index.html`.
- Added compact family-tree personality tags, daily personality-change details, and selected-Tarinai rolling recent-change causes for personality, damage, item-effect, disease, and ant-carry changes.
- Added `InputModeManager` for touch-first mobile classification and `WeatherSystem` for weather transitions/rain-water spawning.
- Added central domain events for placement, eating, decay, effects, diseases, damage, death, birth, fights, ants, weather, and rain item drops.
- Moved item-effect modifiers, active summaries, apply/tick behavior callbacks, and food interest class queries into registries.
- Moved explosion-disease base infection chance reads into `DiseaseRegistry` while preserving protein/niterop disease multipliers.
- Moved serving-food nutrition and hunger-relief reads into `FoodRegistry`, including laxative's zero hunger relief.
- Routed direct love/fight/sleep item eating through `EffectRegistry` while preserving the previous bite-specific stress, fear, and sleep-disease chance behavior.
- Routed sleep drug, rain-water item, and water-bowl field visuals through `item_visual_registry.js`; added water-bowl icon-generation support.
- Moved canonical sound gains, sample paths, categories, cooldowns, and ID category mapping out of `audio.js` into `sound_pack.js`.
- Added named ant ecology extension hooks/comments without changing ant balance.
- Bumped cache/version references to `15.20.16`; documented the new source-of-truth rules.
## 15.20.12
- Updated documentation after inspecting the current game surface: field choices, startup flow, tool groups, chart/family/debug surfaces, localStorage settings, and validation fallback when Node is unavailable.
- Removed unused `css/styles.css` shim and no-op `js/ui_family.js` loader entry; refactored duplicate world initialization and allocation-heavy live-count paths.
- Removed the unwanted item-effect speech bubbles listed in `docs/prompt.txt`.
- Reorganized the tool palette into color-coded collapsible groups: operation, food/water/grass, medicine/effects, habitat/living things, hazards/obstacles.
- Changed the tool UI ball icon to a soccer-ball emoji preview.
- Changed time phases: morning 05:00, day 10:00, evening 17:00, night 20:00.
- Increased genkotsu knockback for Tarinai and ants; genkotsu now also blasts zunchi away.
- Genkotsu damage now adds fear/stress and decreases Tarinai openness.
- Rain weather overlay is now diagonal streaks. Fallen rain-water items, zunda juice, and mercury remain horizontal oval item sprites.
- Tightened mobile operation UI visibility to smartphone-class compact touch devices.
- Sleep-disease Tarinai can be moved by ant hauling without snapping back to their sleep anchor.
## 15.20.12 and earlier summary
- Consolidated documentation into dense English files; left `docs/prompt.txt` untouched.
- Restored rain, revised water/zunda/mercury item sprites, added zunchi-slave head zunchi, fight win rate, fight defeat chance, and post-wake lock.
- Earlier patches added parameter items, persistent item effects, WebP assets, sound categories, push notifications, ant systems, family tree UI, performance manager, event bus, manifest generation, CSS split, and large-file JS split.
## Debug patch: panic chain and action reasons
- Removed the friend-only panic chain that made only a best friend follow a panic target.
- Added general nearby panic contagion: any nearby live tarinai in panic can cause another tarinai to panic. Neuroticism, distance, and current stress affect the probability.
- Added / completed visible action reasons for:
- eating all food and effect items
- eating grass, ant corpses, and zunchi
- drinking / touching water
- resting near stone, bed, and nest box
- sunbathing
- zunchi-slave avoidance
- being pinched / dragged
- firecracker blast panic
- explosion-disease blast panic
- falling object / stone / genkotsu impact
- high-speed ball collision
- high-speed tarinai collision
- intimidation, intimidation escape, fights, birth ritual, and thaw surprise
- fence-blocked path give-up
- Added state labels for `sunbath`, `ant_attack`, `ant_intimidate`, and `frozen`.
- Expanded target labels for all current tool/item target types, including medicine/effect items, ant corpses, ant nests, firecrackers, pushpins, signboards, and fences.
- Verification performed:
- `node --check` over every `js/*.js`
- state-label coverage scan: no uncovered tarinai states
- old friend-panic chain scan: zero matches
## おしり鋲・冷凍操作変更パッチ
- ずんちどれい化条件の喧嘩勝率を30%以下に変更。
- 衰弱死因判定を厳格化。
- 冷凍手段をクリック式から「つまむで冷凍UIへ運ぶ」方式へ変更。
- 新アイテム「おしり鋲」を追加。刺さるとずんちを溜め、日光浴不可。抜くと溜まったずんちが高速で飛散。
- 高速ずんち衝突時の吹き飛び・パニック理由文を追加。
## 15.22.0 統合リファクタ
- `snapshot_system.js` を追加し、セーブ/ロードと冷凍で重複していた `clonePlain` / `copyOwnData` / `applyOwnData` / 参照復元 / スナップショット作成・復元を共通化。
- `tarinai_action_state.js` を追加し、`setActionState()` / `enterPanic()` / `beginEating()` により、状態変更・パニック化・食事開始の理由文設定を統合。
- 直接 `state = "panic"` していた処理を `enterPanic()` 経由へ変更。爆竹、爆発病、高速ボール、高速衝突、アリ、画鋲、近傍パニック連鎖、関係者死亡、解凍時の訃報などが対象。
- 直接 `state = "eat"` していた食事処理を `beginEating()` 経由へ変更。
- `physics_helpers.js` を追加し、壁反射・摩擦・回転を `TarinaiPhysics.applyKinematicItemMotion()` に共通化。おしり鋲/画鋲/高速ずんちの移動処理に適用。
- `ITEM_TRAITS``PIN_BEHAVIORS` を追加し、アイテム分類と画鋲/おしり鋲の差分挙動をテーブル化。
- `text_catalog.js` に状態ラベル、対象ラベル、理由文補完を集約。`ui_selected.js` はカタログ参照へ変更。
- `family_graph.js` を追加し、家系グラフの連結成分探索を共通化。同期家系データと描画側に適用。
- `relation_event_system.js` を追加し、関係者死亡の即時反応と冷凍個体への遅延通知を共通化。
- `ui_helpers.js` を追加し、HTMLエスケープ等のUI共通処理を切り出し。
- 構文チェック: 全 `js/*.js` OK。
- 重複確認: `clonePlain` / `copyOwnData` / `applyOwnData``snapshot_system.js` に集約、直接 `state = "panic"` と直接 `state = "eat"` は除去済み。
## 15.23.0 safe duplicate integration pass
- `registry_base.js` を追加し、`food_registry.js` / `effect_registry.js` / `disease_registry.js``get` / `has` / `label` / `color` / `debugSnapshot` 系の共通基盤を統合。
- `patch_helpers.js` を追加し、後付けの `World.prototype` パッチを `TarinaiPatcher.patchWorld()` 経由に統合。冷凍・関係者死亡通知で利用。
- `data.js``sleepFurniture` / `draggable` trait と、`isPinType()` / `isSleepFurnitureType()` / `lodgedPinFor()` / `hasLodgedPinEffect()` / `normalizedToolSizeFor()` / `toolSizeScaleFor()` を追加。
- `ui_tools.js``world_view.js` のツールサイズ算出を `normalizedToolSizeFor()` / `toolSizeScaleFor()` に統合。
- `tarinai_action_state.js``currentLodgedPin()` / `currentLodgedPinBehavior()` / `hasLodgedPinEffect()` / `goIdle()` / `startSleeping()` / `seekTarget()` を追加。
- `blockedByOshibyo` 系の重複判定を `hasLodgedPinEffect("blocksZunchi")` に統合。
- 画鋲/おしり鋲の型判定を `isPinType()` に寄せ、描画・配置・つまみ・削除判定の分岐を整理。
- 睡眠家具判定を `isSleepFurnitureType()` に寄せ、ベッド/巣箱の直書き判定を削減。
- 手動アリ近傍探索の一部を `world.nearbyAnts()` に寄せた。
- `pruneExtinctFamilies()``TarinaiFamilyGraph.connectedComponents()` に寄せた。`family_graph.js` 側のデフォルト edge も親子参照を使うようにした。
- `save_system.js` / `freeze_system.js` / `ui_selected.js` のHTMLエスケープを `TarinaiUIHelpers.htmlEscape()` に統合。
- `assets.js` のスプライト/汚れオーバーレイ用キャンバス生成を `TarinaiPhysics.createCachedCanvas()` に寄せた。
- 危険度の高い `render.js` 全体再設計、`Tarinai.update()` の状態機械化、`ants.js` の状態機械化、`items.js` 描画全体のレジストリ化は未実施。
- 確認: 全 `js/*.js``node --check` 通過。
### 15.23.0 追確認
- `service-worker.js` / `app_manifest.json` のスクリプト一覧を `index.html` の実ロード順に同期。新規共通化ファイルもキャッシュ対象に追加。
- アリ運搬中に寝ているたりないの状態変更も `startSleeping()` 経由へ寄せた。
- 最終確認: 全 `js/*.js``service-worker.js``node --check` 通過。
- 最終確認: `blockedByOshibyo``type === "oshibyo"``type === "pushpin"` の直接判定は 0件。`clonePlain` / `copyOwnData` / `applyOwnData` / `isPlainObject``snapshot_system.js` のみ。
## 15.24.0 safe integration / UI escape recursion fix
- Fixed `Maximum call stack size exceeded` caused by `ui_helpers.htmlEscape()` calling the global `escapeHtml()` while `ui_selected.escapeHtml()` called back into `TarinaiUIHelpers.htmlEscape()`.
- Made `TarinaiUIHelpers.htmlEscape()` a pure escaping function and loaded `ui_helpers.js` before `ui_selected.js`.
- Added a guard in `ui_selected.escapeHtml()` so it never delegates to itself.
- Added a data URL favicon link to stop the browser's default `/favicon.ico` 404 request.
- Bumped static asset version to `15.24.0` in `app_manifest.json`, `index.html`, `service-worker.js`, and `js/version.js` to avoid stale cached scripts.
- Reduced target-label duplication: item labels now come from `TOOL_DEFINITIONS` / `toolLabel()`, with `text_catalog.js` holding only non-tool overrides.
- Removed the remaining synchronous family-tree DFS fallback blocks in `ui_family_data.js` and `ui_family_render.js`; both now use `TarinaiFamilyGraph.connectedComponents()`.
- Simplified the remaining manual ant proximity fallback in `tarinai_needs_items.js` to `world.nearbyAnts()`.
- Verification: all `js/*.js` and `service-worker.js` pass `node --check`.

View file

@ -1,51 +1,60 @@
# Tarinai Colony Observation Game
# たりない観察 v39.18.6
Buildless single-page browser simulation. Open `index.html`; no package manager, transpiler, server API, or module loader is required. Runtime order is explicit `<script defer>` order generated from `app_manifest.json`. Current inspected version: `15.20.16`.
## 実績システム
## Edit contract
実績定義は `js/achievement_catalog.js` を唯一のカタログとして管理します。表示名、説明、カテゴリ、未解除時の公開設定、同期可能な進捗条件、累積上限は、クライアントと `achievement_api.php` が同じカタログから読み取ります。
- Preserve Japanese in-game UI tone and strings.
- Do not edit `docs/prompt.txt`; it is external user material.
- Use `app_manifest.json` as the source of truth for CSS/JS load order and service-worker cache entries.
- Run `python3 scripts/generate_app_files.py` after version or asset-list changes.
- Run `node --check js/*.js service-worker.js` before zipping.
- Verify ZIP integrity with `zip -T`.
累積進捗の正規データは端末・タブ別の増加台帳です。表示用 `progress` に同じ累積値を重複保存しません。旧形式の重複カウンターと `synced` 一覧は読込時だけ移行し、現行形式では保存しません。
## Documentation map
「敵の敵は味方」は実績画面の「生態」カテゴリに掲載されます。
- `docs/ARCHITECTURE.md`: runtime architecture, file ownership, system boundaries.
- `docs/CONTENT_GUIDE.md`: item visuals, UI/field visual parity, audio, bubble text inventory.
- `docs/MAINTENANCE.md`: release process, validation, debugging, cache/version rules.
- `docs/prompt.txt`: untouched user prompt/context archive.
## ローカル保存
## Player-facing surface
実績状態とサーバー資格情報は、通常保存、予備保存、同一タブ保存、IndexedDBへ世代付きで保存します。起動時は同じ世代の状態を追加方向に統合し、明示的な実績リセット時だけ世代を進めます。
- Startup uses a loading overlay that eagerly preloads a small sprite set, initializes UI/world state, then defers remaining image work.
- Field choices are cage, garden, and park. `FIELD_TYPES` controls world scale, starting population, initial grass count, and responsive field height.
- The default field is garden. Reset opens the field dialog; cancel keeps the current field.
- Tools are grouped in collapsible categories: operation/observation, food/water/grass, medicine/effects, habitat/living things, hazards/obstacles.
- The top bar owns pause, speed, ecology, sound, credits, and reset controls. Event push toasts live inside the Event card, while sound category toggles live only in the sound menu.
- The colony panel charts population, mental state, environment, and object counts. The family tree is a separate main section with manual/auto refresh and reset controls.
- Mobile operation modes are available only on compact touch-first phone viewports: auto, camera, tool, family.
v39.18.6は、v39.18.0以前の実績保存、旧ワールド実績メタデータ、実績スペルv9・v10、旧バイナリセーブを読み込めます。旧データは現行形式へ正規化され、新規保存には廃止済みフィールドを書きません。
## Core systems
## サーバー同期
- `World`: `js/world.js` plus `js/world_*.js`; time, weather, placement, effects, family records, spatial queries, ants, combat, logging.
- `Tarinai`: `js/tarinai.js` plus `js/tarinai_*.js`; identity, personality, needs, items, disease, movement, social behavior, rendering.
- `Item`: `js/items.js`; lifecycle, scaling, field rendering. Field rendering is the visual source of truth for tool icons.
- `ItemVisualRegistry`: `js/item_visual_registry.js`; canonical item visuals used by field sprites and generated icon previews.
- `EffectRegistry`: `js/effect_registry.js`; item-effect names, modifiers, exclusivity, display metadata, random-effect candidates.
- `DiseaseRegistry`: `js/disease_registry.js`; disease flags, labels, display metadata, cure-rule notes, mystery-drug eligibility.
- `FoodRegistry`: `js/food_registry.js`; serving classes, labels, decay, hygiene penalty, special effect IDs.
- `InputModeManager`: `js/input_mode_manager.js`; mobile/touch classification, gesture capability policy, mode persistence.
- `WeatherSystem`: `js/weather_system.js`; weather state transitions and rain-spawned water items.
- `Audio`: `js/sound_pack.js` owns sound metadata; `js/audio.js` loads samples, applies settings, enforces cooldowns, and runs synthetic fallbacks.
- `Performance`: `js/performance_manager.js`; FPS quality level, ant stride, effect budget, terrain redraw cadence.
同期プロトコルはv5です。
## Current high-risk areas
- 解除状態と途中進捗を追加型で統合
- 同じ端末台帳の再送を重複加算しない
- リセット要求の再送を無害化
- 古いサーバーバックアップから新しいローカル世代を復旧
- 古い端末からリセット前の状態を復活させない
- プレイヤーIDと所有秘密鍵を耐久保存から復旧
- Item effects touch `effect_registry`, `tarinai_item_effects`, `tarinai_needs_items`, `items`, and selected-info UI.
- Mobile input must remain hidden on desktop-class devices; `TarinaiInputMode.snapshot().touchFirst` controls body class `touch-mobile-ui`.
- Weather rain overlay is diagonal line streaks. Fallen water items, zunda juice, and mercury share the same horizontal oval item-droplet silhouette; recolor only.
- Tool UI icons must be generated previews of `js/items.js` drawing helpers, never separate designs.
共有達成率は、サーバーがゲームシミュレーションを再実行した検証値ではなく、クライアント端末の報告値です。UIでは共有値を「達成率」「達成人数」と表示します。
## 旧サーバーデータの自動移行
データディレクトリ、またはアプリ直下の `.achievement_data` に旧コンパクト形式の `state.json`スキーマ4がある場合、最初のAPI要求時に現行形式へ自動移行します。環境変数で新保存先を指定した場合や、v39.18.1が空の現行保存先を先に作成済みの場合も検出します。
- 原本は `legacy-backup/` へSHA-256付きで保存
- 旧プレイヤーIDから決定的なUUIDを生成
- プレイヤー数、解除実績、解除時刻、実績別集計を移行
- 旧データに存在しない途中進捗は0から開始
- `legacy-v4-migration.json` に完了件数を記録
- 中断時は次のAPI要求で冪等に再開
- 切替中にコピー元の旧 `state.json` が更新された場合も、内容ハッシュを再確認して追加方向で再取込
- ファイルサイズとmtimeが同じ更新もSHA-256比較で検出
旧ブラウザーに22文字のプレイヤーIDが残っている場合、クライアントはUUIDへ上書きする前に専用領域へ退避し、初回セッションで一度だけ新しい所有秘密鍵へ引き継ぎます。引継ぎ済みの旧IDを別の秘密鍵で再取得することはできません。
旧版から直接v39.18.6へ更新したブラウザーは自動引継ぎできます。v39.18.1を既に起動して旧22文字IDがブラウザーから失われている場合、サーバーデータだけから端末との本人対応を再構成することはできません。解除履歴と共有集計自体は正常に移行されます。
## 実績APIの保存先
本番環境では、PHP実行ユーザーが書き込める非公開ディレクトリを用意し、環境変数 `TARINAI_ACHIEVEMENT_DATA_DIR` に絶対パスを設定してください。未設定時は、HTTP公開ディレクトリ外の安定したユーザーデータ領域を使用します。
## PHP組み込みサーバーでの起動
PHP組み込みサーバーは `.htaccess` を解釈しません。旧 `.achievement_data/state.json` には引継ぎ用IDが含まれるため、必ず同梱のルーターを使用してください。
```sh
./start_server.sh 8000
```
Windowsでは `start_server.bat 8000` を使用します。本番環境では引き続き `TARINAI_ACHIEVEMENT_DATA_DIR` をHTTP公開ディレクトリ外へ設定してください。

1227
achievement_api.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"version": "15.24.0",
"version": "39.18.18",
"css": [
"css/base.css",
"css/layout.css",
@ -10,56 +10,166 @@
"js": [
"js/version.js",
"js/event_bus.js",
"js/performance_manager.js",
"js/domain_ids.js",
"js/registry_base.js",
"js/disease_registry.js",
"js/effect_registry.js",
"js/sound_pack.js",
"js/deterministic_helpers.js",
"js/item_tool_metadata.js",
"js/item_tool_definitions.js",
"js/item_visual_definitions.js",
"js/item_food_definitions.js",
"js/item_effect_definitions.js",
"js/item_registry.js",
"js/data.js",
"js/item_visual_registry.js",
"js/food_registry.js",
"js/ground_types.js",
"js/input_mode_manager.js",
"js/math.js",
"js/geometry_helpers.js",
"js/collision_footprint_system.js",
"js/physics_helpers.js",
"js/placement_preview_system.js",
"js/pin_attachment_system.js",
"js/physics_shape_editor_system.js",
"js/mechanical_system.js",
"js/mechanical_shape_bridge.js",
"js/constraint_system.js",
"js/physics_world_system.js",
"js/physics_projection_system.js",
"js/circuit_board_system.js",
"js/signal_system.js",
"js/assets.js",
"js/audio.js",
"js/perf_profiler.js",
"js/display_helpers.js",
"js/render.js",
"js/sim_core.js",
"js/tarinai_seed_factory.js",
"js/structures.js",
"js/item_type_catalog.js",
"js/items.js",
"js/item_type_initializers.js",
"js/item_lifecycle_support.js",
"js/item_update_policy.js",
"js/fire_runtime_system.js",
"js/robot_cleaner_system.js",
"js/item_dynamic_tool_system.js",
"js/item_dynamic_ball_system.js",
"js/item_dynamic_duplicator_system.js",
"js/item_dynamic_pin_system.js",
"js/item_dynamic_zunchi_system.js",
"js/item_environment_hazard_system.js",
"js/item_dynamic_system.js",
"js/item_lifecycle_decay_system.js",
"js/item_lifecycle_growth_system.js",
"js/item_lifecycle_step_frame.js",
"js/item_lifecycle_step_decay.js",
"js/item_lifecycle_step_dynamic.js",
"js/item_lifecycle_step_growth.js",
"js/update_step_pipeline_runner.js",
"js/item_lifecycle_pipeline.js",
"js/item_runtime.js",
"js/structure_lifecycle.js",
"js/memorial_bell_system.js",
"js/item_render_helpers.js",
"js/item_render_runtime.js",
"js/burn_motion_util.js",
"js/ants.js",
"js/health.js",
"js/tarinai.js",
"js/tarinai_action_spec.js",
"js/tarinai_behavior_state.js",
"js/tarinai_identity_social.js",
"js/tarinai_action_state.js",
"js/tarinai_disease_nest.js",
"js/tarinai_item_effects.js",
"js/tarinai_needs_core.js",
"js/tarinai_behavior_text.js",
"js/tarinai_forced_behavior.js",
"js/tarinai_nest_sleep_system.js",
"js/tarinai_item_targeting.js",
"js/tarinai_consumable_behavior.js",
"js/tarinai_social_action_runtime.js",
"js/tarinai_building_behavior.js",
"js/seesaw_system.js",
"js/tarinai_action_definitions.js",
"js/tarinai_needs_items.js",
"js/tarinai_food_prototype_mixin.js",
"js/tarinai_direct_feeding_system.js",
"js/tarinai_need_planner_system.js",
"js/tarinai_item_interaction_context.js",
"js/tarinai_food_interaction_system.js",
"js/tarinai_contact_item_system.js",
"js/tarinai_item_interaction_system.js",
"js/tarinai_sunbath_system.js",
"js/tarinai_cursor_care_system.js",
"js/tarinai_local_environment_system.js",
"js/tarinai_social_move_life.js",
"js/tarinai_update_step_frame.js",
"js/tarinai_update_step_ai.js",
"js/tarinai_update_step_environment.js",
"js/tarinai_update_step_movement.js",
"js/tarinai_update_step_health.js",
"js/tarinai_update_pipeline.js",
"js/tarinai_runtime.js",
"js/tarinai_render.js",
"js/world.js",
"js/world_view.js",
"js/family_graph.js",
"js/world_reset_presets.js",
"js/world_family_social.js",
"js/impact_core_system.js",
"js/impact_response_system.js",
"js/collision_response_system.js",
"js/world_combat_effects.js",
"js/world_environment.js",
"js/world_temperature_system.js",
"js/world_pathfinding_system.js",
"js/world_grass_placement_system.js",
"js/world_spatial_budget.js",
"js/world_ants_system.js",
"js/weather_system.js",
"js/item_update_scheduler.js",
"js/simulation_runtime_helpers.js",
"js/tarinai_update_policy.js",
"js/simulation_environment_system.js",
"js/simulation_item_ant_system.js",
"js/simulation_effects_system.js",
"js/simulation_creature_system.js",
"js/simulation_maintenance_system.js",
"js/simulation_ambient_system.js",
"js/simulation_systems.js",
"js/system_order.js",
"js/simulation.js",
"js/colony_situation_system.js",
"js/world_update.js",
"js/world_tool_actions.js",
"js/world_placement_log.js",
"js/world_bootstrap.js",
"js/world_event_effects.js",
"js/command_dispatcher.js",
"js/text_catalog.js",
"js/ui.js",
"js/ui_log.js",
"js/game_dialogs.js",
"js/achievement_catalog.js",
"js/achievements.js",
"js/ui_helpers.js",
"js/ui_log.js",
"js/ui_selected.js",
"js/save_schema.js",
"js/snapshot_system.js",
"js/restore_coordinator.js",
"js/history_system.js",
"js/save_codec.js",
"js/save_storage.js",
"js/save_system.js",
"js/patch_helpers.js",
"js/freeze_system.js",
"js/relation_event_system.js",
"js/ui_tooltips.js",
"js/ui_layout_dialogs.js",
"js/ui_ground.js",
"js/ui_tools.js",
"js/ui_input_shared.js",
"js/ui_pointer_action_system.js",
"js/ui_input_touch.js",
"js/ui_input_mouse.js",
"js/ui_bind.js",
"js/ui_charts.js",
"js/ui_family_data.js",
@ -69,13 +179,5 @@
"js/ui_family_render.js",
"js/main.js",
"js/debug_tools.js"
],
"assetGlobs": [
"assets/ui/*.webp",
"assets/objects/*.webp",
"assets/sprites/*.webp",
"assets/sounds/*.wav",
"assets/sounds/*.mp3"
],
"notes": "Source of truth for static loading order; generated files are index.html script/link tags and service-worker.js cache list."
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/ui/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
assets/ui/pwa-icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/ui/pwa-icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
assets/ui/tool_oshibyo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/ui/tool_shoot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -36,23 +36,6 @@ button {
color: inherit;
}
.loading-screen {
position: fixed;
inset: 0;
z-index: 9999;
display: grid;
place-items: center;
padding: 24px;
background:
radial-gradient(circle at 50% 30%, rgba(164, 225, 104, 0.22), transparent 32%),
linear-gradient(180deg, rgba(239, 248, 255, 0.98), rgba(247, 245, 236, 0.98));
transition: opacity 180ms ease, visibility 180ms ease;
}
.loading-screen.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.loading-panel {
width: min(420px, 92vw);
padding: 24px 22px;
@ -62,37 +45,6 @@ button {
box-shadow: 0 18px 48px rgba(64, 48, 32, 0.16);
text-align: center;
}
.loading-title {
font-weight: 700;
font-size: 18px;
letter-spacing: 0.04em;
margin-bottom: 8px;
}
.loading-text {
color: var(--muted);
font-size: 13px;
line-height: 1.55;
min-height: 1.55em;
}
.loading-bar {
height: 12px;
margin: 18px 0 8px;
border-radius: 999px;
overflow: hidden;
background: rgba(90, 70, 48, 0.12);
}
.loading-bar-fill {
width: 0%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #9ce84b, #f0d35b);
transition: width 160ms ease;
}
.loading-percent {
color: var(--muted);
font-size: 12px;
font-variant-numeric: tabular-nums;
}
#app {
width: min(1480px, 100vw);
@ -104,15 +56,17 @@ button {
.topbar {
display: flex;
justify-content: space-between;
gap: 18px;
gap: 14px;
align-items: center;
flex-wrap: nowrap;
margin-bottom: 16px;
}
.top-left {
display: flex;
align-items: center;
gap: 12px;
gap: 10px;
flex: 1 1 auto;
min-width: 0;
}
@ -124,10 +78,19 @@ button {
min-width: 0;
}
.room-links {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 6px;
flex: 0 0 auto;
}
.room-link {
text-decoration: none;
white-space: nowrap;
background: rgba(255,255,255,0.86);
text-align: center;
}
h1 {
@ -137,26 +100,26 @@ h1 {
white-space: nowrap;
}
.subtitle {
margin: 0;
color: var(--muted);
font-size: clamp(11px, 1.1vw, 13px);
line-height: 1.35;
white-space: nowrap;
}
.speed-select-native { display: none; }
.speed-menu { position: relative; display: inline-block; }
.speed-menu-btn { min-width: 92px; text-align: center; white-space: nowrap; }
.top-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
gap: 6px;
flex: 0 0 auto;
flex-wrap: nowrap;
justify-content: flex-end;
align-items: center;
white-space: nowrap;
}
.btn, .tool {
border: 1px solid var(--line);
background: rgba(255,255,255,0.76);
border-radius: 999px;
padding: 9px 13px;
padding: 8px 11px;
cursor: pointer;
box-shadow: 0 4px 14px rgba(80,60,40,0.06);
transition: transform 130ms ease, background 130ms ease, border-color 130ms ease;
@ -216,7 +179,7 @@ h1 {
width: min(440px, 100%);
max-height: calc(100vh - 36px);
overflow: auto;
border-radius: 16px;
border-radius: 12px;
padding: 16px;
background: rgba(255, 252, 245, 0.96);
border: 1px solid rgba(84,68,48,0.16);
@ -240,9 +203,30 @@ h1 {
color: var(--ink);
}
.field-choice:hover { border-color: rgba(112,178,66,0.45); }
.field-choice.active,
.field-choice[aria-pressed="true"] {
border-color: rgba(112,178,66,0.72);
box-shadow: 0 0 0 2px rgba(112,178,66,0.18), inset 0 0 0 1px rgba(255,255,255,0.64);
background: linear-gradient(180deg, rgba(248,255,240,0.96), rgba(231,246,210,0.82));
}
.field-choice strong, .field-choice span { display: block; }
.field-choice strong { font-size: 15px; }
.field-choice span { margin-top: 4px; color: var(--muted); font-size: 12px; }
.field-dialog-lead {
margin: -4px 0 12px;
color: var(--muted);
font-size: 12px;
line-height: 1.5;
}
.field-dialog-section-title {
margin: 12px 0 7px;
font-size: 12px;
font-weight: 700;
color: rgba(75,62,43,0.78);
}
.reset-dialog-panel { width: min(760px, 100%); }
.reset-preset-choice-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.reset-preset-choice-grid .field-choice { min-height: 66px; padding: 7px 5px; }
.field-dialog-actions {
margin-top: 12px;
display: flex;
@ -260,7 +244,6 @@ h1 {
min-width: 132px;
}
.ecology-dialog-panel {
width: min(760px, 100%);
max-height: calc(100vh - 36px);
@ -309,7 +292,7 @@ h1 {
color: white;
padding: 10px 16px;
border-radius: 999px;
font-size: 14px;
font-size: 12px;
pointer-events: none;
opacity: 1;
transition: opacity 200ms ease, transform 200ms ease;
@ -346,75 +329,728 @@ h1 {
.tool-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap: 8px;
grid-template-columns: repeat(3, minmax(0,1fr));
gap: 6px;
}
.tool {
border-radius: 14px;
display: flex;
--tool-main-icon-size: 28px;
--tool-icon-zoom: 1.05;
--tool-watermark-size: 0px;
border-radius: 12px;
display: grid;
grid-template-columns: 31px minmax(0,1fr);
align-items: center;
gap: 7px;
justify-content: flex-start;
padding: 8px 8px 8px 42px;
min-height: 46px;
justify-items: stretch;
gap: 2px;
padding: 3px 3px 3px 2px;
min-height: 47px;
font-size: 11.4px;
line-height: 1.06;
letter-spacing: -0.015em;
text-align: left;
position: relative;
overflow: hidden;
background-repeat: no-repeat;
background-position: 7px center, center;
background-size: 30px 30px, cover;
background-position: left 3px center, center;
background-size: var(--tool-main-icon-size) var(--tool-main-icon-size), cover;
}
.tool::before {
content: "";
position: absolute;
left: 7px;
left: 3px;
top: 50%;
width: 30px;
height: 30px;
transform: translateY(-50%);
width: var(--tool-main-icon-size);
height: var(--tool-main-icon-size);
transform: translateY(-50%) scale(var(--tool-icon-zoom, 1));
transform-origin: center;
background: var(--tool-icon) center / contain no-repeat;
opacity: 0.92;
opacity: 0.98;
z-index: 1;
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
}
.tool::after {
content: "";
position: absolute;
inset: 0;
background: var(--tool-icon) right -8px center / 54px 54px no-repeat;
opacity: 0.10;
background: none;
display: none;
pointer-events: none;
z-index: 0;
}
.tool-label {
position: relative;
z-index: 2;
display: block;
grid-column: 2;
align-self: center;
justify-self: start;
max-width: 100%;
min-width: 0;
overflow-wrap: anywhere;
font-size: 11.4px;
line-height: 1.08;
font-weight: 700;
max-width: 5.35em;
word-break: normal;
overflow-wrap: anywhere;
text-shadow: 0 1px 0 rgba(255,255,255,0.62);
white-space: pre-line;
}
.tool.tool-has-canvas-icon { --tool-icon: none; grid-template-columns: 31px minmax(0,1fr); min-height: 47px; padding-top: 4px; }
.tool.tool-has-canvas-icon::before,
.tool.tool-has-canvas-icon::after { background: none !important; }
.tool-sprite-preview {
position: absolute;
left: 1px;
top: 50%;
width: 30px;
height: 30px;
transform: translateY(-50%);
pointer-events: none;
z-index: 1;
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
}
.tool[data-tool="giant_drug"] .tool-sprite-preview { width: 39px; height: 39px; left: -4px; }
.tool[data-tool="dwarf_drug"] .tool-sprite-preview,
.tool[data-tool="oshibyo"] .tool-sprite-preview { width: 22px; height: 22px; left: 5px; }
.tool.tool-has-physics-icon .tool-physics-preview { width: 31px; height: 31px; left: 0; filter: drop-shadow(0 1px 2px rgba(52,40,26,0.16)); }
.tool[data-tool="poke"], .tool[data-tool="water_hose"] { --tool-icon-zoom: 0.92; }
.signboard-editor {
position: fixed;
inset: 0;
z-index: 42;
display: grid;
place-items: center;
padding: 18px;
background: rgba(34, 29, 22, 0.34);
backdrop-filter: blur(3px);
}
.signboard-editor.hidden { display: none; }
.signboard-editor-panel {
width: min(220px, calc(100vw - 28px));
border-radius: 12px;
padding: 14px;
background: linear-gradient(180deg, rgba(255,253,247,0.98), rgba(245,237,221,0.96));
border: 1px solid rgba(94, 70, 42, 0.20);
box-shadow: 0 22px 60px rgba(36, 28, 20, 0.28), inset 0 1px 0 rgba(255,255,255,0.72);
max-height: calc(100dvh - 36px);
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.signboard-editor-titlebar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin: 0 0 10px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(94, 70, 42, 0.13);
}
.signboard-editor-titlebar h2 {
margin: 0;
font-size: 16px;
letter-spacing: .03em;
}
.signboard-editor-close {
width: 30px;
height: 30px;
border-radius: 999px;
border: 1px solid rgba(84,68,48,0.16);
background: rgba(255,255,255,0.72);
color: var(--ink);
font-size: 19px;
line-height: 1;
cursor: pointer;
}
.signboard-editor-close:hover { background: rgba(255,248,232,0.96); }
#signboardEditorText {
width: 7.4em;
max-width: 100%;
box-sizing: border-box;
display: block;
margin: 0 auto;
resize: none;
min-height: 88px;
max-height: 88px;
overflow: hidden;
white-space: pre;
border-radius: 12px;
border: 1px solid rgba(84,68,48,0.18);
background: rgba(255,255,255,0.84);
padding: 10px 11px;
color: var(--ink);
font: 15px/1.55 ui-rounded, "Hiragino Maru Gothic ProN", "Yu Gothic", sans-serif;
letter-spacing: 0.03em;
outline: none;
}
#signboardEditorText:focus {
border-color: rgba(112,178,66,0.54);
box-shadow: 0 0 0 3px rgba(112,178,66,0.14);
}
.signboard-editor-meta {
display: flex;
flex-wrap: wrap;
gap: 2px 8px;
justify-content: space-between;
color: var(--muted);
font-size: 12px;
margin: 6px 2px 0;
}
.signboard-editor-actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
margin-top: 12px;
}
.robot-cleaner-editor-panel { width: min(300px, calc(100vw - 28px)); }
.robot-cleaner-editor-help {
color: var(--muted);
font-size: 12px;
line-height: 1.45;
margin: 0 2px 10px;
}
.robot-cleaner-targets {
display: grid;
gap: 8px;
margin: 8px 0 2px;
}
.robot-cleaner-targets label {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 10px;
background: rgba(255,255,255,0.58);
border: 1px solid rgba(84,68,48,0.11);
cursor: pointer;
user-select: none;
}
.robot-cleaner-targets input { accent-color: #70b242; }
/* v16.07.08: right menu fixed quick tabs */
.panel-quick-tabs {
position: sticky;
top: 0;
z-index: 12;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 6px;
padding: 8px 4px 9px;
margin: -2px 0 2px;
background: linear-gradient(180deg, rgba(244, 250, 247, 0.96), rgba(244, 250, 247, 0.78));
border-bottom: 1px solid rgba(102, 169, 215, 0.34);
box-shadow: 0 8px 16px rgba(79, 103, 121, 0.10);
backdrop-filter: blur(8px);
}
.panel-quick-tabs button {
min-width: 0;
border: 1px solid rgba(102, 169, 215, 0.34);
border-radius: 999px;
padding: 7px 5px;
background: rgba(255,255,255,0.76);
color: var(--ink);
font-size: 11px;
line-height: 1.1;
font-weight: 700;
cursor: pointer;
box-shadow: 0 3px 9px rgba(68, 92, 110, 0.08);
}
.panel-quick-tabs button:hover,
.panel-quick-tabs button.active {
background: #efffde;
border-color: rgba(103,178,44,0.46);
}
.rotator-editor {
position: fixed;
inset: 0;
z-index: 43;
display: grid;
place-items: center;
padding: 18px;
background: rgba(34, 29, 22, 0.34);
backdrop-filter: blur(3px);
}
.rotator-editor.hidden { display: none; }
.rotator-editor-panel {
width: min(620px, calc(100vw - 28px));
border-radius: 14px;
padding: 14px;
background: linear-gradient(180deg, rgba(255,253,247,0.98), rgba(245,237,221,0.97));
border: 1px solid rgba(94, 70, 42, 0.20);
box-shadow: 0 22px 60px rgba(36, 28, 20, 0.28), inset 0 1px 0 rgba(255,255,255,0.72);
max-height: calc(100dvh - 36px);
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.rotator-editor-titlebar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(94, 70, 42, 0.13);
}
.rotator-editor-titlebar h2 { margin: 0; font-size: 16px; letter-spacing: .03em; }
.rotator-editor-close {
width: 30px;
height: 30px;
border-radius: 999px;
border: 1px solid rgba(84,68,48,0.16);
background: rgba(255,255,255,0.72);
color: var(--ink);
font-size: 19px;
line-height: 1;
cursor: pointer;
}
.rotator-editor-close:hover { background: rgba(255,248,232,0.96); }
.rotator-editor-controls {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
margin-bottom: 8px;
}
.rotator-editor-controls label {
display: grid;
grid-template-columns: auto 1fr 72px auto;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--ink);
}
.rotator-editor-controls input[type="number"] {
width: 72px;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid rgba(84,68,48,0.18);
background: rgba(255,255,255,0.84);
padding: 4px 6px;
}
.pressure-numeric-control[hidden] { display: none; }
.pressure-numeric-control {
display: grid;
gap: 8px;
padding: 10px 10px 8px;
border-radius: 11px;
border: 1px solid rgba(84,68,48,0.13);
background: rgba(255,255,255,0.58);
}
.pressure-value-slider {
width: calc(100% - 14px);
height: 34px;
margin: 0 7px;
padding: 0;
appearance: none;
-webkit-appearance: none;
background: transparent;
}
.pressure-value-slider::-webkit-slider-runnable-track {
height: 6px;
border-radius: 999px;
background: rgba(67,158,218,0.78);
box-shadow: inset 0 1px 2px rgba(43,55,66,0.16);
}
.pressure-value-slider::-webkit-slider-thumb {
width: 20px;
height: 20px;
margin-top: -7px;
border-radius: 50%;
border: 2px solid rgba(42,121,172,0.88);
background: #fafdff;
box-shadow: 0 2px 6px rgba(42,78,104,0.24);
appearance: none;
-webkit-appearance: none;
cursor: grab;
}
.pressure-value-slider::-moz-range-track {
height: 6px;
border: 0;
border-radius: 999px;
background: rgba(67,158,218,0.78);
box-shadow: inset 0 1px 2px rgba(43,55,66,0.16);
}
.pressure-value-slider::-moz-range-thumb {
width: 17px;
height: 17px;
border-radius: 50%;
border: 2px solid rgba(42,121,172,0.88);
background: #fafdff;
box-shadow: 0 2px 6px rgba(42,78,104,0.24);
cursor: grab;
}
.pressure-value-slider:focus-visible::-webkit-slider-thumb {
outline: 3px solid rgba(73,157,216,0.30);
outline-offset: 2px;
}
.pressure-value-slider:focus-visible::-moz-range-thumb {
outline: 3px solid rgba(73,157,216,0.30);
outline-offset: 2px;
}
.pressure-time-control[hidden] { display: none; }
.pressure-spatial-control[hidden] { display: none; }
.pressure-time-control {
display: grid;
gap: 8px;
padding: 10px 10px 8px;
border-radius: 11px;
border: 1px solid rgba(84,68,48,0.13);
background: rgba(255,255,255,0.58);
}
.pressure-time-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
color: var(--ink);
font-size: 13px;
font-weight: 700;
}
.pressure-time-summary output {
display: inline-block;
min-width: 42px;
text-align: center;
font-variant-numeric: tabular-nums;
}
.pressure-time-range {
position: relative;
height: 34px;
margin: 0 7px;
}
.pressure-time-track {
position: absolute;
left: 0;
right: 0;
top: 14px;
height: 6px;
border-radius: 999px;
background: rgba(94,112,128,0.22);
box-shadow: inset 0 1px 2px rgba(43,55,66,0.16);
}
.pressure-time-thumb {
position: absolute;
inset: 0;
width: 100%;
height: 34px;
margin: 0;
padding: 0;
appearance: none;
-webkit-appearance: none;
background: transparent;
pointer-events: none;
}
.tool > span:not(.tool-size-badge) { display: none; }
.tool[data-tool="observe"] { --tool-icon: url("../assets/ui/tool_observe.webp"); }
.tool[data-tool="zunchi"] { --tool-icon: url("../assets/ui/tool_zunchi.webp"); }
.tool[data-tool="sweet"] { --tool-icon: url("../assets/ui/tool_sweet.webp"); }
.tool[data-tool="grass"] { --tool-icon: url("../assets/ui/tool_grass.webp"); }
.tool[data-tool="stone"] { --tool-icon: url("../assets/ui/tool_stone.webp"); }
.tool[data-tool="genkotsu"] { --tool-icon: url("../assets/ui/tool_genkotsu.webp"); }
.tool[data-tool="firecracker"] { --tool-icon: url("../assets/ui/tool_firecracker.webp"); }
.tool[data-tool="fence_v"] { --tool-icon: url("../assets/ui/tool_fence_v.webp"); }
.tool[data-tool="fence_h"] { --tool-icon: url("../assets/ui/tool_fence_h.webp"); }
.tool[data-tool="bed"] { --tool-icon: url("../assets/ui/tool_bed.webp"); }
.tool[data-tool="love_mochi"] { --tool-icon: url("../assets/ui/tool_love_mochi.webp"); }
.tool[data-tool="fight_mochi"] { --tool-icon: url("../assets/ui/tool_fight_mochi.webp"); }
.tool[data-tool="water_bowl"] { --tool-icon: url("../assets/ui/tool_water_bowl.webp"); }
/* Generated parameter item tool icons. */
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.20.16"); }
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.20.16"); }
.tool[data-tool="niteropu"] { --tool-icon: url("../assets/ui/tool_niteropu.webp?v=15.20.16"); }
.tool[data-tool="ammo"] { --tool-icon: url("../assets/ui/tool_ammo.webp?v=15.20.16"); }
.tool[data-tool="mystery_drug"] { --tool-icon: url("../assets/ui/tool_mystery_drug.webp?v=15.20.16"); }
.tool[data-tool="mercury"] { --tool-icon: url("../assets/ui/tool_mercury.webp?v=15.20.16"); }
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.20.16"); }
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.20.16"); }
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.20.16"); }
.tool[data-tool="freeze"] { --tool-icon: none; }
.tool[data-tool="freeze"]::before {
content: "🧊";
background: none;
font-size: 24px;
line-height: 1;
text-align: center;
.pressure-time-thumb::-webkit-slider-runnable-track {
height: 6px;
background: transparent;
border: 0;
}
.tool[data-tool="freeze"]::after { content: "🧊"; background: none; font-size: 52px; right: -10px; left: auto; opacity: 0.08; display:flex; align-items:center; justify-content:flex-end; }
.pressure-time-thumb::-webkit-slider-thumb {
width: 20px;
height: 20px;
margin-top: -7px;
border-radius: 50%;
border: 2px solid rgba(42,121,172,0.88);
background: #fafdff;
box-shadow: 0 2px 6px rgba(42,78,104,0.24);
appearance: none;
-webkit-appearance: none;
pointer-events: auto;
cursor: grab;
}
.pressure-time-thumb::-moz-range-track {
height: 6px;
background: transparent;
border: 0;
}
.pressure-time-thumb::-moz-range-thumb {
width: 17px;
height: 17px;
border-radius: 50%;
border: 2px solid rgba(42,121,172,0.88);
background: #fafdff;
box-shadow: 0 2px 6px rgba(42,78,104,0.24);
pointer-events: auto;
cursor: grab;
}
.pressure-time-thumb:focus-visible::-webkit-slider-thumb {
outline: 3px solid rgba(73,157,216,0.30);
outline-offset: 2px;
}
.pressure-time-thumb:focus-visible::-moz-range-thumb {
outline: 3px solid rgba(73,157,216,0.30);
outline-offset: 2px;
}
.pressure-time-thumb-start { z-index: 2; }
.pressure-time-thumb-end { z-index: 3; }
.pressure-time-range[data-wraps="1"] .pressure-time-thumb-start { z-index: 4; }
.signal-channel-label {
font-size: 13px;
color: var(--ink);
font-weight: 700;
}
.signal-channel-picker {
display: grid;
grid-template-columns: repeat(3, minmax(44px, 1fr));
gap: 7px;
}
.signal-channel-picker button {
min-height: 38px;
border-radius: 10px;
border: 1px solid rgba(84,68,48,0.18);
background: rgba(255,255,255,0.78);
color: var(--ink);
font: 700 14px ui-rounded, sans-serif;
cursor: pointer;
}
.signal-channel-picker button:hover { background: rgba(255,248,232,0.98); }
.signal-channel-picker button.active {
background: rgba(78,179,224,0.22);
border-color: rgba(42,133,184,0.58);
box-shadow: inset 0 0 0 1px rgba(42,133,184,0.18);
}
.rotator-editor-tools {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 8px;
}
.rotator-editor-tools button {
border: 1px solid rgba(84,68,48,0.16);
background: rgba(255,255,255,0.74);
color: var(--ink);
border-radius: 999px;
padding: 5px 10px;
font-size: 12px;
cursor: pointer;
}
.rotator-editor-tools button.active {
background: rgba(137, 101, 198, 0.18);
border-color: rgba(112, 80, 170, 0.38);
font-weight: 700;
}
#rotatorCanvas,
#reciprocatorCanvas {
width: 100%;
height: min(58vh, 360px);
display: block;
border-radius: 12px;
border: 1px solid rgba(84,68,48,0.20);
background: #f7f3ea;
touch-action: none;
cursor: crosshair;
}
.rotator-editor-hint {
margin: 7px 2px 0;
color: var(--muted);
font-size: 12px;
}
.rotator-editor-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 12px;
}
@media (max-width: 560px) {
.rotator-editor {
place-items: start center;
padding: 8px;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.rotator-editor-panel {
width: calc(100vw - 16px);
max-height: calc(100dvh - 16px);
padding: 10px;
}
.rotator-editor-controls label { grid-template-columns: 1fr; align-items: stretch; }
.rotator-editor-tools { max-height: 22vh; overflow-y: auto; padding-right: 2px; }
#rotatorCanvas, #reciprocatorCanvas { height: min(44vh, 270px); }
}
.fan-editor-panel { width: min(430px, calc(100vw - 28px)); }
.fan-editor-help {
color: var(--muted);
font-size: 12px;
line-height: 1.45;
margin: 0 2px 10px;
}
.fan-editor-controls label {
grid-template-columns: 56px 1fr 72px auto;
}
.fan-editor-controls .fan-editor-check {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 10px;
background: rgba(255,255,255,0.58);
border: 1px solid rgba(84,68,48,0.11);
}
.fan-editor-controls input[type="checkbox"] { accent-color: #70aee8; }
@media (max-width: 560px) {
.fan-editor-controls label { grid-template-columns: 1fr; align-items: stretch; }
.fan-editor-panel { width: calc(100vw - 16px); }
}
/* v39.15.130: only in-game overlay windows scroll when they exceed the viewport.
Right-panel cards and tool categories intentionally keep the older stable layout. */
.field-dialog,
.signboard-editor,
.rotator-editor {
overflow: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
align-items: flex-start;
justify-items: center;
box-sizing: border-box;
padding: 12px;
}
.field-dialog-panel,
.signboard-editor-panel,
.rotator-editor-panel {
max-height: calc(100dvh - 24px);
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.save-confirm-dialog { z-index: 36; }
.save-confirm-panel { width: min(390px, 100%); }
.save-confirm-message { margin-bottom: 8px; color: var(--ink); font-size: 14px; }
/* Logic-board editor */
.circuit-board-editor{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;padding:18px;background:rgba(18,22,27,.58);backdrop-filter:blur(3px)}
.circuit-board-editor.hidden{display:none}
.circuit-board-panel{width:min(980px,96vw);max-height:92vh;overflow:hidden;background:rgba(250,249,244,.98);color:#2e3531;border:1px solid rgba(44,74,57,.35);border-radius:18px;box-shadow:0 24px 70px rgba(0,0,0,.35);display:flex;flex-direction:column}
.circuit-board-titlebar{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;background:linear-gradient(135deg,#235b3a,#347b50);color:white}
.circuit-board-titlebar h2{margin:0;font-size:1.18rem}
.circuit-board-titlebar button{border:0;background:transparent;color:white;font-size:1.7rem;line-height:1;cursor:pointer}
.circuit-board-help{margin:12px 18px 6px;font-size:.88rem;color:#536059}
.circuit-board-layout{display:grid;grid-template-columns:minmax(280px,42%) minmax(360px,58%);gap:14px;padding:10px 18px;min-height:0;flex:1}
.circuit-board-preview{align-self:start;background:#17231c;border-radius:14px;padding:10px;min-height:250px;position:sticky;top:0}
.circuit-board-preview svg{width:100%;height:auto;display:block}
.circuit-preview-lines path{fill:none;stroke:#d7b84d;stroke-width:2.4}
.circuit-preview-gates rect{fill:#26352d;stroke:#d9e9dd;stroke-width:1.3}.circuit-preview-gates text{fill:#f4f7f2;text-anchor:middle;font:700 11px sans-serif}
.circuit-preview-pins circle{fill:#73c8ff;stroke:#f4f1d6;stroke-width:1.2}.circuit-preview-pins circle:nth-of-type(n+4){fill:#ff9a60}.circuit-preview-pins text{fill:#eef4ef;font:700 11px sans-serif}
.circuit-board-scroll{overflow:auto;padding-right:4px}.circuit-board-scroll section{margin:0 0 14px;padding:12px;border:1px solid rgba(45,83,60,.18);border-radius:12px;background:white}.circuit-board-scroll h3{margin:0 0 10px;font-size:.94rem}
.circuit-port-row,.circuit-gate-row,.circuit-output-row{display:grid;grid-template-columns:minmax(72px,1fr) auto auto auto;gap:8px;align-items:center;padding:7px 0;border-bottom:1px solid rgba(0,0,0,.07)}
.circuit-port-row:last-child,.circuit-gate-row:last-child,.circuit-output-row:last-child{border-bottom:0}.circuit-port-row label,.circuit-gate-row label{display:flex;align-items:center;gap:4px;font-size:.8rem}
.circuit-board-panel select,.circuit-board-panel button{font:inherit}.circuit-board-panel select{padding:5px 7px;border:1px solid rgba(37,77,53,.27);border-radius:7px;background:white}
.circuit-gate-actions{display:flex;gap:7px;margin-bottom:8px}.circuit-gate-actions button,.circuit-gate-row button,.circuit-board-actions button{padding:7px 12px;border:1px solid rgba(38,82,54,.26);border-radius:9px;background:#f1f5f1;cursor:pointer}.circuit-gate-row button{padding:4px 9px;color:#a13f3f}
.circuit-empty{margin:6px 0;color:#7a837d;font-size:.84rem}.circuit-output-row{grid-template-columns:50px 1fr}.circuit-board-actions{display:flex;justify-content:flex-end;gap:9px;padding:12px 18px 16px;border-top:1px solid rgba(0,0,0,.09)}.circuit-board-actions .primary{background:#2e754b;color:white;border-color:#245f3d}
@media(max-width:760px){.circuit-board-layout{grid-template-columns:1fr}.circuit-board-preview{position:relative;min-height:180px}.circuit-port-row,.circuit-gate-row{grid-template-columns:1fr 1fr}.circuit-gate-row button{justify-self:end}.circuit-board-panel{max-height:96vh}}
/* Grid circuit editor reuses the mechanical editor shell and toolbar. */
.circuit-grid-panel { width: min(930px, calc(100vw - 28px)); }
.circuit-grid-tools { max-height: none; overflow: visible; }
.circuit-grid-canvas-wrap {
max-width: 100%;
max-height: min(62vh, 560px);
overflow: auto;
padding: 6px;
border-radius: 12px;
background: rgba(20,35,27,0.35);
}
#circuitGridCanvas {
width: auto;
height: auto;
max-width: none;
display: block;
border-radius: 10px;
border: 1px solid rgba(84,68,48,0.20);
background: #14231b;
touch-action: none;
cursor: crosshair;
}
.circuit-grid-hint { line-height: 1.55; }
@media (max-width: 560px) {
.circuit-grid-panel { width: calc(100vw - 16px); }
.circuit-grid-tools { max-height: 20vh; overflow-y: auto; }
.circuit-grid-canvas-wrap { max-height: min(48vh, 360px); }
#circuitGridCanvas { max-width: none; }
}
/* v39.16.69: circuit terminal count controls and detector numeric range inputs. */
.circuit-port-count-controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 0 0 10px;
}
.circuit-port-count-controls label {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 7px 9px;
border: 1px solid rgba(84,68,48,0.14);
border-radius: 10px;
background: rgba(255,255,255,0.56);
font-size: 13px;
font-weight: 700;
}
.circuit-port-count-controls button {
width: 30px;
height: 30px;
padding: 0;
border: 1px solid rgba(38,82,54,.26);
border-radius: 8px;
background: #f1f5f1;
cursor: pointer;
}
.circuit-port-count-controls input {
width: 54px;
min-height: 30px;
padding: 3px 6px;
border: 1px solid rgba(37,77,53,.27);
border-radius: 7px;
background: white;
text-align: center;
font: inherit;
font-variant-numeric: tabular-nums;
}
.pressure-number-inputs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.pressure-number-inputs label {
display: grid;
grid-template-columns: auto minmax(72px, 1fr);
align-items: center;
gap: 7px;
font-size: 12px;
font-weight: 700;
}
.pressure-number-inputs input {
width: 100%;
min-width: 0;
padding: 6px 8px;
border: 1px solid rgba(42,121,172,0.28);
border-radius: 8px;
background: rgba(255,255,255,0.92);
color: var(--ink);
font: inherit;
font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
.circuit-port-count-controls { display: grid; grid-template-columns: 1fr; }
.circuit-port-count-controls label { justify-content: space-between; }
}
/* v39.16.70: resizable circuit board and movable junctions. */
.circuit-port-count-controls label:nth-child(-n+2){background:rgba(236,246,239,.82)}

View file

@ -2,8 +2,10 @@
.tool-tip-popover {
position: fixed;
z-index: 2147483000;
width: 220px;
max-width: min(220px, calc(100vw - 16px));
width: fit-content;
min-width: min(160px, calc(100vw - 18px));
max-width: min(620px, calc(100vw - 18px));
box-sizing: border-box;
padding: 7px 9px;
border-radius: 10px;
background: rgba(46, 38, 30, 0.78);
@ -15,6 +17,9 @@
backdrop-filter: blur(1px);
text-shadow: 0 1px 3px rgba(25, 18, 12, 0.55);
pointer-events: none;
white-space: pre-line;
overflow-wrap: anywhere;
word-break: break-word;
}
.tool-tip-popover.hidden { display: none; }
.selected-actions {
@ -22,6 +27,9 @@
gap: 8px;
flex-wrap: wrap;
margin: 8px 0 4px;
position: relative;
z-index: 4;
isolation: isolate;
}
.favorite-toggle {
border: 1px solid rgba(128, 102, 56, 0.32);
@ -29,8 +37,15 @@
background: rgba(255, 248, 221, 0.92);
color: #5a4630;
font-weight: 700;
padding: 5px 10px;
padding: 7px 12px;
cursor: pointer;
position: relative;
z-index: 5;
display: block;
width: 100%;
box-sizing: border-box;
pointer-events: auto;
touch-action: manipulation;
}
.favorite-toggle.active {
background: rgba(255, 229, 122, 0.95);
@ -241,12 +256,6 @@
font-size: 12px;
color: rgba(88, 73, 60, 0.82);
}
.loading-hint {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.55;
color: rgba(111, 92, 73, 0.78);
}
.collapsible-card {
padding-top: 10px;
@ -278,14 +287,14 @@
position: relative;
}
.panel-card-state::before {
content: "";
content: "\25be ";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-52%);
color: rgba(98, 82, 64, 0.72);
}
.collapsible-card.collapsed .panel-card-state::before { content: ""; }
.collapsible-card.collapsed .panel-card-state::before { content: "\25b8 "; }
.collapsible-card.collapsed .panel-card-body { display: none; }
.panel-card-body { padding-top: 12px; }
@ -336,13 +345,20 @@
border: 1px solid rgba(192, 138, 96, 0.24);
background: rgba(255, 247, 239, 0.74);
}
.event-category-heading {
margin-bottom: 7px;
font-size: 11px;
font-weight: 700;
color: rgba(82, 66, 52, 0.88);
}
.push-master {
display: flex;
align-items: center;
gap: 7px;
font-size: 12px;
font-weight: 700;
margin-bottom: 8px;
margin-top: 9px;
margin-bottom: 0;
}
.push-kind-grid {
display: grid;
@ -419,6 +435,14 @@
.log-push-toast.log-push-food::before { background: #e9bd61; }
.log-push-toast.log-push-weather::before { background: #7bbbdc; }
.log-push-toast.log-push-grass::before { background: #8cc77a; }
.log-push-toast.log-push-event::before { background: #8b82d6; }
.log-push-toast.log-push-life::before { background: #82bd72; }
.log-push-toast.log-push-danger::before { background: #df845d; }
.log-push-toast.log-push-social::before { background: #b28bd0; }
.log-push-toast.log-push-living::before { background: #d7ad58; }
.log-push-toast.log-push-environment::before { background: #70aac8; }
.log-push-toast.log-push-observe::before { background: #9b9184; }
.log-push-toast.leaving {
animation: logPushOut .18s ease both;
}
@ -431,17 +455,6 @@
to { transform: translateY(8px); opacity: 0; }
}
/* v15.12.0: loading / collapsible cards / sound settings */
.loading-screen {
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
place-items: center;
padding: 20px;
background: radial-gradient(circle at top, rgba(255,251,243,0.98), rgba(245,238,225,0.96) 56%, rgba(234,225,208,0.98));
transition: opacity .22s ease, visibility .22s ease;
}
.selected-info .info-row[data-metric] {
border-radius: 8px;
@ -451,6 +464,34 @@
.selected-info .info-row[data-metric="loneliness"] { background: rgba(116, 150, 214, 0.10); }
.selected-info .info-row[data-metric="energy"] { background: rgba(104, 184, 96, 0.10); }
.selected-info .info-row[data-metric="stress"] { background: rgba(214, 104, 88, 0.10); }
.selected-info .need-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 6px;
margin: 7px 0 8px;
}
.selected-info .need-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 6px 4px;
border: 1px solid rgba(84,68,48,0.12);
border-radius: 10px;
background: rgba(255,255,255,0.42);
}
.selected-info .need-cell span { font-size: 11px; color: var(--muted); }
.selected-info .need-cell strong { font-size: 16px; color: #3f3225; }
.selected-info .need-cell[data-chosen-need="true"] {
border-color: rgba(65, 112, 74, 0.58);
background: rgba(106, 154, 84, 0.20);
box-shadow: inset 0 0 0 1px rgba(65, 112, 74, 0.18);
}
.selected-info .need-cell[data-chosen-need="true"] strong {
color: #315f32;
}
.selected-info .action-reason-row strong { text-align: right; }
.selected-info .info-row[data-metric="zunchi"] { background: rgba(112, 142, 70, 0.10); }
.selected-info .info-row[data-metric="mood"] { background: rgba(222, 196, 86, 0.10); }
.selected-info .info-row[data-metric="lack"] { background: rgba(142, 112, 184, 0.10); }
@ -559,19 +600,29 @@
}
.save-slot-list {
display: grid;
gap: 8px;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin: 14px 0;
}
.save-slot-row {
display: grid;
grid-template-columns: minmax(86px, 0.7fr) minmax(180px, 2fr) auto;
gap: 10px;
align-items: center;
padding: 9px 10px;
grid-template-rows: auto 1fr auto;
gap: 8px;
align-items: stretch;
min-height: 132px;
padding: 10px;
border: 1px solid rgba(115, 91, 62, 0.20);
border-radius: 12px;
background: rgba(255, 255, 255, 0.56);
}
.save-warning {
color: #8a4f2f;
font-weight: 800;
background: rgba(255, 239, 220, 0.72);
border: 1px solid rgba(190, 118, 72, 0.22);
border-radius: 10px;
padding: 7px 9px;
}
.save-slot-title {
font-weight: 800;
color: #4f3928;
@ -583,9 +634,12 @@
.save-slot-actions {
display: flex;
gap: 6px;
justify-content: flex-end;
justify-content: flex-start;
flex-wrap: wrap;
}
@media (max-width: 720px) {
.save-slot-list { grid-template-columns: 1fr; }
}
.save-slot-actions .btn[disabled] {
opacity: 0.42;
pointer-events: none;
@ -613,11 +667,605 @@
font-size: 12px;
line-height: 1.45;
}
@media (max-width: 640px) {
.save-slot-row {
grid-template-columns: 1fr;
}
.save-slot-actions {
justify-content: flex-start;
}
/* v39.16.40: compact header menu and folded colony settings. */
.top-menu { position: relative; display: inline-block; }
.top-menu-btn {
min-width: 40px;
padding-inline: 11px;
font-size: 20px;
line-height: 1;
font-family: system-ui, sans-serif;
}
.top-menu-panel {
position: absolute;
right: 0;
top: calc(100% + 8px);
z-index: 14000;
width: min(250px, calc(100vw - 20px));
padding: 8px;
border: 1px solid rgba(84,68,48,.16);
border-radius: 16px;
background: rgba(255,252,246,.98);
box-shadow: 0 16px 42px rgba(44,34,25,.20);
display: grid;
gap: 5px;
}
.top-menu-panel.hidden { display: none; }
.speed-menu-panel { width: 116px; min-width: 116px; }
.speed-menu-panel .top-menu-item { text-align: center; }
.speed-menu-panel .top-menu-item.active,
.speed-menu-panel .top-menu-item[aria-checked="true"] { background: rgba(224,242,201,.92); }
.top-menu-item {
width: 100%;
min-height: 38px;
border: 0;
border-radius: 11px;
padding: 8px 10px;
background: transparent;
color: var(--ink);
text-align: left;
font: inherit;
font-size: 13px;
font-weight: 700;
cursor: pointer;
}
.top-menu-item:hover,
.top-menu-item:focus-visible { background: rgba(238,245,225,.88); outline: none; }
.top-menu-sound { display: block; width: 100%; }
.top-menu-sound .sound-menu-btn { display: block; border: 0; box-shadow: none; }
.top-menu-sound .sound-panel {
position: static;
width: auto;
min-width: 0;
margin: 3px 3px 5px;
padding: 9px 10px;
border-radius: 12px;
box-shadow: none;
background: rgba(245,240,229,.78);
}
.top-menu-visual { display: block; width: 100%; }
.sound-panel-subtitle {
padding: 2px 0 1px;
color: var(--muted);
font-size: 10px;
font-weight: 800;
letter-spacing: .05em;
}
.top-menu-visual .sound-panel label { min-height: 26px; }
.reset-top-btn {
color: #8e2424 !important;
border-color: rgba(182,69,69,.48) !important;
background: linear-gradient(180deg, rgba(255,245,245,.96), rgba(255,224,224,.92)) !important;
font-weight: 800;
}
.reset-top-btn:hover { background: linear-gradient(180deg, #fff4f4, #ffd6d6) !important; }
.colony-settings-body { display: grid; gap: 0; }
.colony-setting-section { padding: 10px; }
.colony-setting-section + .colony-setting-section { border-top: 1px solid rgba(118,92,61,.12); }
.colony-setting-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 7px; color: var(--ink); font-size: 12px; font-weight: 800; }
.colony-setting-section-head strong { font-size: 15px; }
.colony-setting-section-head small { margin-left: auto; color: var(--muted); font-size: 10px; font-weight: 500; }
.colony-setting-fold {
margin: 8px 0 4px;
border: 1px solid rgba(118,92,61,.16);
border-radius: 14px;
background: rgba(255,251,244,.56);
overflow: hidden;
}
.colony-setting-fold > summary {
min-height: 38px;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 11px;
color: rgba(68,55,41,.84);
font-size: 12px;
font-weight: 700;
cursor: pointer;
list-style: none;
user-select: none;
}
.colony-setting-fold > summary::-webkit-details-marker { display: none; }
.colony-setting-fold > summary::before { content: "\25b8"; color: var(--muted); width: 12px; }
.colony-setting-fold[open] > summary::before { content: "\25be"; }
.colony-setting-fold > summary strong { margin-left: auto; font-size: 15px; }
.colony-setting-fold > summary small { margin-left: auto; color: var(--muted); font-size: 10px; font-weight: 500; }
.colony-setting-fold[open] > summary { border-bottom: 1px solid rgba(118,92,61,.12); }
.colony-setting-fold .temperature-control,
.colony-setting-fold .colony-limit-control {
margin: 0;
border: 0;
border-radius: 0;
background: transparent;
}
.colony-setting-fold .temperature-control { display: grid; gap: 5px; }
.colony-setting-fold .temperature-auto { margin-left: 0; justify-self: end; }
/* v39.16.40: one unified colony settings fold. */
.colony-settings-fold .temperature-control,
.colony-settings-fold .colony-limit-control { padding: 0; border: 0; border-radius: 0; background: transparent; }
/* v39.16.40: restore the compact sticky operation palette from the earlier UI. */
.tool-category[data-tool-category="operate"]:not(.collapsed) {
position: sticky;
top: 58px;
z-index: 11;
overflow: hidden;
background: linear-gradient(180deg, rgba(238,247,255,.98), rgba(221,237,255,.98));
border: 3px solid rgba(52,112,188,.72);
box-shadow: 0 9px 22px rgba(58,82,112,.18), inset 0 0 0 1px rgba(255,255,255,.86);
}
.tool-category[data-tool-category="operate"] .tool-category-toggle {
background: linear-gradient(180deg, rgba(255,255,255,.98), rgba(231,243,255,.98));
border-bottom-color: rgba(82,128,190,.30);
}
.tool-category[data-tool-category="operate"] .tool-category-body {
grid-template-columns: repeat(6, minmax(0,1fr));
gap: 5px;
background: rgba(238,247,255,.98);
}
.tool-category[data-tool-category="operate"] .tool {
grid-template-columns: 1fr;
min-height: 38px;
padding: 4px;
justify-items: center;
align-items: center;
text-align: center;
background: rgba(255,255,255,.96);
border-color: rgba(82,128,190,.28);
}
.tool-category[data-tool-category="operate"] .tool-label { display: none; }
.tool-category[data-tool-category="operate"] .tool::before {
left: 50%; top: 50%; transform: translate(-50%,-50%) scale(var(--tool-icon-zoom,1));
}
.tool-category[data-tool-category="operate"] .tool-sprite-preview {
left: 50%; top: 50%; transform: translate(-50%,-50%);
}
.tool-category[data-tool-category="operate"] .tool-shortcut {
position: absolute; right: 3px; bottom: 2px; min-width: 13px; height: 13px;
box-sizing: border-box; padding: 0 3px; border: 1px solid rgba(82,128,190,.26);
border-radius: 5px; background: rgba(255,255,255,.88); color: rgba(48,74,112,.86);
font: 700 9px/12px system-ui,sans-serif; text-align: center; pointer-events: none;
}
/* A trash can inside a selection square for range delete. */
.tool[data-tool="area_delete"].tool-emoji-icon::before {
content: "";
box-sizing: border-box;
width: 30px;
height: 30px;
border: 2.5px solid #a8444d;
border-radius: 4px;
background: rgba(255,255,255,.40);
filter: none;
}
.tool[data-tool="area_delete"].tool-emoji-icon::after {
content: "";
display: block;
position: absolute;
left: 50%;
top: 52%;
width: 11px;
height: 13px;
box-sizing: border-box;
transform: translate(-50%,-50%);
border: 2px solid #a8444d;
border-top-width: 3px;
border-radius: 1px 1px 3px 3px;
background: repeating-linear-gradient(90deg, transparent 0 2px, rgba(168,68,77,.75) 2px 3px, transparent 3px 5px);
box-shadow: 0 -5px 0 -3px #a8444d;
z-index: 3;
pointer-events: none;
}
.tool-category[data-tool-category="operate"] .tool[data-tool="area_delete"].tool-emoji-icon::before {
top: 50% !important;
transform: translate(-50%,-50%) !important;
}
/* Colony settings use the colony card's own button palette. */
.colony-card .colony-setting-fold {
border-color: var(--ui-theme-mid);
background: var(--ui-theme-soft);
}
.colony-card .colony-setting-fold > summary {
border-color: var(--ui-theme-mid);
background: linear-gradient(180deg, rgba(255,255,255,.78), var(--ui-theme-soft-2));
color: var(--ink);
}
.colony-card .colony-setting-fold[open] > summary { border-bottom-color: var(--ui-theme-mid); }
.colony-card .colony-setting-section + .colony-setting-section { border-top-color: rgba(var(--ui-theme-rgb),.22); }
.colony-stats { grid-template-columns: 1fr; }
.ground-type-btn.colony-setting-action {
width: 100%;
min-height: 38px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 7px 10px;
border: 1px solid var(--ui-theme-mid);
border-radius: 11px;
background: linear-gradient(180deg, rgba(255,255,255,.76), var(--ui-theme-soft-2));
color: var(--ink);
cursor: pointer;
font: inherit;
}
.ground-type-btn.colony-setting-action:hover { background: rgba(var(--ui-theme-rgb),.16); }
.ground-type-btn.colony-setting-action strong { font-size: 15px; }
.ground-type-btn.colony-setting-action span { color: var(--muted); font-size: 11px; font-weight: 700; }
/* Shared panel-search primitives. */
.panel-search-control {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(0,1fr) auto 32px;
align-items: center;
gap: 5px;
margin-top: 2px;
padding-top: 6px;
border-top: 1px solid rgba(82,128,190,.20);
}
.panel-search-input {
width: 100%; min-width: 0; height: 32px; box-sizing: border-box;
padding: 6px 8px; border: 1px solid rgba(82,128,190,.34); border-radius: 9px;
background: rgba(255,255,255,.94); color: var(--ink); font: 12px/1.2 var(--font,sans-serif);
}
.panel-search-input:focus { outline: 2px solid rgba(40,112,180,.28); border-color: rgba(40,112,180,.58); }
.panel-search-status { min-width: 23px; color: rgba(48,74,112,.86); font: 700 10px/1 system-ui,sans-serif; text-align: center; }
.panel-search-next {
width: 32px; height: 32px; padding: 0; border: 1px solid rgba(82,128,190,.34); border-radius: 9px;
background: rgba(255,255,255,.94); color: #315f91; font: 800 17px/1 system-ui,sans-serif; cursor: pointer;
}
.panel-search-next:disabled { opacity: .36; cursor: default; }
.panel-search-match {
position: relative;
outline: 3px solid rgba(255,190,38,.72) !important;
outline-offset: 1px;
background-color: rgba(255,235,135,.62) !important;
scroll-margin-block: 150px 80px;
}
.panel-search-current {
outline-color: rgba(225,91,38,.92) !important;
box-shadow: 0 0 0 4px rgba(255,160,70,.22) !important;
}
/* v39.16.40: header search uses partial matching and bidirectional navigation. */
.top-panel-search {
flex: 0 1 270px;
width: clamp(190px, 21vw, 270px);
min-width: 190px;
display: grid;
grid-template-columns: minmax(92px, 1fr) auto 30px 30px;
align-items: center;
gap: 4px;
margin: 0 2px 0 0;
padding: 0;
border: 0;
}
.top-panel-search .panel-search-input { height: 36px; }
.panel-search-prev,
.panel-search-next {
width: 30px;
height: 32px;
padding: 0;
border: 1px solid rgba(82,128,190,.34);
border-radius: 9px;
background: rgba(255,255,255,.94);
color: #315f91;
font: 800 16px/1 system-ui,sans-serif;
cursor: pointer;
}
.panel-search-prev:disabled,
.panel-search-next:disabled { opacity: .36; cursor: default; }
/* Match the expanded colony settings surface to the colony graph surface. */
.colony-card .colony-setting-fold[open] {
background: linear-gradient(180deg, rgba(255,255,255,0.70), rgba(246,240,228,0.54));
}
/* Shared in-game confirmations */
.game-confirm-dialog { z-index: 16000; }
.game-confirm-panel { width: min(460px, 100%); }
.game-confirm-message { white-space: pre-line; }
.game-confirm-actions { justify-content: flex-end; }
.game-confirm-actions .btn { width: auto; }
/* Achievements */
.achievement-top-btn {
border-color: rgba(177, 132, 38, .38);
color: #72521a;
background: linear-gradient(180deg, rgba(255, 251, 224, .96), rgba(247, 229, 153, .88));
font-weight: 800;
}
.achievement-top-btn.complete {
border-color: rgba(105, 158, 63, .44);
color: #456729;
background: linear-gradient(180deg, rgba(245, 255, 229, .98), rgba(213, 239, 173, .92));
}
.achievement-dialog-panel { width: min(720px, 100%); }
.achievement-dialog-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.achievement-dialog-head h2 { margin-bottom: 5px; }
.achievement-dialog-kicker {
margin-bottom: 3px;
color: var(--muted);
font-size: 10px;
font-weight: 800;
letter-spacing: .08em;
}
.achievement-close-icon {
width: 34px;
height: 34px;
border: 1px solid rgba(84, 68, 48, .16);
border-radius: 999px;
background: rgba(255, 255, 255, .76);
color: var(--ink);
font-size: 22px;
line-height: 1;
cursor: pointer;
}
.achievement-shared-status {
margin: 10px 0;
padding: 8px 10px;
border: 1px solid rgba(104, 86, 61, .12);
border-radius: 10px;
background: rgba(241, 235, 222, .54);
color: var(--muted);
font-size: 11px;
}
.achievement-list { display: grid; gap: 10px; }
.achievement-group {
overflow: hidden;
border: 1px solid rgba(105, 87, 60, .15);
border-radius: 13px;
background: rgba(247, 243, 234, .72);
}
.achievement-group-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
color: #554b3e;
cursor: pointer;
list-style: none;
user-select: none;
}
.achievement-group-summary::-webkit-details-marker { display: none; }
.achievement-group-summary::before {
content: "\25b6";
flex: 0 0 auto;
color: #8b7657;
font-size: 10px;
transition: transform .16s ease;
}
.achievement-group[open] > .achievement-group-summary::before { transform: rotate(90deg); }
.achievement-group-summary strong { margin-right: auto; font-size: 13px; }
.achievement-group-count {
color: var(--muted);
font-size: 10px;
font-weight: 800;
font-variant-numeric: tabular-nums;
}
.achievement-group-body {
display: grid;
gap: 9px;
padding: 0 9px 9px;
}
.achievement-group-empty {
margin: 0;
padding: 12px;
border-radius: 10px;
background: rgba(255, 255, 255, .58);
color: var(--muted);
font-size: 11px;
}
.achievement-entry {
--achievement-rate: 0%;
position: relative;
isolation: isolate;
overflow: hidden;
display: grid;
grid-template-columns: 42px minmax(0, 1fr);
gap: 10px;
align-items: center;
min-height: 62px;
padding: 9px 11px;
border: 1px solid rgba(105, 87, 60, .15);
border-radius: 13px;
background: linear-gradient(180deg, rgba(255, 255, 255, .88), rgba(244, 238, 226, .78));
cursor: default;
}
.achievement-entry:focus-visible {
outline: 2px solid rgba(85, 119, 149, .58);
outline-offset: 2px;
}
.achievement-entry::before {
content: "";
position: absolute;
z-index: 0;
inset: 0 auto 0 0;
pointer-events: none;
width: var(--achievement-rate);
background: linear-gradient(90deg, rgba(129, 158, 188, .27), rgba(156, 184, 207, .17));
border-right: 1px solid rgba(85, 119, 149, .20);
transition: width .32s ease;
}
.achievement-entry > * {
position: relative;
z-index: 1;
}
.achievement-entry.unlocked {
border-color: rgba(173, 129, 35, .38);
background: linear-gradient(180deg, rgba(255, 253, 235, .96), rgba(248, 231, 163, .70));
}
.achievement-entry.unlocked::before {
background: linear-gradient(90deg, rgba(229, 179, 51, .34), rgba(244, 211, 102, .22));
border-right-color: rgba(173, 129, 35, .28);
}
.achievement-entry-icon {
width: 38px;
height: 38px;
display: grid;
place-items: center;
border-radius: 11px;
background: rgba(106, 91, 68, .09);
color: #8b7657;
font-size: 22px;
}
.achievement-entry.unlocked .achievement-entry-icon {
background: rgba(242, 196, 74, .22);
color: #b67b13;
}
.achievement-entry-copy { min-width: 0; }
.achievement-entry-heading {
display: flex;
align-items: baseline;
gap: 9px;
min-width: 0;
}
.achievement-entry-copy strong {
flex: 0 0 auto;
display: block;
font-size: 14px;
line-height: 1.35;
}
.achievement-entry-description {
min-width: 0;
color: #665d50;
font-size: 11px;
line-height: 1.45;
}
.achievement-entry.locked .achievement-entry-description {
color: #8b8378;
font-style: italic;
}
.achievement-entry-progress {
margin-top: 4px;
color: #526b82;
font-size: 10px;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.achievement-entry-meta {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 6px 12px;
margin-top: 5px;
color: var(--muted);
font-size: 10px;
font-weight: 700;
}
.achievement-entry-percentage {
flex: 0 0 auto;
margin-left: auto;
color: #5d6570;
font-variant-numeric: tabular-nums;
}
@media (max-width: 620px) {
.achievement-entry-heading { display: block; }
.achievement-entry-description { display: block; margin-top: 2px; }
}
.achievement-dialog-actions { justify-content: flex-end; flex-wrap: wrap; }
.achievement-dialog-actions .btn { width: auto; }
.achievement-dialog-actions .achievement-reset-btn { margin-right: auto; }
.achievement-toast {
position: fixed;
top: 18px;
left: 18px;
right: auto;
z-index: 14000;
width: min(360px, calc(100vw - 36px));
display: grid;
grid-template-columns: 44px minmax(0, 1fr);
gap: 10px;
align-items: center;
padding: 11px 13px;
border: 1px solid rgba(184, 137, 37, .42);
border-radius: 14px;
background: rgba(255, 249, 220, .97);
color: #57401d;
box-shadow: 0 16px 44px rgba(45, 34, 20, .24);
transform-origin: center;
transition: opacity .18s ease, transform .18s ease, filter .18s ease, visibility .18s ease;
}
.achievement-toast.absorbing {
opacity: 0;
transform: translate(var(--achievement-toast-dx, 0px), var(--achievement-toast-dy, 0px)) scale(.08);
filter: blur(2px) saturate(1.4);
border-radius: 999px;
pointer-events: none;
transition: opacity .72s ease-in, transform .72s cubic-bezier(.55, .02, .86, .3), filter .72s ease-in, border-radius .72s ease-in;
}
.achievement-top-btn.achievement-absorb-target {
animation: achievement-target-pulse .22s ease-out .54s both;
}
@keyframes achievement-target-pulse {
0% { transform: scale(1); }
45% { transform: scale(1.09); }
100% { transform: scale(1); }
}
.achievement-toast.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateY(-10px);
}
.achievement-toast-icon {
width: 40px;
height: 40px;
display: grid;
place-items: center;
border-radius: 12px;
background: #efc34c;
color: #fff8d8;
font-size: 24px;
}
.achievement-toast-copy { min-width: 0; }
.achievement-toast-copy small { display: block; margin-bottom: 2px; color: #8a6b2c; font-size: 10px; font-weight: 800; }
.achievement-toast-copy strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.achievement-toast-condition { display: block; margin-top: 3px; max-width: 360px; color: #6f6248; font-size: 11px; line-height: 1.35; white-space: normal; }
@media (max-width: 980px) {
.achievement-toast {
top: calc(env(safe-area-inset-top, 0px) + 10px);
left: 12px;
right: auto;
width: min(360px, calc(100vw - 24px));
}
.achievement-dialog-actions { flex-direction: column-reverse; }
.achievement-dialog-actions .btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
.achievement-toast.absorbing { transition-duration: .12s; }
.achievement-top-btn.achievement-absorb-target { animation: none; }
}
/* v39.16.56: keep the upper-limit heading on one line in the compact panel. */
.colony-limit-section .colony-setting-section-head > span { font-size: 10.8px; white-space: nowrap; }
.colony-limit-section .colony-setting-section-head > small { font-size: 9px; white-space: nowrap; }
/* v39.16.60: align both upper-limit sliders independently of label length. */
.colony-limit-grid { display: grid; gap: 7px; }
.colony-limit-row { display: grid; grid-template-columns: 82px minmax(0, 1fr) 58px; align-items: center; gap: 8px; min-width: 0; }
.colony-limit-row > span { min-width: 0; font-size: 11px; font-weight: 700; white-space: nowrap; }
.colony-limit-slider { width: 100%; min-width: 0; margin: 0; }
.colony-limit-number { width: 58px; min-width: 0; box-sizing: border-box; }
@media (max-width: 520px) {
.colony-limit-row { grid-template-columns: 72px minmax(0, 1fr) 54px; gap: 6px; }
.colony-limit-number { width: 54px; }
}

View file

@ -1,80 +1,49 @@
/* CSS split file; preserve manifest load order. */
.tool[data-tool="sleep_drug"] { --tool-icon: url("../assets/ui/tool_sleep_drug.webp"); }
.tool[data-tool="nest_box"] { --tool-icon: url("../assets/ui/tool_nest_box.webp"); }
.tool[data-tool="ant_nest"] { --tool-icon: url("../assets/ui/tool_ant_nest.webp"); }
.tool[data-tool="ball"] { --tool-icon: url("../assets/ui/tool_ball.webp"); }
.tool[data-tool="poke"] { --tool-icon: none; }
.tool[data-tool="poke"]::before {
content: "\1F448";
/* CSS split file; preserve manifest load order. Tool image paths come from TOOL_DEFINITIONS. */
.tool[data-tool="ball"] { --tool-icon-zoom: 1.36; }
.tool[data-tool="shoot"] { --tool-icon-zoom: 1.48; }
.tool.tool-emoji-icon { --tool-icon: none; }
.tool.tool-emoji-icon::before {
content: var(--tool-emoji, "");
background: none;
font-size: 25px;
display: grid;
place-items: center;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
font-size: 34px;
line-height: 1;
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
}
.tool[data-tool="poke"]::after {
content: "\1F448";
background: none;
font-size: 48px;
.tool.tool-emoji-icon::after { content: none; display: none; }
.tool[data-tool="poke"].tool-emoji-icon::before,
.tool[data-tool="water_hose"].tool-emoji-icon::before { font-size: 25px; }
.tool[data-tool="rope"].tool-emoji-icon::before,
.tool[data-tool="rod"].tool-emoji-icon::before,
.tool[data-tool="reciprocator"].tool-emoji-icon::before,
.tool[data-tool="undo"].tool-emoji-icon::before,
.tool[data-tool="redo"].tool-emoji-icon::before { font-size: 29px; font-family: var(--font, sans-serif); }
.tool[data-tool="delete"] { --tool-icon-zoom: 1.42; }
.tool[data-tool="area_delete"] { --tool-icon-zoom: 1; }
.tool[data-tool="pinch"].tool-emoji-icon::before { font-size: 22px; }
.tool[data-tool="bell"].tool-emoji-icon::before { font-size: 21px; z-index: 1; }
.tool[data-tool="area_delete"].tool-emoji-icon::before {
font-size: 31px;
line-height: 1;
opacity: 0.11;
right: -6px;
left: auto;
top: 50%;
bottom: auto;
transform: translateY(-50%);
width: 54px;
height: 54px;
color: #b14f58;
text-shadow: 0 1px 0 rgba(255,255,255,0.75), 0 2px 5px rgba(146,54,62,0.14);
filter: none;
transform: translateY(-54%) scale(var(--tool-icon-zoom, 1));
}
.tool-category[data-tool-category="operate"] .tool[data-tool="area_delete"].tool-emoji-icon::before {
top: 42% !important;
transform: translate(-50%, -50%) scale(var(--tool-icon-zoom, 1)) !important;
}
.tool[data-tool="pinch"] { --tool-icon: none; }
.tool[data-tool="pinch"]::before {
content: "\1F90F";
background: none;
font-size: 25px;
line-height: 1;
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
}
.tool[data-tool="pinch"]::after {
content: "\1F90F";
background: none;
font-size: 48px;
line-height: 1;
opacity: 0.10;
right: -6px;
left: auto;
top: 50%;
bottom: auto;
transform: translateY(-50%);
width: 54px;
height: 54px;
}
.tool[data-tool="water_hose"] { --tool-icon: none; }
.tool[data-tool="water_hose"]::before {
content: "\1F6BF";
background: none;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
font-size: 24px;
line-height: 1;
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
}
.tool[data-tool="water_hose"]::after {
content: "\1F6BF";
background: none;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
font-size: 46px;
line-height: 1;
opacity: 0.10;
right: -6px;
left: auto;
top: 50%;
bottom: auto;
transform: translateY(-50%);
width: 54px;
height: 54px;
}
.tool[data-tool="delete"] { --tool-icon: url("../assets/ui/tool_delete.webp"); }
.tool[data-tool="new"] { --tool-icon: url("../assets/ui/tool_new.webp"); }
.tool[data-tool="observe"],
.tool[data-tool="new"] { --tool-icon-zoom: 1.20; }
.tool[data-tool="giant_drug"] { --tool-icon-zoom: 1.58; }
.tool[data-tool="dwarf_drug"],
.tool[data-tool="oshibyo"] { --tool-icon-zoom: 0.62; }
.tool.selected {
background: linear-gradient(180deg, #ffffff, #efffde);
border-color: rgba(103, 178, 44, 0.52);
@ -88,23 +57,21 @@
margin: 10px 0 0;
}
.tool-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tool-head h2 { margin-bottom: 0; }
.btn.mini { padding: 6px 10px; font-size: 12px; }
.tool-body { margin-top: 12px; }
.tool-palette { display: flex; flex-direction: column; gap: 9px; }
.tool-body { margin-top: 10px; }
.tool-palette { display: flex; flex-direction: column; gap: 8px; }
.tool-category {
border: 1px solid rgba(84,68,48,0.10);
border: 1px solid var(--tool-cat-line, rgba(84,68,48,0.10));
border-radius: 14px;
background: rgba(255,255,255,0.34);
background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,255,255,0.36)), rgba(255,255,255,0.28));
overflow: hidden;
}
.tool-category-toggle {
width: 100%;
border: 0;
border-bottom: 1px solid rgba(84,68,48,0.08);
background: rgba(255,252,244,0.66);
padding: 8px 10px;
border-bottom: 1px solid var(--tool-cat-line, rgba(84,68,48,0.08));
background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,252,244,0.66)), var(--tool-cat-b, rgba(255,252,244,0.54)));
padding: 7px 10px;
text-align: left;
cursor: pointer;
font-weight: 700;
@ -112,7 +79,7 @@
}
.tool-category-toggle::before { content: "\25be "; display: inline-block; width: 16px; color: var(--muted); }
.tool-category.collapsed .tool-category-toggle::before { content: "\25b8 "; }
.tool-category-body { padding: 8px; }
.tool-category-body { padding: 7px; }
.tool-category.collapsed .tool-category-body { display: none; }
.selected-data-groups {
@ -130,8 +97,8 @@
.selected-category-toggle {
width: 100%;
border: 0;
border-bottom: 1px solid rgba(84,68,48,0.08);
background: rgba(255,252,244,0.66);
border-bottom: 1px solid var(--tool-cat-line, rgba(84,68,48,0.08));
background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,252,244,0.66)), var(--tool-cat-b, rgba(255,252,244,0.54)));
padding: 8px 10px;
text-align: left;
cursor: pointer;
@ -146,20 +113,22 @@
.selected-category-body .info-row:last-child { border-bottom: 0; }
.tool-size-badge {
position: absolute;
right: 7px;
bottom: 6px;
min-width: 20px;
padding: 2px 5px;
right: 4px;
bottom: 3px;
min-width: 22px;
padding: 1px 4px;
border-radius: 999px;
background: rgba(42,36,29,0.68);
background: rgba(42,36,29,0.72);
color: #fff;
font-size: 10px;
font-size: 9.5px;
line-height: 1;
text-align: center;
box-shadow: 0 1px 4px rgba(42,36,29,0.16);
pointer-events: none;
pointer-events: auto;
cursor: pointer;
z-index: 4;
}
.chart-tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; margin: 3px 0 8px; }
.chart-tabs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin: 3px 0 8px; }
.chart-tabs button { border: 1px solid var(--line); background: rgba(255,255,255,0.62); border-radius: 999px; padding: 5px 4px; font-size: 11px; cursor: pointer; }
.chart-tabs button.active { background: #efffde; border-color: rgba(103,178,44,0.46); font-weight: 700; }
.log-accident { border-left-color: rgba(226, 136, 54, 0.76); }
@ -182,14 +151,6 @@
.stat span { display: block; font-size: 11px; color: var(--muted); }
.stat strong { font-size: 18px; }
.meter-row {
display: grid;
grid-template-columns: 72px 1fr;
align-items: center;
gap: 8px;
font-size: 12px;
margin: 6px 0;
}
meter {
width: 100%;
height: 12px;
@ -321,7 +282,7 @@ meter {
margin-right: 6px;
padding: 2px 5px;
border-radius: 999px;
font-size: 9px;
font-size: 9.5px;
line-height: 1.1;
text-align: center;
background: rgba(84,68,48,0.08);
@ -336,56 +297,65 @@ meter {
.log-grass { border-left-color: rgba(72, 148, 67, 0.68); }
.log-weather { border-left-color: rgba(92, 142, 210, 0.62); }
.log-relation { border-left-color: rgba(204, 126, 176, 0.62); }
.log-observe { border-left-color: rgba(175, 146, 86, 0.62); }
@media (max-width: 980px) {
#app { padding: 10px; }
.topbar { align-items: flex-start; flex-direction: column; }
.top-left { align-items: flex-start; flex-direction: column; }
.brand { align-items: center; flex-direction: row; gap: 10px; }
.layout { grid-template-columns: 1fr; }
.panel { max-height: none; overflow: visible; }
#gameCanvas { height: var(--field-mobile-height, 66vh); min-height: var(--field-mobile-min-height, 460px); }
.stage-wrap { min-height: var(--field-mobile-min-height, 460px); }
.field-choice-grid { grid-template-columns: 1fr; }
}
.tree-note {
color: var(--muted);
font-size: 12px;
line-height: 1.45;
margin: 0 0 10px;
padding: 8px 10px;
border-radius: 10px;
background: rgba(255,255,255,0.54);
border: 1px solid rgba(84,68,48,0.10);
}
.log-event { border-left-color: rgba(132, 120, 210, 0.62); }
.log-life { border-left-color: rgba(105, 166, 96, 0.68); }
.log-danger { border-left-color: rgba(214, 104, 70, 0.72); }
.log-social { border-left-color: rgba(184, 116, 184, 0.66); }
.log-living { border-left-color: rgba(183, 151, 74, 0.68); }
.log-environment { border-left-color: rgba(82, 142, 170, 0.66); }
.log-observe { border-left-color: rgba(142, 132, 116, 0.62); }
/* Clearer tool category grouping. */
.tool-category[data-tool-category="operate"] { --tool-cat-a: rgba(224,239,255,0.82); --tool-cat-b: rgba(203,226,255,0.74); --tool-cat-line: rgba(82,128,190,0.26); }
.tool-category[data-tool-category="food"] { --tool-cat-a: rgba(236,252,219,0.82); --tool-cat-b: rgba(214,244,194,0.74); --tool-cat-line: rgba(96,156,72,0.26); }
.tool-category[data-tool-category="medicine"] { --tool-cat-a: rgba(243,231,255,0.84); --tool-cat-b: rgba(230,214,252,0.78); --tool-cat-line: rgba(136,94,184,0.28); }
.tool-category[data-tool-category="habitat"] { --tool-cat-a: rgba(255,245,218,0.84); --tool-cat-b: rgba(250,229,186,0.76); --tool-cat-line: rgba(174,125,54,0.28); }
.tool-category[data-tool-category="physics"] { --tool-cat-a: rgba(226,247,246,0.84); --tool-cat-b: rgba(199,232,231,0.76); --tool-cat-line: rgba(70,145,148,0.28); }
.tool-category[data-tool-category="hazard"] { --tool-cat-a: rgba(255,230,224,0.84); --tool-cat-b: rgba(250,207,197,0.78); --tool-cat-line: rgba(189,88,66,0.30); }
.tool-category { border-color: var(--tool-cat-line, rgba(84,68,48,0.10)); background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,255,255,0.36)), rgba(255,255,255,0.28)); }
.tool-category-toggle { background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,252,244,0.66)), var(--tool-cat-b, rgba(255,252,244,0.54))); border-bottom-color: var(--tool-cat-line, rgba(84,68,48,0.08)); }
.tool[data-tool="ball"] { --tool-icon: none; }
.tool[data-tool="ball"]::before {
content: "⚽";
background: none;
display: grid;
place-items: center;
font-size: 28px;
line-height: 1;
opacity: 1;
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
}
.tool[data-tool="ball"]::after { background: none; }
@media (min-width: 921px) {
.mobile-mode-controls { display: none !important; }
}
.tool-pick-mode-control.tool-emoji-icon::before {
opacity: 0.18;
color: #3b78a0;
font-size: 29px;
filter: none;
}
.tool-pick-mode-control {
--tool-icon-zoom: 1;
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(235,246,255,0.88));
border-color: rgba(48,126,176,0.34);
}
.tool-pick-mode-control .tool-pick-mode-value {
position: absolute;
left: 50%;
right: auto;
bottom: 5px;
transform: translateX(-50%);
min-width: 38px;
padding: 3px 6px;
border-radius: 999px;
z-index: 4;
background: rgba(25,85,128,0.94);
color: #fff;
font-size: 8.5px;
line-height: 1;
text-align: center;
box-shadow: 0 1px 4px rgba(25,85,128,0.18);
}
.tool-pick-mode-control[data-pick-mode="grid20"],
.tool-pick-mode-control[data-pick-mode="grid40"],
.tool-pick-mode-control[data-pick-mode="grid60"] {
background: linear-gradient(180deg, #ffffff, #eaf7ff);
border-color: rgba(38,132,190,0.56);
box-shadow: inset 0 0 0 1px rgba(84,176,226,0.20), 0 8px 20px rgba(59,133,177,0.10);
}
.chart-range-tabs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin: -2px 0 7px; }
.chart-range-tabs button { border: 1px solid var(--line); background: rgba(255,255,255,0.48); border-radius: 8px; padding: 4px 4px; font-size: 10px; cursor: pointer; color: var(--muted); }
.chart-range-tabs button.active { background: rgba(239,255,222,0.92); border-color: rgba(103,178,44,0.42); color: var(--text); font-weight: 700; }

View file

@ -1,70 +1,4 @@
/* CSS split file; preserve manifest load order. */
.loading-screen.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
.loading-kicker { font-size: 12px; letter-spacing: .14em; color: rgba(116,96,76,.82); margin-bottom: 6px; }
.loading-title { margin: 0 0 8px; font-size: 28px; color: #3f3429; }
.loading-text { margin: 0 0 12px; color: rgba(90,74,58,.86); }
.loading-sprite-strip { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; margin: 8px 0 14px; }
.loading-sprite-card { border-radius: 16px; background: linear-gradient(180deg,rgba(255,255,255,.84),rgba(248,243,232,.90)); border: 1px solid rgba(126,104,80,.12); min-height: 92px; display: grid; place-items: center; padding: 8px; box-shadow: 0 8px 18px rgba(83,63,44,.06); }
.loading-sprite-card img { width: 68px; height: 68px; object-fit: contain; }
.loading-progress-track { width: 100%; height: 14px; border-radius: 999px; overflow: hidden; background: rgba(132,164,217,.16); border: 1px solid rgba(92,126,181,.14); }
.loading-progress-bar { height: 100%; border-radius: inherit; background: linear-gradient(90deg,#92d1ff,#a3dfa0 58%,#ffe192); transition: width .18s ease; }
.loading-progress-meta { margin-top: 8px; display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: rgba(88,73,60,.82); }
.loading-hint { margin: 10px 0 0; font-size: 12px; line-height: 1.55; color: rgba(111,92,73,.78); }
.collapsible-card { padding-top: 10px; }
.panel-card-toggle { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; border: 0; border-radius: 14px; padding: 10px 12px; cursor: pointer; font: inherit; color: var(--ink); background: rgba(255,255,255,.48); box-shadow: inset 0 1px 0 rgba(255,255,255,.54); }
.panel-card-title { font-size: 17px; font-weight: 700; letter-spacing: .03em; }
.panel-card-state { font-size: 12px; color: rgba(98,82,64,.78); padding-left: 16px; position: relative; }
.panel-card-state::before { content: "▾"; position: absolute; left: 0; top: 50%; transform: translateY(-52%); color: rgba(98,82,64,.72); }
.collapsible-card.collapsed .panel-card-state::before { content: "▸"; }
.collapsible-card.collapsed .panel-card-body { display: none; }
.panel-card-body { padding-top: 12px; }
.tool-card { background: linear-gradient(180deg, rgba(255,247,252,.92), rgba(249,240,249,.90)); border-color: rgba(200,153,186,.34); }
.colony-card { background: linear-gradient(180deg, rgba(244,250,238,.92), rgba(235,246,230,.90)); border-color: rgba(132,178,128,.34); }
.log-card { background: linear-gradient(180deg, rgba(255,246,238,.92), rgba(252,239,226,.90)); border-color: rgba(207,157,121,.34); }
.tool-card .panel-card-toggle { background: linear-gradient(180deg,rgba(255,239,248,.82),rgba(253,234,246,.84)); }
.colony-card .panel-card-toggle { background: linear-gradient(180deg,rgba(240,252,237,.82),rgba(230,247,228,.86)); }
.log-card .panel-card-toggle { background: linear-gradient(180deg,rgba(255,242,231,.84),rgba(253,235,220,.86)); }
.sound-category-controls,
.log-push-controls {
margin-top: 10px;
padding: 10px;
border-radius: 14px;
border: 1px solid rgba(84,68,48,.12);
background: rgba(255,255,255,.46);
font-size: 12px;
}
.control-group-title { font-weight: 700; margin-bottom: 7px; color: rgba(58,48,39,.88); }
.sound-category-controls { display: grid; grid-template-columns: 1fr; gap: 6px; }
.sound-category-controls label,
.push-master,
.push-kind-grid label { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.sound-category-controls input,
.push-master input,
.push-kind-grid input { accent-color: #82bf66; }
.log-push-controls { display: flex; flex-direction: column; gap: 8px; }
.push-kind-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px 8px; }
.log-push-overlay { position: fixed; left: 18px; bottom: 18px; z-index: 9998; width: min(320px, calc(100vw - 36px)); display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
.log-push-toast { position: relative; overflow: hidden; padding: 10px 12px 10px 17px; border-radius: 15px; background: rgba(255,252,246,.94); border: 1px solid rgba(84,68,48,.16); box-shadow: 0 12px 32px rgba(44,34,25,.18); animation: logPushIn .18s ease-out; }
.log-push-toast::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 5px; background: #a5c77a; }
.log-push-toast .log-push-tag { display: inline-block; margin-right: 6px; font-size: 11px; font-weight: 700; color: rgba(54,45,38,.88); }
.log-push-toast .log-push-time { font-size: 10px; color: rgba(104,88,72,.72); }
.log-push-toast .log-push-text { margin-top: 3px; font-size: 12px; line-height: 1.35; color: rgba(48,40,33,.92); }
.log-push-toast.log-push-death::before { background: #8f817a; }
.log-push-toast.log-push-accident::before { background: #ee9e5d; }
.log-push-toast.log-push-birth::before { background: #98cf7a; }
.log-push-toast.log-push-fight::before { background: #e47b80; }
.log-push-toast.log-push-relation::before { background: #b99be0; }
.log-push-toast.log-push-food::before { background: #e9bd61; }
.log-push-toast.log-push-weather::before { background: #7bbbdc; }
.log-push-toast.log-push-grass::before { background: #8cc77a; }
.log-push-toast.leaving { opacity: 0; transform: translateY(8px); transition: opacity .22s ease, transform .22s ease; }
@keyframes logPushIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 920px) { .loading-sprite-strip { grid-template-columns: repeat(2,minmax(0,1fr)); } }
/* v15.12.1: top-right sound settings panel */
/* Sound menu */
.sound-menu { position: relative; display: inline-block; }
.sound-menu-btn.active { background: linear-gradient(180deg, #f3ffe9, #e7f8db); border-color: rgba(103,178,44,.46); }
.sound-panel {
@ -88,40 +22,8 @@
.sound-panel input:disabled + span { opacity: .55; }
.sound-master { font-weight: 700; }
.sound-panel-divider { height: 1px; background: rgba(84,68,48,.12); margin: 2px 0; }
@media (max-width: 760px) { .sound-panel { right: auto; left: 0; } }
/* v15.12.3: collapsed event panel should be genuinely thin */
.log-card.collapsed {
min-height: 0 !important;
padding: 5px 7px !important;
flex: 0 0 auto !important;
}
.log-card.collapsed .panel-card-toggle {
min-height: 30px;
padding: 5px 9px;
border-radius: 11px;
}
.log-card.collapsed .panel-card-title {
font-size: 13px;
line-height: 1.1;
}
.log-card.collapsed .panel-card-state {
font-size: 10px;
}
/* v15.12.4: event log scroll chains to the right menu at top/bottom */
.log {
overscroll-behavior-y: auto !important;
}
/* v15.12.4: sound settings live only in the top-right sound menu */
.log-card .sound-category-controls {
display: none !important;
}
/* v15.17.0: mobile layout isolation and canvas touch gestures */
/* Shared layout and gesture guards */
#app { overflow-x: clip; }
.layout { position: relative; z-index: 2; }
.panel { position: relative; z-index: 3; }
@ -142,100 +44,408 @@
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 980px) {
.layout {
position: relative;
z-index: 4;
}
.panel {
position: relative;
z-index: 5;
isolation: isolate;
}
.lineage-main-card {
z-index: 1;
width: 100%;
max-width: 100%;
margin-top: 14px;
clear: both;
}
.lineage-main-content {
height: auto;
min-height: 340px;
max-height: 72vh;
overflow: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
touch-action: pan-x pan-y pinch-zoom;
}
.lineage-family-tree {
overflow: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
touch-action: pan-x pan-y pinch-zoom;
}
.lineage-family-tree .lineage-tree-panel {
max-width: none;
}
.log {
overscroll-behavior-y: auto !important;
}
body.touch-mobile-ui .tool-tip-popover {
max-width: min(520px, calc(100vw - 24px));
}
body.touch-mobile-ui .tool-category:not([data-tool-category="operate"]).collapsed .tool-category-body {
display: none;
}
/* Item icons and mobile input mode */
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.20.16"); }
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.20.16"); }
.tool[data-tool="niteropu"] { --tool-icon: url("../assets/ui/tool_niteropu.webp?v=15.20.16"); }
.tool[data-tool="ammo"] { --tool-icon: url("../assets/ui/tool_ammo.webp?v=15.20.16"); }
.tool[data-tool="mystery_drug"] { --tool-icon: url("../assets/ui/tool_mystery_drug.webp?v=15.20.16"); }
.tool[data-tool="mercury"] { --tool-icon: url("../assets/ui/tool_mercury.webp?v=15.20.16"); }
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.20.16"); }
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.20.16"); }
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.20.16"); }
.mobile-mode-controls {
/* Mobile input controls */
.mobile-mode-controls,
.mobile-rotate-controls {
display: none;
position: absolute;
left: 10px;
bottom: 10px;
z-index: 24;
gap: 5px;
gap: 6px;
padding: 6px;
border-radius: 14px;
background: rgba(255, 251, 245, 0.90);
background: rgba(255, 251, 245, 0.92);
box-shadow: 0 8px 22px rgba(80,60,35,.18);
border: 1px solid rgba(120, 95, 60, .16);
backdrop-filter: blur(8px);
}
.mobile-mode-controls button {
.mobile-mode-controls {
left: 10px;
bottom: 10px;
gap: 5px;
background: rgba(255, 251, 245, 0.90);
}
.mobile-rotate-controls {
right: 10px;
bottom: 58px;
}
.mobile-mode-controls button,
.mobile-rotate-controls button {
border: 0;
border-radius: 10px;
padding: 7px 8px;
padding: 7px 10px;
font-size: 12px;
font-weight: 800;
color: #65533f;
background: rgba(255,255,255,.78);
background: rgba(255,255,255,.82);
box-shadow: inset 0 -1px 0 rgba(0,0,0,.05);
}
.mobile-mode-controls button { padding: 7px 8px; background: rgba(255,255,255,.78); }
.mobile-mode-controls button.active {
color: #3d321f;
background: linear-gradient(180deg, #ffe9a8, #ffd376);
box-shadow: 0 3px 10px rgba(150, 105, 30, .20);
}
body.touch-mobile-ui .mobile-mode-controls:not([hidden]) { display: flex; }
body:not(.touch-mobile-ui) .mobile-mode-controls { display: none !important; }
.mobile-rotate-controls button:active { transform: translateY(1px); }
body.touch-mobile-ui .mobile-mode-controls:not([hidden]),
body.touch-mobile-ui .mobile-rotate-controls:not([hidden]) { display: flex; }
body:not(.touch-mobile-ui) .mobile-mode-controls,
body:not(.touch-mobile-ui) .mobile-rotate-controls { display: none !important; }
body.touch-mobile-ui.mobile-mode-camera #gameCanvas { cursor: grab; }
body.touch-mobile-ui.mobile-mode-tool #gameCanvas { cursor: crosshair; }
body.touch-mobile-ui.mobile-mode-family .lineage-main-card { z-index: 9; }
/* Keep panel themes on narrow layouts. */
.card.tool-card { --ui-theme-rgb: 0, 114, 178; background: linear-gradient(180deg, rgba(242, 248, 252, 0.94), rgba(232, 243, 250, 0.91)); border-color: rgba(0, 114, 178, 0.24); }
.card.selected-card { --ui-theme-rgb: 204, 121, 167; background: linear-gradient(180deg, rgba(253, 245, 250, 0.94), rgba(249, 235, 244, 0.91)); border-color: rgba(204, 121, 167, 0.24); }
.card.colony-card { --ui-theme-rgb: 0, 158, 115; background: linear-gradient(180deg, rgba(240, 250, 247, 0.94), rgba(229, 245, 239, 0.91)); border-color: rgba(0, 158, 115, 0.24); }
.card.log-card { --ui-theme-rgb: 230, 159, 0; background: linear-gradient(180deg, rgba(255, 249, 236, 0.94), rgba(253, 242, 215, 0.91)); border-color: rgba(230, 159, 0, 0.24); }
.tool[data-tool="freeze"] { --tool-icon: none; }
.tool[data-tool="freeze"]::before {
content: "🧊";
background: none;
font-size: 24px;
line-height: 1;
text-align: center;
/* Back to canvas affordance */
.back-to-game-btn {
position: fixed;
right: calc(env(safe-area-inset-right, 0px) + 12px);
bottom: calc(env(safe-area-inset-bottom, 0px) + 68px);
z-index: 12000;
border: 1px solid rgba(103,178,44,.38);
border-radius: 999px;
padding: 9px 12px;
background: rgba(246, 255, 236, 0.94);
color: #3f5f2b;
font-weight: 900;
font-size: 12px;
box-shadow: 0 10px 25px rgba(54, 76, 40, .20);
backdrop-filter: blur(8px);
}
.back-to-game-btn[hidden] { display: none !important; }
@media (min-width: 981px) {
html,
body {
overflow-x: hidden;
overflow-y: auto;
}
#app {
width: min(1480px, 100vw);
padding: 18px;
overflow: visible;
}
.topbar { gap: 12px; }
.top-left { max-width: 100%; }
.brand,
.brand h1,
h1 {
flex: 0 0 auto;
min-width: max-content;
max-width: none !important;
overflow: visible !important;
text-overflow: clip !important;
}
.top-actions { margin-left: auto; }
.top-actions .btn,
.top-actions .sound-menu-btn,
.room-link {
min-height: 38px;
padding: 8px 10px;
font-size: 12px;
line-height: 1.12;
}
.room-link {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
}
.layout {
display: grid;
grid-template-columns: minmax(680px, 1fr) minmax(320px, 390px);
align-items: start;
}
.panel {
position: sticky;
top: 12px;
align-self: start;
max-height: calc(100vh - 24px);
overflow-y: auto;
overflow-x: hidden;
}
.panel-quick-tabs {
position: sticky;
top: 0;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.panel-quick-tabs [data-panel-tab="game"] { display: none !important; }
.lineage-main-card {
display: block;
visibility: visible;
width: auto;
margin: 16px 0 0;
}
.lineage-main-content {
height: min(78vh, 980px);
min-height: 520px;
max-height: calc(100vh - 120px);
overflow: auto;
overscroll-behavior: auto;
}
.back-to-game-btn { display: none !important; }
}
@media (max-width: 980px) {
html,
body {
width: 100%;
max-width: 100%;
min-height: 100%;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior-y: auto;
}
#app {
width: 100%;
max-width: 100%;
padding: 10px max(10px, env(safe-area-inset-right, 0px)) calc(env(safe-area-inset-bottom, 0px) + 104px) max(10px, env(safe-area-inset-left, 0px));
overflow-x: clip;
overflow-y: visible;
}
.topbar,
.top-left,
.brand,
.layout,
.stage-wrap,
.panel,
.card,
.lineage-main-card {
min-width: 0;
max-width: 100%;
box-sizing: border-box;
}
.topbar {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.top-left {
align-items: flex-start;
flex-direction: column;
gap: 8px;
}
.brand {
align-items: center;
flex-direction: row;
gap: 10px;
}
.brand h1,
h1 {
max-width: 100%;
overflow: visible;
text-overflow: clip;
white-space: nowrap;
font-size: clamp(20px, 7vw, 30px);
}
.top-actions {
justify-content: flex-start;
gap: 5px;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding: 1px 0 5px;
-webkit-overflow-scrolling: touch;
}
.top-actions > .btn,
.top-actions > .sound-menu,
.top-actions .sound-menu-btn {
flex: 0 0 auto;
white-space: nowrap;
}
.top-actions .btn,
.top-actions .sound-menu-btn {
min-height: 34px;
padding: 7px 9px;
font-size: 11px;
}
.sound-panel { right: auto; left: 0; }
.layout {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
overflow: visible;
}
.field-choice-grid { grid-template-columns: 1fr; }
.reset-preset-choice-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
#gameCanvas {
height: var(--field-mobile-height, 66vh);
min-height: var(--field-mobile-min-height, 460px);
}
.stage-wrap {
position: relative;
width: 100%;
min-height: var(--field-mobile-min-height, 460px);
overflow: hidden;
}
.panel {
position: relative !important;
top: auto !important;
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-height: none !important;
height: auto !important;
overflow: visible !important;
isolation: auto;
}
body.touch-mobile-ui .panel-quick-tabs,
.panel-quick-tabs {
position: fixed;
left: max(8px, env(safe-area-inset-left, 0px));
right: max(8px, env(safe-area-inset-right, 0px));
bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
top: auto;
z-index: 12002;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 4px;
width: auto;
max-width: none;
margin: 0;
padding: 6px;
border: 1px solid rgba(102, 169, 215, 0.34);
border-radius: 16px;
background: rgba(244, 250, 247, 0.95);
box-shadow: 0 12px 28px rgba(42, 54, 64, 0.18);
}
.panel-quick-tabs button {
min-height: 34px;
padding: 5px 2px;
font-size: 10px;
}
body.touch-mobile-ui .mobile-mode-controls:not([hidden]) {
display: flex !important;
position: absolute;
left: 50%;
right: auto;
bottom: 10px;
transform: translateX(-50%);
z-index: 24;
max-width: calc(100% - 18px);
}
body.touch-mobile-ui .mobile-rotate-controls:not([hidden]) {
position: fixed;
left: 50%;
right: auto;
bottom: calc(env(safe-area-inset-bottom, 0px) + 56px);
transform: translateX(-50%);
z-index: 12002;
max-width: calc(100vw - 22px);
}
body.touch-mobile-ui.mobile-mode-auto #gameCanvas {
touch-action: pan-y pinch-zoom !important;
}
body.touch-mobile-ui.mobile-mode-camera #gameCanvas,
body.touch-mobile-ui.mobile-mode-tool #gameCanvas {
touch-action: none !important;
}
.log {
max-height: none !important;
height: auto !important;
overflow: visible !important;
}
.lineage-main-card {
display: block !important;
visibility: visible !important;
width: 100%;
margin: 10px 0 calc(env(safe-area-inset-bottom, 0px) + 132px);
overflow: visible !important;
contain: none !important;
clear: both;
}
.lineage-main-content {
display: block !important;
height: auto !important;
min-height: 360px;
max-height: none !important;
overflow-x: hidden !important;
overflow-y: visible !important;
overscroll-behavior-y: auto !important;
-webkit-overflow-scrolling: auto;
touch-action: pan-y pinch-zoom;
}
.lineage-family-tree {
overflow-x: auto !important;
overflow-y: visible !important;
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
-webkit-overflow-scrolling: touch;
touch-action: pan-x pan-y pinch-zoom;
}
.lineage-family-tree .lineage-tree-panel { max-width: none; }
.selected-card,
.tool-card,
.colony-card,
.log-card,
.lineage-main-card,
.stage-wrap {
scroll-margin-top: 0 !important;
scroll-margin-bottom: calc(env(safe-area-inset-bottom, 0px) + 104px);
}
.back-to-game-btn {
bottom: calc(env(safe-area-inset-bottom, 0px) + 62px);
right: max(10px, env(safe-area-inset-right, 0px));
}
}
/* v39.16.40: selected tool descriptions appear at the top on touch devices. */
body.touch-mobile-ui .tool-tip-popover.mobile-selection-tip {
top: calc(env(safe-area-inset-top, 0px) + 8px) !important;
left: 50% !important;
transform: translateX(-50%);
width: min(680px, calc(100vw - 18px));
max-width: calc(100vw - 18px);
padding: 10px 12px;
border-radius: 12px;
background: rgba(46,38,30,.90);
font-size: 12px;
line-height: 1.45;
text-align: left;
}
@media (max-width: 980px) {
.top-actions { overflow: visible; }
.top-menu { flex: 0 0 auto; }
.top-menu-panel { position: fixed; top: calc(env(safe-area-inset-top, 0px) + 58px); right: max(10px, env(safe-area-inset-right, 0px)); }
}
@media (max-width: 980px) {
.top-panel-search {
flex: 0 0 220px;
width: 220px;
min-width: 220px;
}
.top-panel-search .panel-search-input { height: 34px; }
}
@media (max-width: 980px) {
.top-actions { overflow-x: auto; overflow-y: hidden; }
}
/* Mobile toolbar: keep every primary action reachable without hidden horizontal overflow. */
@media (max-width: 520px) {
.top-actions {
display: flex;
flex-wrap: wrap;
overflow: visible;
align-items: center;
}
.top-panel-search {
flex: 1 1 100%;
width: 100%;
min-width: 0;
}
.top-actions > .btn,
.top-actions > .top-menu,
.top-actions > .speed-menu {
flex: 0 1 auto;
}
}
.tool[data-tool="freeze"]::after { content: "🧊"; background: none; font-size: 52px; right: -10px; left: auto; opacity: 0.08; display:flex; align-items:center; justify-content:flex-end; }

View file

@ -1,9 +1,4 @@
/* CSS split file; preserve manifest load order. */
.lineage-tree {
overflow: auto;
padding: 8px 10px 18px;
contain: content;
}
.lineage-main-card {
margin-top: 16px;
@ -24,6 +19,14 @@
}
.lineage-head h2 { margin-bottom: 0; }
.lineage-title-note {
display: inline-block;
margin-left: 10px;
font-size: 12px;
font-weight: 700;
color: rgba(126, 86, 52, 0.78);
vertical-align: middle;
}
.lineage-main-content {
height: min(78vh, 980px);
@ -44,11 +47,6 @@
gap: 12px;
}
.lineage-main-content .lineage-tree {
min-width: 360px;
overflow: visible;
padding-right: 2px;
}
body::before {
@ -101,6 +99,7 @@ body::before {
box-shadow: 0 3px 8px rgba(64,48,32,0.045);
overflow: hidden;
isolation: isolate;
overflow: hidden !important;
}
.family-node.child {
border-color: rgba(103, 178, 44, 0.38);
@ -119,6 +118,52 @@ body::before {
z-index: 0;
pointer-events: none;
}
.family-death-time {
display: block;
margin-top: 1px;
font-size: 10px;
line-height: 1.15;
color: rgba(93, 72, 61, 0.76);
}
.family-genetic-stats {
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px 5px;
margin-top: 2px;
font-size: 9.7px;
line-height: 1.13;
color: rgba(88, 70, 48, 0.86);
letter-spacing: -0.02em;
white-space: normal;
max-width: 82px;
}
.family-genetic-stats span {
display: inline-flex;
gap: 1px;
min-width: 0;
white-space: nowrap;
}
.family-genetic-stats b {
font-weight: 800;
color: rgba(120, 86, 30, 0.92);
}
.lineage-family-tree .tree-node.champion {
border-color: rgba(202, 146, 33, 0.58);
box-shadow: inset 0 0 0 1px rgba(247, 200, 64, 0.30), 0 3px 8px rgba(120, 78, 12, 0.09);
}
.lineage-family-tree .tree-node.champion::before {
content: "\265B ";
position: absolute;
right: 6px;
top: 4px;
z-index: 2;
font-size: 14px;
color: rgba(158, 102, 20, 0.95);
text-shadow: 0 1px 0 rgba(255,255,255,0.70);
}
.family-node.alive .family-portrait { opacity: 0.80; }
.family-node.dead .family-portrait { opacity: 0.20; }
.family-portrait img {
@ -134,141 +179,10 @@ body::before {
text-overflow: ellipsis;
white-space: nowrap;
}
.family-copy .death-reason {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.12;
margin-top: 1px;
color: rgba(126, 80, 64, 0.92);
}
.family-personality-tags {
display: flex !important;
overflow: visible !important;
text-overflow: clip !important;
flex-wrap: wrap;
gap: 2px;
margin-top: 2px;
white-space: normal !important;
overflow: visible;
max-height: none;
}
.family-personality-tags em {
display: inline-block;
max-width: none;
padding: 1px 4px;
border-radius: 999px;
background: rgba(103,178,44,0.11);
border: 1px solid rgba(103,178,44,0.20);
color: rgba(64,72,48,0.86);
font-size: 7.5px;
font-style: normal;
line-height: 1.25;
overflow: visible;
text-overflow: clip;
white-space: nowrap;
}
.family-copy strong { font-size: 11px; }
.family-copy span { color: var(--muted); font-size: 8.5px; margin-top: 1px; }
.tool[data-tip] { position: relative; }
.colony-card { overflow: visible; min-height: 0; flex: 0 0 auto; }
.colony-stats {
grid-template-columns: repeat(2, 1fr);
margin-bottom: 8px;
}
.colony-chart {
display: block;
width: 100%;
height: 188px;
min-height: 188px;
border: 1px solid rgba(84, 68, 48, 0.14);
border-radius: 14px;
background: linear-gradient(180deg, rgba(255,255,255,0.70), rgba(246,240,228,0.54));
overflow: hidden;
}
.colony-card h2 { line-height: 1.45; margin-bottom: 10px; }
.colony-chart svg { display: block; width: 100%; height: 100%; }
.colony-chart-legend {
display: flex;
flex-wrap: wrap;
gap: 4px 8px;
align-items: center;
margin: 4px 0 8px;
font-size: 11px;
color: var(--muted);
line-height: 1.25;
}
.colony-chart-legend span {
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.colony-chart-legend i {
width: 8px;
height: 8px;
border-radius: 999px;
display: inline-block;
box-shadow: 0 0 0 1px rgba(70, 52, 36, 0.10);
}
.relation-row strong { display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; }
.relation-face {
width: 24px;
height: 24px;
object-fit: contain;
border-radius: 999px;
background: rgba(255,255,255,0.66);
border: 1px solid rgba(84,68,48,0.12);
vertical-align: middle;
flex: 0 0 auto;
}
.lineage-family {
padding: 10px;
margin: 0 0 12px;
border-radius: 15px;
background: rgba(255,253,248,0.62);
border: 1px solid rgba(84,68,48,0.12);
}
.lineage-family h3 {
margin: 0 0 8px;
font-size: 13px;
color: rgba(64,52,38,0.86);
}
/* Family archive: actual top-down tree diagram. */
.lineage-family-tree {
overflow-x: auto;
overflow-y: visible;
padding: 10px 10px 22px;
-webkit-overflow-scrolling: touch;
overscroll-behavior-x: contain;
}
.lineage-family-tree h3 {
position: sticky;
left: 0;
z-index: 3;
width: max-content;
padding-right: 12px;
background: rgba(255,253,248,0.82);
border-radius: 8px;
}
.lineage-tree-hint {
position: sticky;
left: 0;
z-index: 3;
width: max-content;
max-width: 100%;
margin: -2px 0 8px;
padding: 5px 8px;
border-radius: 9px;
color: var(--muted);
font-size: 11px;
background: rgba(255,255,255,0.62);
border: 1px solid rgba(84,68,48,0.08);
}
.lineage-family-tree .lineage-tree-panel {
position: relative;
overflow: visible;
@ -292,7 +206,6 @@ body::before {
z-index: 1;
isolation: isolate;
}
.lineage-actions {
display: flex;
align-items: center;
@ -305,7 +218,6 @@ body::before {
border-color: rgba(103,178,44,0.46);
font-weight: 700;
}
.lineage-render-status {
position: sticky;
top: 0;
@ -355,9 +267,9 @@ body::before {
}
.lineage-family-tree .tree-node {
position: absolute;
width: 132px;
height: 84px;
min-height: 84px;
width: 150px;
height: 112px;
min-height: 112px;
box-sizing: border-box;
align-items: flex-start;
background: linear-gradient(180deg, #fffdf8, #f8f1e5);
@ -376,11 +288,35 @@ body::before {
.lineage-family-tree .tree-node[data-family-tarinai-id]:hover {
transform: none;
}
.lineage-family-tree.family-hover-active .tree-node.family-hover-dim {
opacity: 0.28;
filter: saturate(0.58);
transition: opacity .12s ease, filter .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.lineage-family-tree.family-hover-active .tree-node.family-hover-focus,
.lineage-family-tree.family-hover-active .tree-node.family-hover-parent,
.lineage-family-tree.family-hover-active .tree-node.family-hover-child {
opacity: 1;
filter: none;
}
.lineage-family-tree.family-hover-active .tree-node.family-hover-focus {
border-color: rgba(124, 165, 66, 0.78);
box-shadow: 0 0 0 3px rgba(124, 165, 66, 0.16), 0 5px 13px rgba(64,48,32,0.10);
}
.lineage-family-tree.family-hover-active .tree-node.family-hover-parent {
border-color: rgba(80, 132, 198, 0.72);
box-shadow: 0 0 0 2px rgba(80, 132, 198, 0.14);
}
.lineage-family-tree.family-hover-active .tree-node.family-hover-child {
border-color: rgba(87, 170, 91, 0.72);
box-shadow: 0 0 0 2px rgba(87, 170, 91, 0.14);
}
.lineage-family-tree .tree-node .family-portrait {
right: 0;
bottom: -6px;
width: 58px;
height: 58px;
right: 4px;
top: 4px;
bottom: auto;
width: 48px;
height: 48px;
}
.lineage-family-tree .tree-generation-label {
position: absolute;
@ -388,7 +324,7 @@ body::before {
width: 54px;
padding: 3px 0;
border-radius: 999px;
font-size: 10px;
font-size: 11px;
color: var(--muted);
text-align: center;
background: rgba(255,255,255,0.70);
@ -403,13 +339,6 @@ body::before {
border-radius: 9px;
background: rgba(255,255,255,0.70);
}
@media (max-width: 720px) {
.ecology-grid { grid-template-columns: 1fr; }
.ecology-card { grid-template-columns: 68px minmax(0, 1fr); }
.ecology-card img { width: 68px; height: 68px; }
}
@media (max-width: 720px) {
.lineage-main-content {
height: 70vh;
@ -418,5 +347,138 @@ body::before {
}
}
/* Restored from the original ZIP: fixed colony graph/legend sizing. */
.colony-card { overflow: visible; min-height: 0; flex: 0 0 auto; }
.colony-stats {
grid-template-columns: 1fr;
margin-bottom: 8px;
}
.colony-chart {
display: block;
width: 100%;
height: 188px;
min-height: 188px;
border: 1px solid rgba(84, 68, 48, 0.14);
border-radius: 14px;
background: linear-gradient(180deg, rgba(255,255,255,0.70), rgba(246,240,228,0.54));
overflow: hidden;
}
.colony-card h2 { line-height: 1.45; margin-bottom: 10px; }
.colony-chart svg { display: block; width: 100%; height: 100%; }
.colony-chart-legend {
display: flex;
flex-wrap: wrap;
gap: 4px 8px;
align-items: center;
margin: 4px 0 8px;
font-size: 11px;
color: var(--muted);
line-height: 1.25;
}
.colony-chart-legend .chart-series-toggle {
appearance: none;
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
margin: 0;
padding: 2px 4px;
border: 0;
border-radius: 6px;
background: transparent;
color: inherit;
font: inherit;
line-height: inherit;
white-space: nowrap;
cursor: pointer;
transition: opacity 120ms ease, background-color 120ms ease, transform 120ms ease;
}
.colony-chart-legend .chart-series-toggle:hover { background: rgba(255,255,255,0.56); }
.colony-chart-legend .chart-series-toggle:focus-visible { outline: 2px solid rgba(103,178,44,0.58); outline-offset: 1px; }
.colony-chart-legend .chart-series-toggle:active { transform: translateY(1px); }
.colony-chart-legend .chart-series-toggle i {
width: 10px;
height: 10px;
flex: 0 0 10px;
border-radius: 999px;
display: inline-block;
box-shadow: 0 0 0 1px rgba(70, 52, 36, 0.10);
}
.colony-chart-legend .series-hidden { opacity: 0.42; text-decoration: line-through; }
.colony-chart-legend .series-hidden i { background: transparent !important; box-shadow: inset 0 0 0 2px currentColor; }
/* Family tree personality tags: consolidated final cascade. */
.family-personality-tags {
box-sizing: border-box !important;
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
grid-auto-rows: 13px !important;
gap: 2px 3px !important;
width: 100% !important;
max-width: 100% !important;
height: 28px !important;
max-height: 28px !important;
margin-left: 0 !important;
margin-right: 0 !important;
justify-self: stretch !important;
overflow: hidden !important;
}
.family-personality-tags em {
box-sizing: border-box !important;
display: block !important;
width: 100% !important;
min-width: 0 !important;
max-width: 100% !important;
height: 13px !important;
padding: 1px 2px !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
line-height: 11px !important;
font-size: 7.2px !important;
font-style: normal;
letter-spacing: -0.08em !important;
text-align: center !important;
border-radius: 999px;
background: rgba(103,178,44,0.11);
border: 1px solid rgba(103,178,44,0.20);
color: rgba(64,72,48,0.86);
}
.lineage-family-tree .tree-node .family-copy {
width: 100% !important;
max-width: 100% !important;
position: static !important;
display: flex !important;
flex-direction: column !important;
gap: 1px !important;
}
.lineage-family-tree .tree-node .family-copy strong,
.lineage-family-tree .tree-node .family-copy > span:not(.family-personality-tags) {
max-width: 100% !important;
}
.lineage-family-tree .tree-node .family-copy > .family-personality-tags {
position: static !important;
width: 100% !important;
max-width: 100% !important;
height: 24px !important;
max-height: 24px !important;
margin: 2px 0 0 !important;
grid-auto-rows: 11px !important;
gap: 1px 3px !important;
z-index: 3 !important;
}
.lineage-family-tree .tree-node .family-personality-tags em {
height: 11px !important;
line-height: 9px !important;
font-size: 6.8px !important;
}
.colony-chart { position: relative; }
.colony-chart-tooltip { position: fixed; z-index: 10020; pointer-events: none; min-width: 112px; max-width: 210px; padding: 7px 8px; border-radius: 9px; border: 1px solid rgba(80,65,48,0.18); background: rgba(255,252,245,0.98); color: #4d443a; box-shadow: 0 4px 14px rgba(64,48,30,0.16); font-size: 10px; line-height: 1.45; transform: none; }
.colony-chart-tooltip.hidden { display: none; }
.colony-chart-tooltip strong { display: block; margin-bottom: 3px; font-size: 10.5px; }
.colony-chart-tooltip-row { display: flex; justify-content: space-between; gap: 10px; white-space: nowrap; }
.colony-chart-hover-point { position: absolute; z-index: 5; width: 8px; height: 8px; border: 2px solid #5d8ee6; border-radius: 50%; background: rgba(255,255,255,0.96); box-shadow: 0 0 0 2px rgba(255,255,255,0.7); pointer-events: none; transform: translate(-50%, -50%); }
.colony-chart-hover-point.hidden { display: none; }
.tool[data-tip] { position: relative; }

View file

@ -1,59 +0,0 @@
# Architecture
Build target: static browser page. No bundler. Globals and ordered deferred scripts are intentional. Current inspected version: `15.20.16`.
## Load order
`app_manifest.json` owns CSS/JS order. Regenerate `index.html` and `service-worker.js` with `python3 scripts/generate_app_files.py` after version/order changes. `js/version.js` exposes `TARINAI_APP.version`, cache name, and query helper.
The old `css/styles.css` compatibility shim and `js/ui_family.js` split marker have been removed. The family tree entry point now starts at `ui_family_data.js` and continues through async/layout/path/render modules.
## Runtime objects
- `World`: constructor in `js/world.js`; methods split into view, family/social, combat/effects, environment, spatial/budget, ants, update, placement/log, bootstrap.
- `Tarinai`: constructor in `js/tarinai.js`; methods split into identity/social, disease/nest, item effects, needs/items, social/move/life, render.
- `Item`: item state, lifecycle, scaling, and field rendering in `js/items.js`.
- `Effect`: transient visuals in `js/sim_core.js`; combat/weather/food effects are spawned through `World` helpers.
- `TarinaiEvents`: global event bus for logs, terrain dirtiness, audio failures, phase/performance changes.
- `TarinaiPerformance`: FPS quality manager; provides effect caps, ant stride, terrain redraw cadence, UI intervals.
- `TarinaiInputMode`: input classification and mobile-mode state. UI code reads this manager instead of duplicating touch heuristics.
- `TarinaiWeatherSystem`: weather transitions and rain-spawned water item creation. Rain overlay drawing remains in `render.js`.
## Startup path
`main.js` owns the user-visible boot sequence:
1. Start the loading showcase and preload the initial sprite/image set through `loadImages`.
2. Bind UI, apply the default `garden` field layout, reset the world, and select `observe`.
3. Render logs, family archive, first frame, and stats.
4. Start deferred background image loading, enter `requestAnimationFrame(loop)`, and hide the loading screen.
5. Register `service-worker.js` with `updateViaCache: "none"` after window load.
## Data ownership
- `js/data.js`: base item/tool definitions, `TOOL_CATEGORIES`, config, image metrics, field presets.
- `js/item_visual_registry.js`: canonical item visual descriptors used by field drawing and generated tool-icon previews.
- `js/food_registry.js`: food/medicine semantics such as labels, serving classes, decay multipliers, and hygiene penalties.
- `js/disease_registry.js`: disease labels, flags, display metadata, cure-rule notes, and mystery-drug eligibility.
- `js/input_mode_manager.js`: touch/mobile classification, mobile mode persistence, and gesture capability policy.
- `js/weather_system.js`: weather state transitions and rain-water item spawning.
- `js/text_catalog.js`: Japanese UI copy/tooltips. Keep JS strings escaped where existing style uses escapes.
- `js/effect_registry.js`: item-effect labels, colors, modifiers, exclusivity/removal metadata, behavior callbacks (`onApply`, `onTick`, `onRemove`), direct eating context, and random-effect candidates.
- `js/sound_pack.js`: canonical sound metadata: IDs, categories, gain multipliers, cooldowns, optional sample paths, and ID-to-category mapping.
- `FIELD_TYPES` in `js/data.js`: cage/garden/park field labels, responsive heights, world scale, starting population, and initial grass.
## State loops
`main.js` starts asset preload, world bootstrap, animation loop, deferred image loading, service-worker registration. `World.update(dt)` advances time, weather, items, ants, Tarinai, effects, budgets, terrain dirtiness. `render()` draws cached background/terrain, visible entities, weather overlay, cards, HUD, and debug overlays.
World time is `CONFIG.dayLength` seconds per day. Version `15.20.16` uses 120 seconds/day and weather changes every 46-96 seconds. The phase boundary rules live in `World.phaseName()`.
Domain events are emitted at central mutation points: placement, eating, decay, effect apply/remove, disease infect/cure, damage/death/birth, fight hit/loss/end, ant spawn/grab/carry/death, weather changes, and rain item drops. Logs/audio still use direct calls where they already existed; do not add duplicate log or sound side effects when subscribing to these events.
Ant behavior remains intentionally conservative. `ANT_EXTENSION_HOOKS` names future ecology slots for nest hunger, nest growth, queen reproduction, worker lifecycle, carrying success, and territorial behavior, but this pass does not rebalance those mechanics.
## Spatial/performance rules
Use `World.nearby*` helpers rather than full-array scans in hot paths. Terrain cache uses dirty/version flags; do not reintroduce every-frame full terrain hashing. Performance degradation should be centralized in `performance_manager.js`.
Family tree rendering is intentionally asynchronous and cached. `ui_family_async.js` chunks component discovery with `requestIdleCallback` when available; `ui_family_render.js` hydrates cached HTML, keeps archive dirty/version state, and exposes `validateFamilyTree()` plus `familyTreeDiagnostics()` for browser-console checks.

View file

@ -1,64 +0,0 @@
# Content and visual guide
## Visual source of truth
Do not invent tool-UI-only item designs. `js/item_visual_registry.js` is the canonical descriptor table for drift-prone item visuals; field drawing in `js/items.js` and generated previews must consume the same concepts. Tool icons in `assets/ui/tool_*.webp` are generated previews and must match field rendering. Update `scripts/generate_item_icons.py` when changing item helper silhouettes; regenerate icons before shipping.
Canonical helper mapping:
- Weather rain overlay: `drawRain` in `render.js`; diagonal blue line streaks only, never item droplets.
- Horizontal item droplet: `drawOvalWaterDropSprite`; used by fallen water items, zunda juice, mercury. Same silhouette; recolor only.
- Water bowl: `water_bowl` descriptor in `js/item_visual_registry.js`; bowl and inner horizontal water palette are registry-owned.
- Sleep drug: `drawSleepTabletSprite`.
- Laxative: `drawLaxativeTabletSprite`; zunchi-motif tablet.
- Mystery drug: `drawMysteryTabletSprite`; question-mark tablet.
- Ammo: `drawBulletSprite`; bullet silhouette.
- Protein/niteropu: powder-pile rendering in the parameter-item branch.
## Tool groups
`TOOL_CATEGORIES` in `js/data.js` owns the runtime-generated tool palette. CSS colors/icons are tied to `data-tool-category` and `data-tool` values:
- Operation/observation: observe, delete, poke, pinch, new, water hose.
- Food/water/grass: zunda mochi, love mochi, fight mochi, grass, water bowl, zunda juice.
- Medicine/effects: sleep drug, laxative, protein, niteropu, ammo, mystery drug, mercury, giant drug, dwarf drug.
- Habitat/living things: zunchi, bed, nest box, ant nest, ball.
- Hazards/obstacles: stone, genkotsu, firecracker, vertical fence, horizontal fence.
When adding or renaming a tool, update `TOOL_DEFINITIONS`, `TOOL_CATEGORIES`, `TEXT_CATALOG.toolTips`, CSS icon mapping, generated `assets/ui/tool_*.webp`, and docs. Do not duplicate category membership in `index.html`.
## Gameplay data registries
- Item effects: `TarinaiEffectRegistry` owns labels, display colors, modifiers, exclusivity/removal metadata, behavior callbacks, and mystery-drug candidates. Tarinai item-effect methods are the integration layer and should route apply/tick/remove behavior through the registry.
- Diseases: `TarinaiDiseaseRegistry` owns disease flags, labels, symptoms/display text, base infection chances, cure-rule notes, and mystery-drug eligibility.
- Food: `TarinaiFoodRegistry` owns labels, serving classes, nutrition, hunger relief, decay multipliers, hygiene penalties, and special effect IDs. Serving-food eating paths must read nutrition and hunger relief from this registry.
- Weather: `TarinaiWeatherSystem` owns weather transitions and rain-spawned water items. Rain visuals are diagonal line streaks only. Rain-spawned water items, water bowls, zunda juice, and mercury are separate item/object concepts.
- Ants: `ANT_EXTENSION_HOOKS` documents future ecology slots only. Do not change worker count, queen founding, carrying, or nest removal balance as part of registry/event cleanup.
## UI copy and labels
Most visible UI remains Japanese. Keep user-facing naming consistent: the former lifestyle chart label is now the mental-state label; selected Tarinai combat summary shows fight win rate, not cumulative defeats. The selected Tarinai data panel must show both daily personality-change causes and the rolling `recentChangeCauses` history.
## Bubble text inventory
Active Tarinai/world bubble text classes:
- Symbols: `!`, `?`, `♡`, `...`.
- Sleep: `Zzz...`.
- Eating/poop: short munching and poop sounds.
- Fear/fight: short panic, shout, and breathing sounds.
- Disease/recovery: short weak or relief sounds.
- Random small voice: short Tarinai voice variants.
- Item effects: `???`.
When adding bubbles, keep them short; every bubble may also trigger `audio.bubble`.
## Audio
Sound settings are in the top-right sound menu, not inside Event UI. Categories: Tarinai voice, notification, operation. `js/sound_pack.js` is the canonical metadata source for sound IDs, categories, gains, cooldowns, sample paths, and ID mapping; `js/audio.js` is the playback engine. Missing samples fall back to synthetic audio. Notification and operation sounds are intentionally louder than voice sounds.
The Event card owns in-page push toast settings for log kinds: death, accident, birth, fight, food, weather, grass, relation. Those settings are separate from audio categories and persist through localStorage.
## Ecology and credits
Ecology dialog card copy and images come from `TEXT_CATALOG.ecologyCards`; image paths should use real assets or generated WebP assets. Credits are hardcoded in `index.html`; update them when adding new external media sources.

View file

@ -1,45 +0,0 @@
# Maintenance
## Release process
1. Edit source files.
2. Bump `app_manifest.json` and `js/version.js` to the same version.
3. Regenerate static references: `python3 scripts/generate_app_files.py`.
4. Regenerate item icons if item visuals changed: `python3 scripts/generate_item_icons.py`.
5. Check versions: no stale `v=<old>` in `index.html`, CSS icon URLs, `service-worker.js`.
6. Validate syntax: `node --check js/*.js service-worker.js`. If Node is not installed on the local machine, document that explicitly in the handoff and use browser-console smoke checks instead.
7. Open `index.html` in a browser and verify startup, reset field selection, one tool placement, observe selection, family tree render, sound menu toggles, and `?debug=1`.
8. Zip from project root and run `zip -T`.
If `scripts/generate_app_files.py` cannot run locally, manually keep these in sync: `app_manifest.json`, `index.html` script/link tags, and `service-worker.js` `coreScriptNames`/CSS cache entries.
## Cache rules
`service-worker.js` must remain no-cache/no-store in `.htaccess`; versioned JS/CSS/WebP can be immutable. Register service worker with `updateViaCache: "none"`.
## Debugging
Use `?debug=1` for FPS, performance level, counts, cache/SW state, last audio ID, effect registry count, terrain/spatial version, and recent event-bus history.
Useful browser-console checks:
- `TARINAI_VERSION` and `TARINAI_APP.cacheName` confirm version/cache naming.
- `world.fieldType`, `world.tarinai.length`, `world.items.length`, and `world.ants.length` confirm the active simulation shape.
- `TarinaiEffectRegistry.debugSnapshot()` confirms registered item effects.
- `TarinaiDiseaseRegistry.debugSnapshot()` confirms disease metadata.
- `TarinaiFoodRegistry.debugSnapshot()` confirms food semantics.
- `toolCategories().map(c => c.id)` confirms the runtime tool palette registry.
- `TarinaiInputMode.snapshot()` confirms mobile classification and mode policy.
- `TarinaiEvents.recent(null, 20).map(e => e.type)` confirms domain-event emission.
- `validateFamilyTree()` checks reciprocal family links and rendered family tree node/link consistency.
- `familyTreeDiagnostics()` reports the latest async archive render state.
## Mobile input
The mobile mode controls are owned by `TarinaiInputMode` and are only for compact/no-hover/coarse-pointer touch-first devices. Desktop browsers, including touch-screen PCs, should not show the mobile controls unless the viewport or UA also indicates mobile/tablet.
The mode state is stored as `tarinai_mobile_input_mode_v1`; sound settings use `tarinai_audio_settings_v2`; log push settings use `tarinai_log_push_settings_v2`.
## Prompt archive
`docs/prompt.txt` is external user material. Do not rewrite, translate, trim, or delete it.

View file

@ -1,466 +0,0 @@
You are working on the browser game "たりないコロニー" based on the current ZIP/project state, assumed to be v15.20.3.
Goal: resolve the remaining internal design confusion and underdeveloped systems, while preserving existing gameplay unless explicitly requested below.
Hard exclusions:
* Do NOT implement field-size balance tuning. Specifically do not implement the previously proposed "field-specific balance correction" work.
* Do NOT implement save/load.
* Do NOT implement debug scenario modes.
* Do NOT redesign or expand the zunchi system. Keep zunchi mechanics and general design as-is, except where existing code must be connected through registries/events without changing behavior.
* Do NOT modify `docs/prompt.txt` if present.
* Do not introduce a build toolchain requirement. Plain static HTML/CSS/JS must still run directly.
Primary requirements:
1. Make item visuals single-source-of-truth.
* Current problem: field item rendering, tool UI icons, generated WebP icons, documentation, and item definitions can diverge.
* Establish one canonical visual definition per item/tool.
* Field rendering must be the primary visual source, or both field rendering and UI preview must derive from the same canonical visual descriptor.
* The tool UI must never have an independently invented appearance.
* Create or consolidate a visual registry, for example `js/item_visual_registry.js`, with descriptors such as:
* `renderer`
* `shape`
* `palette`
* `scale`
* `uiPreviewScale`
* `fieldScale`
* `accessibleShapeTraits`
* optional `emojiFallback`
* Update field rendering and UI icon generation/preview so they share this registry.
* Fix visual drift for all known drift-prone items:
* ammo
* mystery drug
* laxative
* sleeping drug
* protein
* nietorop
* giant drug
* dwarf drug
* zunda juice
* mercury
* rain-water item
* ball
* Keep the rule in docs: UI icon is a preview of the field visual, not a separate design.
* Ensure scalable items correctly respond to small/medium/large placement size.
2. Data-drive tool categories.
* Current problem: tool category membership/order/color is partly hardcoded in HTML/CSS and not owned by tool data.
* Move tool categories into a data registry, for example `js/tool_registry.js` or `js/data.js`.
* The registry must define:
* category id
* Japanese label
* color/theme class
* sort order
* tool ids included in that category
* Generate/build the tool UI from this registry at runtime.
* Keep collapsible category behavior.
* Keep simple category color coding.
* Remove duplicated static category definitions from `index.html` where possible.
* Category changes must require editing only the registry, not HTML.
3. Convert EffectRegistry from display-only to real behavior ownership.
* Current problem: `effect_registry.js` mainly handles labels/display/random candidates, while actual behavior remains scattered in Tarinai/item code.
* Promote `EffectRegistry` into the canonical owner for item effect metadata and behavior.
* Each effect should define, where relevant:
* `id`
* Japanese label
* duration or permanent flag
* exclusive group
* stack behavior
* display color
* display summary
* modifiers
* `onApply(tarinai, context)`
* `onTick(tarinai, dt, context)`
* `onRemove(tarinai, context)`
* visual effect id
* bubble policy
* whether it is eligible for "怪しい薬"
* whether it is removed by "ずんだ汁"
* Migrate these item effects into the registry:
* laxative
* protein
* nietorop
* ammo
* mystery drug
* mercury
* giant drug
* dwarf drug
* zunda juice clearing behavior
* heko mochi timed effect
* fight mochi timed effect
* sleeping drug if it changes behavior
* Existing semantics must remain:
* laxative: emits zunchi every 3 seconds during effect; eating does not reduce hunger because it immediately passes through.
* protein: permanent; damage dealt x1.75; disease chance x0.5; red upward particles; mutually exclusive with nietorop; latest wins.
* nietorop: permanent; damage dealt x0.5; disease chance x2; purple downward particles; mutually exclusive with protein; latest wins.
* ammo: timed; walking speed x10; while moving, collision with Tarinai/ants knocks them away.
* mystery drug: grants random two effects from behavior-changing item effects and diseases.
* mercury: each consumption multiplicatively increases lifespan by x1.3.
* giant drug: permanent; size x3; damage x2; speed x1.5; 1 damage/sec; mutually exclusive with dwarf drug; latest wins.
* dwarf drug: permanent; size x0.25; damage x0.25; speed x0.5; 1 damage/sec; mutually exclusive with giant drug; latest wins.
* zunda juice: removes all removable item effects.
* "たりないデータ" must read active item effects from the registry, not hand-built scattered conditions.
4. Add DiseaseRegistry and connect disease behavior.
* Current problem: diseases are individual booleans/branches and are not uniformly represented.
* Add `DiseaseRegistry` or equivalent.
* It must define:
* disease id
* Japanese label
* symptoms/behavior flags
* infection chance modifiers
* cure rules
* display text
* visual/bubble policy
* eligibility for mystery drug
* Migrate these diseases into registry metadata while preserving behavior:
* zunchi disease
* sleep disease
* explosion disease
* fight disease
* Explosion disease infection probability must remain 15% where currently defined, with protein/nietorop disease multipliers applied to that base.
* Mystery drug must sample diseases through this registry.
* Zunda juice should remove item effects, not necessarily diseases, unless current semantics already remove a given disease; do not silently change disease cure rules.
5. Add FoodRegistry and centralize food semantics.
* Current problem: food interest, nutrition, decay, hygiene penalty, cures, and special exceptions are scattered.
* Add `FoodRegistry`.
* It must define:
* item id/type
* Japanese label
* nutrition
* palatability/interest class
* decay profile
* hygiene penalty on decay
* special effect id, if eating applies an effect
* whether it counts as grass-like interest, zunda-mochi high interest, medicine-like low interest, water, corpse, etc.
* Preserve existing behavior:
* zunda mochi remains highly attractive.
* most non-zunda foods remain only slightly more interesting than grass.
* all food naturally decays about twice per half-day, not continuously.
* decay slightly reduces hygiene stats at those discrete decay ticks.
* laxative effect prevents hunger reduction from eating.
* `Tarinai` food-seeking logic must consult `FoodRegistry`.
* Food decay logic must consult `FoodRegistry`.
* Tooltips/descriptions should remain Japanese and current user-facing wording must not regress.
6. Separate WeatherSystem from water items.
* Current problem: rain visual effects and rain-spawned items/water-like items are conceptually mixed.
* Create or isolate a `WeatherSystem`.
* Explicitly separate:
* `rain_visual`: diagonal blue-ish streaks/lines falling at an angle.
* `rain_water_item`: horizontal oval water item dropped by rain.
* `water_bowl`: placed water object.
* `zunda_juice`: green horizontal oval liquid item.
* `mercury`: gray horizontal oval liquid item.
* Do not reuse rain visual renderer for item droplets.
* Do not render rain effect as oval droplets. Rain effect must be angled falling lines.
* Do not render zunda juice or mercury with diagonal lines.
* Preserve current rain gameplay behavior.
* Add code comments documenting the distinction so future changes do not confuse them.
7. Apply event bus more broadly.
* Current problem: logs, sounds, notifications, stats, effects, and debug traces are still partly direct calls.
* Use the existing event bus or strengthen it.
* Add domain events for:
* `tool:placed`
* `item:eaten`
* `item:decayed`
* `effect:applied`
* `effect:removed`
* `disease:infected`
* `disease:cured`
* `tarinai:damaged`
* `tarinai:died`
* `tarinai:born`
* `fight:hit`
* `fight:lost`
* `fight:ended`
* `ant:spawned`
* `ant:grabbed`
* `ant:carried`
* `ant:died`
* `weather:changed`
* `rain:itemDropped`
* Logs, push notifications, audio, stats, and debug history should subscribe to events where practical.
* Do not rewrite everything destructively. Add event emission at central points first, then migrate obvious direct calls.
* Avoid duplicate logs/sounds.
8. Sound system source-of-truth cleanup.
* Current problem: `sound_pack.js` and `audio.js` both contain overlapping settings.
* Make `sound_pack.js` the canonical metadata source for:
* sound id
* category
* volume multiplier
* cooldown
* sample path
* fallback synthesis profile
* `audio.js` should be the playback engine only:
* loading samples
* fallback synthesis
* category enable/disable
* master gain
* cooldown enforcement
* concurrent voice limiting
* Keep the current user settings:
* master effect sound on/off
* Tarinai voice
* notification
* operation
* Do not regress volume. The previous issue was that only the first sound was loud and subsequent sounds became too quiet. Preserve the fixed master gain behavior.
* Keep `?debug=1` showing recent sound IDs and sound-pack version.
9. Mobile input manager.
* Current problem: mobile operation UI has appeared on PC repeatedly.
* Add a single `InputModeManager` or equivalent.
* It must own:
* `isTouchDevice`
* `isCompactViewport`
* `hasHover`
* `pointerCoarse`
* `userAgentMobile`
* `currentMode`
* `gestureState`
* whether canvas can pan
* whether canvas can place tools
* whether family tree is allowed to capture touch
* Mobile operation UI must appear only when the device is clearly touch-first:
* coarse pointer
* no hover
* real touch support
* and either compact viewport or mobile/tablet UA
* Desktop PCs with touch screens must not automatically show the mobile UI unless the viewport/UA also indicates mobile/tablet.
* Add debug display of the input classification under `?debug=1`.
* Keep existing modes:
* auto
* camera
* tool
* family tree
10. Ant system cleanup and extension hooks, without broad balance changes.
* Do not create a full new ant ecology balance pass.
* Do consolidate ant domain events and state names.
* Preserve existing behavior:
* ants can carry sleeping Tarinai.
* sleeping Tarinai do not wake just because ants carry them.
* sleeping Tarinai do not counterattack ants while asleep.
* ants without nests either join another available nest or wander.
* empty nests are removed.
* queen ants are visually larger.
* Add clear extension hooks or comments for future ant ecology:
* nest hunger
* nest growth
* queen reproduction
* worker lifecycle
* carrying success rate
* territorial behavior
* Do not implement those future mechanics now unless already present.
11. Family tree: add personality tags.
* The family tree currently records lineage but should expose personality more directly.
* Add compact personality tags to each Tarinai node/card in the family tree.
* Use existing personality axes:
* aggression
* openness
* sociability
* neuroticism
* Tags should be short Japanese labels, for example:
* aggressive/high aggression
* calm/low aggression
* curious/high openness
* cautious/low openness
* social/high sociability
* solitary/low sociability
* nervous/high neuroticism
* stable/low neuroticism
* Use at most 2-3 tags per node to avoid clutter.
* Make tags visually subtle and compact.
* Do not break existing family tree panning/zooming/layout.
* If a Tarinai is dead, still show its personality tags if data exists.
12. Tarinai data panel: show recent change causes.
* The Tarinai individual data panel must include recent reasons for personality/state changes.
* Add a small "recent change causes" section.
* Record recent causes when personality or important state changes happen, for example:
* genkotsu damage: fear increase and openness decrease
* fight win/loss
* being carried by ants
* disease infection/cure
* protein/nietorop
* giant/dwarf drug
* mercury consumption
* zunda juice clearing effects
* sleep/wake transitions where meaningful
* stress/fear significant changes
* Store only a short rolling history per Tarinai, for example last 5-8 entries.
* Each entry should include:
* Japanese short reason
* affected stat/effect
* approximate in-game time or "recent"
* This is UI/diagnostic information, not save/load.
* Do not add full persistence.
13. Personality system visibility.
* The existing personality axes are meaningful but currently not visible enough.
* In addition to family tree tags and recent change causes, improve the Tarinai data panel to make personality effects understandable:
* show current personality values already present, if available
* show the most recent cause next to changed values where practical
* avoid long prose; use compact labels.
* Do not add new personality axes.
14. Documentation consolidation and English-only high-density docs.
* Keep `docs/prompt.txt` untouched.
* Keep documentation concise, English-only, and high information density.
* Update or consolidate docs so the following source-of-truth rules are explicit:
* item visuals come from the canonical visual registry.
* UI icons are previews, not independent art.
* tool categories come from the tool registry.
* item effects come from EffectRegistry.
* diseases come from DiseaseRegistry.
* food behavior comes from FoodRegistry.
* rain visuals are separate from rain-spawned water items.
* zunchi is intentionally unchanged.
* no field balance, save/load, or debug scenario implementation in this pass.
* Avoid many small docs. Prefer a small set such as:
* `docs/ARCHITECTURE.md`
* `docs/CONTENT_GUIDE.md`
* `docs/MAINTENANCE.md`
* Remove obsolete docs if safely redundant.
* Do not delete `docs/prompt.txt`.
15. Preserve user-facing Japanese UI text unless intentionally specified.
* Game UI remains Japanese.
* Docs are English.
* The Codex implementation may use English comments for architecture, but visible labels/tooltips remain Japanese.
16. Regression fixes to preserve.
Do not regress the following existing fixes:
* Removed unwanted bubbles:
* ドン
* ぐるる
* すっきり
* Hg xN
* ちまっ
* でかっ
* へにゃ
* ムキッ
* mm...
* Ball tool UI icon must be soccer ball emoji.
* Time periods:
* morning starts at 05:00
* noon/day starts at 10:00
* evening starts at 17:00
* night starts at 20:00
* Genkotsu:
* stronger knockback for living things
* zunchi is knocked away
* damaged Tarinai gain fear and lose openness
* Rain visual:
* diagonal angled line rain, not droplets.
* Rain-spawned water item / zunda juice / mercury:
* horizontal oval item visuals.
* Sleeping Tarinai:
* can be carried by ants
* do not wake from ant carrying
* do not attack ants while sleeping
* after waking, remain awake for at least 10 seconds.
* Tarinai data:
* fight win rate instead of cumulative defeats.
* Fight:
* on taking damage, Tarinai may lose/flee probabilistically.
* high aggression lowers loss chance.
* high damage and low HP increase loss chance.
17. Versioning and cache.
* Bump app version to the next patch/minor version consistently.
* Update:
* version file
* service worker cache name
* asset query versions
* manifest if present
* Ensure `service-worker.js` itself remains no-cache/no-store.
* Ensure generated app manifest validation still passes.
18. Validation.
Run and report:
* `node --check js/*.js service-worker.js`
* manifest validation/generation check if scripts exist
* zip integrity check if packaging
* basic static grep checks:
* no stale app version references
* no removed bubble strings still emitted as bubbles
* mobile UI visibility logic uses the new InputModeManager
* tool categories are not duplicated in `index.html`
* sound metadata is not duplicated as canonical data in both `sound_pack.js` and `audio.js`
* item visual drift-prone items use canonical visual registry
* docs are English except `docs/prompt.txt`
Deliverables:
* Updated project files.
* Updated ZIP.
* Brief implementation summary.
* Validation results.
* Mention any deliberately deferred items:
* field balance tuning
* save/load
* debug scenario modes
* zunchi expansion

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -4,123 +4,202 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>&#x305F;&#x308A;&#x306A;&#x3044;&#x89B3;&#x5BDF;</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Ctext y=%2752%27 font-size=%2752%27%3E%E3%81%9F%3C/text%3E%3C/svg%3E" />
<link rel="stylesheet" href="css/base.css?v=15.24.0" />
<link rel="stylesheet" href="css/layout.css?v=15.24.0" />
<link rel="stylesheet" href="css/panel.css?v=15.24.0" />
<link rel="stylesheet" href="css/components.css?v=15.24.0" />
<link rel="stylesheet" href="css/mobile.css?v=15.24.0" />
<link rel="icon" type="image/png" href="assets/ui/favicon.png?v=39.18.18" />
<link rel="apple-touch-icon" href="assets/ui/apple-touch-icon.png?v=39.18.18" />
<link rel="manifest" href="manifest.webmanifest?v=39.18.18" />
<meta name="theme-color" content="#244d3a" />
<link rel="shortcut icon" href="favicon.ico?v=39.18.18" />
<link rel="stylesheet" href="css/base.css?v=39.18.18" />
<link rel="stylesheet" href="css/layout.css?v=39.18.18" />
<link rel="stylesheet" href="css/panel.css?v=39.18.18" />
<link rel="stylesheet" href="css/components.css?v=39.18.18" />
<link rel="stylesheet" href="css/mobile.css?v=39.18.18" />
</head>
<body>
<div id="loadingScreen" class="loading-screen" aria-live="polite">
<div class="loading-panel">
<div class="loading-kicker">たりないコロニー</div>
<h1 class="loading-title">読み込み中…</h1>
<p id="loadingText" class="loading-text">準備しています</p>
<div class="loading-kicker">&#x305F;&#x308A;&#x306A;&#x3044;&#x89B3;&#x5BDF;</div>
<h1 class="loading-title">&#x8AAD;&#x307F;&#x8FBC;&#x307F;&#x4E2D;&#x2026;</h1>
<p id="loadingText" class="loading-text">&#x6E96;&#x5099;&#x3057;&#x3066;&#x3044;&#x307E;&#x3059;&#x3002;</p>
<div id="loadingSpriteStrip" class="loading-sprite-strip" aria-hidden="true"></div>
<div class="loading-progress-track"><div id="loadingBar" class="loading-progress-bar" style="width:0%"></div></div>
<div class="loading-progress-meta"><span>進行状況</span><strong id="loadingPercent">0%</strong></div>
<p class="loading-hint">初期表示に必要な画像だけ先に読み込み、残りは後から順次ロードします。</p>
<div class="loading-progress-meta"><span>&#x9032;&#x884C;&#x72B6;&#x6CC1;</span><strong id="loadingPercent">0%</strong></div>
</div>
</div>
<div id="app">
<header class="topbar">
<div class="top-left">
<a class="btn room-link" href="https://host.nishi.boats/~333/">333&#x306E;&#x90E8;&#x5C4B;&#x306B;&#x79FB;&#x52D5;</a>
<div class="room-links" style="display:flex;flex-direction:column;align-items:stretch;gap:6px;">
<a class="btn room-link" style="display:block;text-align:center;" href="https://2012r2.nishi.boats/~333/">333&#x306E;&#x90E8;&#x5C4B;&#x306B;&#x79FB;&#x52D5;</a>
<a class="btn room-link" style="display:block;text-align:center;" href="https://seesaawiki.jp/informalzunda/d/%a5%b2%a1%bc%a5%e0%3a%20%a4%bf%a4%ea%a4%ca%a4%a4%b4%d1%bb%a1">&#x5730;&#x4E0B;&#x7262;&#x306B;&#x623B;&#x308B;</a>
</div>
<div class="brand">
<h1>&#x305F;&#x308A;&#x306A;&#x3044;&#x89B3;&#x5BDF;</h1>
</div>
</div>
<div class="top-actions">
<div id="panelSearchControl" class="panel-search-control top-panel-search" role="search" aria-label="&#x53F3;&#x30E1;&#x30CB;&#x30E5;&#x30FC;&#x691C;&#x7D22;">
<input id="panelSearchInput" class="panel-search-input" type="search" autocomplete="off" spellcheck="false" placeholder="&#x691C;&#x7D22;" aria-label="&#x53F3;&#x30E1;&#x30CB;&#x30E5;&#x30FC;&#x3092;&#x691C;&#x7D22;">
<span class="panel-search-status" aria-live="polite">0</span>
<button class="panel-search-prev" type="button" disabled aria-label="&#x524D;&#x306E;&#x8A72;&#x5F53;&#x4F4D;&#x7F6E;&#x3078;&#x79FB;&#x52D5;">&#x2191;</button>
<button class="panel-search-next" type="button" disabled aria-label="&#x6B21;&#x306E;&#x8A72;&#x5F53;&#x4F4D;&#x7F6E;&#x3078;&#x79FB;&#x52D5;">&#x2193;</button>
</div>
<button id="pauseBtn" class="btn primary">&#x4E00;&#x6642;&#x505C;&#x6B62;</button>
<button id="speedBtn" class="btn">&#x901F;&#x5EA6; x1</button>
<button id="ecologyBtn" class="btn">&#x305F;&#x308A;&#x306A;&#x3044;&#x306E;&#x751F;&#x614B;</button>
<div id="soundMenu" class="sound-menu">
<button id="soundBtn" class="btn sound-menu-btn" type="button" aria-haspopup="true" aria-expanded="false">音設定 ON</button>
<div id="soundPanel" class="sound-panel hidden" role="menu" aria-label="効果音設定">
<label class="sound-master"><input id="soundMasterToggle" type="checkbox" checked><span>効果音</span></label>
<div class="sound-panel-divider"></div>
<label><input type="checkbox" data-sound-category="voice"><span>たりないの声</span></label>
<label><input type="checkbox" data-sound-category="notify" checked><span>通知</span></label>
<label><input type="checkbox" data-sound-category="ops" checked><span>操作</span></label>
<div id="speedMenu" class="top-menu speed-menu">
<button id="speedMenuBtn" class="btn speed-menu-btn" type="button" aria-haspopup="true" aria-expanded="false">&#x901F;&#x5EA6; &#xD7;1</button>
<div id="speedMenuPanel" class="top-menu-panel speed-menu-panel hidden" role="menu" aria-label="&#x901F;&#x5EA6;">
<button class="top-menu-item" type="button" role="menuitemradio" data-speed-value="0.5">&#xD7;0.5</button>
<button class="top-menu-item" type="button" role="menuitemradio" data-speed-value="1">&#xD7;1</button>
<button class="top-menu-item" type="button" role="menuitemradio" data-speed-value="2">&#xD7;2</button>
<button class="top-menu-item" type="button" role="menuitemradio" data-speed-value="4">&#xD7;4</button>
<button class="top-menu-item" type="button" role="menuitemradio" data-speed-value="8">&#xD7;8</button>
<button class="top-menu-item" type="button" role="menuitemradio" data-speed-value="16">&#xD7;16</button>
</div>
<select id="speedSelect" class="speed-select-native" aria-hidden="true" tabindex="-1">
<option value="0.5">0.5</option><option value="1" selected>1</option><option value="2">2</option><option value="4">4</option><option value="8">8</option><option value="16">16</option>
</select>
</div>
<button id="saveBtn" class="btn">&#x30BB;&#x30FC;&#x30D6;</button>
<button id="ecologyBtn" class="btn ecology-top-btn" type="button">&#x305F;&#x308A;&#x306A;&#x3044;&#x306E;&#x751F;&#x614B;</button>
<button id="achievementsBtn" class="btn achievement-top-btn" type="button" aria-haspopup="dialog" aria-controls="achievementsDialog">&#x5B9F;&#x7E3E; <span id="achievementButtonCount">0/77</span></button>
<button id="resetBtn" class="btn danger reset-top-btn">&#x30EA;&#x30BB;&#x30C3;&#x30C8;</button>
<div id="topMenu" class="top-menu">
<button id="topMenuBtn" class="btn top-menu-btn" type="button" aria-haspopup="true" aria-expanded="false" aria-label="&#x30E1;&#x30CB;&#x30E5;&#x30FC;">&#x2630;</button>
<div id="topMenuPanel" class="top-menu-panel hidden" role="group" aria-label="&#x30E1;&#x30CB;&#x30E5;&#x30FC;">
<div id="soundMenu" class="sound-menu top-menu-sound">
<button id="soundBtn" class="top-menu-item sound-menu-btn" type="button" aria-haspopup="true" aria-expanded="false">&#x97F3; ON</button>
<div id="soundPanel" class="sound-panel hidden" role="group" aria-label="&#x97F3;">
<label class="sound-master"><input id="soundMasterToggle" type="checkbox" checked><span>&#x52B9;&#x679C;&#x97F3;</span></label>
<div class="sound-panel-divider"></div>
<label><input type="checkbox" data-sound-category="voice"><span>&#x305F;&#x308A;&#x306A;&#x3044;&#x306E;&#x58F0;</span></label>
<label><input type="checkbox" data-sound-category="notify" checked><span>&#x901A;&#x77E5;</span></label>
<label><input type="checkbox" data-sound-category="ops" checked><span>&#x64CD;&#x4F5C;</span></label>
</div>
</div>
<div id="visualSettingsMenu" class="sound-menu top-menu-sound top-menu-visual">
<button id="visualSettingsBtn" class="top-menu-item sound-menu-btn" type="button" aria-haspopup="true" aria-expanded="false">&#x8868;&#x793A;&#x8A2D;&#x5B9A;</button>
<div id="visualSettingsPanel" class="sound-panel hidden" role="group" aria-label="&#x8868;&#x793A;&#x8A2D;&#x5B9A;">
<label><input id="visualEffectsToggle" type="checkbox" data-visual-setting="effects" data-visual-on="high" data-visual-off="off"><span>&#x30A8;&#x30D5;&#x30A7;&#x30AF;&#x30C8;</span></label>
<label><input id="visualShadowsToggle" type="checkbox" data-visual-setting="shadows" data-visual-on="high" data-visual-off="off"><span>&#x5F71;</span></label>
<div class="sound-panel-divider"></div>
<div class="sound-panel-subtitle">&#x63CF;&#x753B;</div>
<label><input type="radio" name="visualDetails" data-visual-setting="details" data-visual-value="auto"><span>&#x81EA;&#x52D5;</span></label>
<label><input type="radio" name="visualDetails" data-visual-setting="details" data-visual-value="low"><span>&#x4F4E;</span></label>
<label><input type="radio" name="visualDetails" data-visual-setting="details" data-visual-value="high"><span>&#x9AD8;</span></label>
</div>
</div>
<button id="creditsBtn" class="top-menu-item" type="button">&#x30AF;&#x30EC;&#x30B8;&#x30C3;&#x30C8;</button>
</div>
</div>
<button id="creditsBtn" class="btn">&#x30AF;&#x30EC;&#x30B8;&#x30C3;&#x30C8;</button>
<button id="saveBtn" class="btn">セーブ</button>
<button id="resetBtn" class="btn danger">&#x30EA;&#x30BB;&#x30C3;&#x30C8;</button>
</div>
</header>
<main class="layout">
<section class="stage-wrap">
<canvas id="gameCanvas" aria-label="&#x305F;&#x308A;&#x306A;&#x3044;&#x89B3;&#x5BDF;&#x30AD;&#x30E3;&#x30F3;&#x30D0;&#x30B9;"></canvas>
<div id="mobileModeControls" class="mobile-mode-controls" aria-label="スマホ操作モード">
<button type="button" data-mobile-mode="auto" class="active">自動</button>
<button type="button" data-mobile-mode="camera">カメラ</button>
<button type="button" data-mobile-mode="tool">道具</button>
<button type="button" data-mobile-mode="family">家系図</button>
<div id="mobileModeControls" class="mobile-mode-controls" aria-label="&#x30B9;&#x30DE;&#x30DB;&#x64CD;&#x4F5C;&#x30E2;&#x30FC;&#x30C9;">
<button type="button" data-mobile-mode="auto" class="active">&#x81EA;&#x52D5;</button>
<button type="button" data-mobile-mode="camera">&#x30AB;&#x30E1;&#x30E9;</button>
<button type="button" data-mobile-mode="tool">&#x9053;&#x5177;</button>
</div>
<div id="mobileRotateControls" class="mobile-rotate-controls" hidden aria-label="&#x7269;&#x7406;&#x30A2;&#x30A4;&#x30C6;&#x30E0;&#x306E;&#x56DE;&#x8EE2;">
<button type="button" data-mobile-rotate="left">&#x5DE6;&#x56DE;&#x8EE2;</button>
<button type="button" data-mobile-rotate="right">&#x53F3;&#x56DE;&#x8EE2;</button>
</div>
<div id="toast" class="toast hidden"></div>
</section>
<aside class="panel">
<nav id="panelQuickTabs" class="panel-quick-tabs" aria-label="&#x53F3;&#x30E1;&#x30CB;&#x30E5;&#x30FC;&#x306E;&#x30BF;&#x30D6;">
<button type="button" data-panel-tab="game">&#x672C;&#x4F53;</button>
<button type="button" data-panel-tab="selected">&#x305F;&#x308A;&#x306A;&#x3044;<br>&#x30C7;&#x30FC;&#x30BF;</button>
<button type="button" data-panel-tab="tools">&#x9053;&#x5177;</button>
<button type="button" data-panel-tab="colony">&#x30B3;&#x30ED;&#x30CB;&#x30FC;</button>
<button type="button" data-panel-tab="events">&#x30A4;&#x30D9;&#x30F3;&#x30C8;</button>
<button type="button" data-panel-tab="family">&#x5BB6;&#x7CFB;&#x56F3;</button>
</nav>
<section id="selectedCard" class="card selected-card hidden">
<div class="selected-head">
<h2>&#x305F;&#x308A;&#x306A;&#x3044;&#x30C7;&#x30FC;&#x30BF;</h2>
<button id="selectedCloseBtn" class="selected-close" type="button" aria-label="&#x89B3;&#x5BDF;&#x3092;&#x3084;&#x3081;&#x308B;">&times;</button>
<button id="selectedCloseBtn" class="selected-close" type="button" aria-label="&#x305F;&#x308A;&#x306A;&#x3044;&#x30C7;&#x30FC;&#x30BF;&#x3092;&#x9589;&#x3058;&#x308B;">&times;</button>
</div>
<div id="selectedInfo" class="selected-info empty">&#x672A;&#x9078;&#x629E;</div>
<div id="selectedInfo" class="selected-info empty">&#x305F;&#x308A;&#x306A;&#x3044;&#x3092;&#x9078;&#x629E;&#x3057;&#x3066;&#x304F;&#x3060;&#x3055;&#x3044;</div>
</section>
<section id="toolCard" class="card tool-card collapsible-card" data-card-key="tools">
<section id="toolCard" class="card tool-card collapsible-card">
<button class="panel-card-toggle" type="button" aria-expanded="true"><span class="panel-card-title">&#x9053;&#x5177;</span><span class="panel-card-state">&#x6298;&#x308A;&#x7573;&#x307F;</span></button>
<div class="panel-card-body">
<div class="tool-body">
<div id="toolPalette" class="tool-palette" aria-live="polite"></div>
<p class="hint">&#x540C;&#x3058;&#x9053;&#x5177;&#x30DC;&#x30BF;&#x30F3;&#x3092;&#x3082;&#x3046;&#x4E00;&#x5EA6;&#x62BC;&#x3059;&#x3068;&#x3001;&#x5BFE;&#x5FDC;&#x9053;&#x5177;&#x306E;&#x5927;&#x304D;&#x3055;&#x304C;&#x5C0F;&#x30FB;&#x4E2D;&#x30FB;&#x5927;&#x3067;&#x5207;&#x308A;&#x66FF;&#x308F;&#x308A;&#x307E;&#x3059;&#x3002;</p>
</div>
</div>
</section>
<section class="card colony-card collapsible-card" data-card-key="colony">
<section class="card colony-card collapsible-card">
<button class="panel-card-toggle" type="button" aria-expanded="true"><span class="panel-card-title">&#x30B3;&#x30ED;&#x30CB;&#x30FC;</span><span class="panel-card-state">&#x6298;&#x308A;&#x7573;&#x307F;</span></button>
<div class="panel-card-body">
<div class="stats colony-stats">
<div class="stat"><span>&#x500B;&#x4F53;&#x6570;</span><strong id="statPop">0</strong></div>
<div class="stat"><span>&#x7D2F;&#x8A08;&#x6B7B;&#x4EA1;&#x6570;</span><strong id="statDead">0</strong></div>
<div class="stat"><span>雰囲気</span><strong id="statColonyMood">のんびり</strong></div>
<div class="stat"><span>&#x72B6;&#x614B;</span><strong id="statColonyMood">&#x5B89;&#x5B9A;</strong></div>
</div>
<div id="colonyChartTabs" class="chart-tabs"><button data-chart="population" class="active">&#x4EBA;&#x53E3;</button><button data-chart="life">精神</button><button data-chart="environment">&#x74B0;&#x5883;</button><button data-chart="objects">&#x30AA;&#x30D6;&#x30B8;&#x30A7;&#x30AF;&#x30C8;</button></div>
<details class="colony-setting-fold colony-settings-fold">
<summary><span>&#x30B3;&#x30ED;&#x30CB;&#x30FC;&#x8A2D;&#x5B9A;</span></summary>
<div class="colony-settings-body">
<section class="colony-setting-section ground-setting-section">
<div class="colony-setting-section-head"><span>&#x5730;&#x9762;</span></div>
<button id="groundTypeBtn" class="ground-type-btn colony-setting-action" type="button" data-ground-toggle aria-label="&#x5730;&#x9762;&#x3092;&#x304D;&#x308A;&#x304B;&#x3048;&#x308B;"><strong id="statGroundType">&#x7802;</strong><span>&#x5207;&#x308A;&#x66FF;&#x3048;</span></button>
</section>
<section class="colony-setting-section temperature-section">
<div class="colony-setting-section-head"><span>&#x6C17;&#x6E29;</span><strong id="temperatureValue">10&#x2103;</strong></div>
<div class="temperature-control" aria-label="&#x6C17;&#x6E29;&#x8ABF;&#x6574;">
<label class="temperature-auto"><input id="temperatureAutoToggle" type="checkbox" checked> &#x81EA;&#x52D5;</label>
<input id="temperatureSlider" class="temperature-slider" type="range" aria-label="&#x6C17;&#x6E29;" min="-100" max="120" step="1" value="10">
</div>
</section>
<section class="colony-setting-section colony-limit-section">
<div class="colony-setting-section-head"><span>&#x4E0A;&#x9650;&#x8A2D;&#x5B9A;</span><small>300&#x306E;&#x6B21;&#x306F;&#x221E;</small></div>
<div class="colony-limit-control" aria-label="&#x30B3;&#x30ED;&#x30CB;&#x30FC;&#x4E0A;&#x9650;&#x8A2D;&#x5B9A;">
<div class="colony-limit-grid">
<label class="colony-limit-row"><span>&#x305F;&#x308A;&#x306A;&#x3044;&#x306E;&#x6570;</span><input id="tarinaiPopulationLimitSlider" class="colony-limit-slider" type="range" min="1" max="301" step="1" value="301"><input id="tarinaiPopulationLimitInput" class="colony-limit-number" type="number" min="1" max="301" step="1" inputmode="numeric" placeholder="&#x221E;"></label>
<label class="colony-limit-row"><span>&#x7269;&#x4F53;&#x6570;</span><input id="objectLimitSlider" class="colony-limit-slider" type="range" min="1" max="301" step="1" value="301"><input id="objectLimitInput" class="colony-limit-number" type="number" min="1" max="301" step="1" inputmode="numeric" placeholder="&#x221E;"></label>
</div>
<div id="colonyLimitStatus" class="colony-limit-status">&#x305F;&#x308A;&#x306A;&#x3044;&#x306E;&#x6570; 0 / &#x221E;&#x3000;&#x7269;&#x4F53;&#x6570; 0 / &#x221E;</div>
</div>
</section>
</div>
</details>
<div id="colonyChartTabs" class="chart-tabs"><button data-chart="population" class="active">&#x500B;&#x4F53;&#x6570;</button><button data-chart="life">&#x6B32;&#x6C42;</button><button data-chart="objects">&#x74B0;&#x5883;</button></div>
<div id="colonyChartRangeTabs" class="chart-range-tabs" aria-label="&#x7D71;&#x8A08;&#x306E;&#x8868;&#x793A;&#x671F;&#x9593;"><button data-chart-range="day">1&#x65E5;</button><button data-chart-range="season" class="active">&#x5B63;&#x7BC0;</button><button data-chart-range="year">&#x5E74;</button></div>
<div id="colonyChartLegend" class="colony-chart-legend"></div>
<div id="colonyChart" class="colony-chart" aria-label="&#x30B3;&#x30ED;&#x30CB;&#x30FC;&#x306E;&#x63A8;&#x79FB;&#x30B0;&#x30E9;&#x30D5;" role="img"></div>
</div>
</section>
<section class="card log-card collapsible-card" data-card-key="events">
<section class="card log-card collapsible-card">
<button class="panel-card-toggle" type="button" aria-expanded="true"><span class="panel-card-title">&#x30A4;&#x30D9;&#x30F3;&#x30C8;</span><span class="panel-card-state">&#x6298;&#x308A;&#x7573;&#x307F;</span></button>
<div class="panel-card-body">
<div id="log" class="log"></div>
<div class="log-push-controls" id="logPushControls" aria-label="&#x30D7;&#x30C3;&#x30B7;&#x30E5;&#x901A;&#x77E5;&#x8A2D;&#x5B9A;">
<label class="push-master"><input id="logPushEnabled" type="checkbox"> &#x30D7;&#x30C3;&#x30B7;&#x30E5;&#x901A;&#x77E5;</label>
<div class="log-push-controls" id="logPushControls" aria-label="&#x30A4;&#x30D9;&#x30F3;&#x30C8;&#x8868;&#x793A;&#x30FB;&#x30D7;&#x30C3;&#x30B7;&#x30E5;&#x901A;&#x77E5;&#x8A2D;&#x5B9A;">
<div class="event-category-heading">&#x8868;&#x793A;&#x30AB;&#x30C6;&#x30B4;&#x30EA;</div>
<div class="push-kind-grid">
<label><input type="checkbox" data-push-kind="death" checked> &#x6B7B;&#x4EA1;</label>
<label><input type="checkbox" data-push-kind="accident" checked> &#x4E8B;&#x6545;</label>
<label><input type="checkbox" data-push-kind="birth" checked> &#x8A95;&#x751F;</label>
<label><input type="checkbox" data-push-kind="fight"> &#x55A7;&#x5629;</label>
<label><input type="checkbox" data-push-kind="food"> &#x98DF;&#x4E8B;</label>
<label><input type="checkbox" data-push-kind="weather"> &#x5929;&#x6C17;</label>
<label><input type="checkbox" data-push-kind="grass"> &#x8349;</label>
<label><input type="checkbox" data-push-kind="relation"> &#x95A2;&#x4FC2;</label>
<label><input type="checkbox" data-event-category="life" checked> &#x751F;&#x547D;</label>
<label><input type="checkbox" data-event-category="danger" checked> &#x5371;&#x967A;</label>
<label><input type="checkbox" data-event-category="social" checked> &#x4EA4;&#x6D41;</label>
<label><input type="checkbox" data-event-category="living" checked> &#x751F;&#x6D3B;</label>
<label><input type="checkbox" data-event-category="environment" checked> &#x74B0;&#x5883;</label>
<label><input type="checkbox" data-event-category="observe" checked> &#x89B3;&#x5BDF;</label>
</div>
<label class="push-master"><input id="logPushEnabled" type="checkbox"> &#x30D7;&#x30C3;&#x30B7;&#x30E5;&#x901A;&#x77E5;</label>
</div>
</div>
</section>
</aside>
</main>
<section class="card lineage-main-card">
<div class="lineage-head">
<h2>&#x5BB6;&#x7CFB;&#x56F3;</h2>
<h2>&#x5BB6;&#x7CFB;&#x56F3;<span class="lineage-title-note">&#x91CD;&#x3044;&#x5834;&#x5408;&#x304C;&#x3042;&#x308B;&#x306E;&#x3067;&#x6CE8;&#x610F;</span></h2>
<div class="lineage-actions">
<button id="archiveUpdateToggleBtn" class="btn" type="button" aria-pressed="false">&#x66F4;&#x65B0; OFF</button>
<button id="archiveResetBtn" class="btn danger" type="button">&#x5BB6;&#x7CFB;&#x56F3;&#x3092;&#x30EA;&#x30BB;&#x30C3;&#x30C8;</button>
@ -128,18 +207,67 @@
</div>
<div id="archiveContent" class="archive-content lineage-main-content"></div>
</section>
<button id="backToGameBtn" class="back-to-game-btn" type="button" hidden aria-label="&#x30B2;&#x30FC;&#x30E0;&#x672C;&#x4F53;&#x3078;&#x623B;&#x308B;">&#x25B2; &#x672C;&#x4F53;</button>
</div>
<div id="achievementsDialog" class="field-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="achievementsDialogTitle">
<div class="field-dialog-panel achievement-dialog-panel">
<div class="achievement-dialog-head">
<div>
<div class="achievement-dialog-kicker">&#x305F;&#x308A;&#x306A;&#x3044;&#x89B3;&#x5BDF;</div>
<h2 id="achievementsDialogTitle">&#x5B9F;&#x7E3E; <span id="achievementDialogCount">0 / 77</span></h2>
</div>
<button id="achievementsCloseIconBtn" class="achievement-close-icon" type="button" aria-label="&#x5B9F;&#x7E3E;&#x3092;&#x9589;&#x3058;&#x308B;">&times;</button>
</div>
<p class="field-dialog-lead">&#x4E00;&#x90E8;&#x306E;&#x6761;&#x4EF6;&#x306F;&#x672A;&#x9054;&#x6210;&#x3067;&#x3082;&#x516C;&#x958B;&#x3055;&#x308C;&#x307E;&#x3059;&#x3002;&#x5B9F;&#x7E3E;&#x306F;&#x3053;&#x306E;&#x7AEF;&#x672B;&#x306B;&#x4FDD;&#x5B58;&#x3055;&#x308C;&#x3001;&#x80CC;&#x666F;&#x306E;&#x68D2;&#x30B0;&#x30E9;&#x30D5;&#x306F;&#x5168;&#x4F53;&#x9054;&#x6210;&#x7387;&#x3092;&#x793A;&#x3057;&#x307E;&#x3059;&#x3002;</p>
<div id="achievementSharedStatus" class="achievement-shared-status" aria-live="polite">&#x5171;&#x6709;&#x9054;&#x6210;&#x7387;&#x3092;&#x78BA;&#x8A8D;&#x4E2D;&#x2026;</div>
<div id="achievementList" class="achievement-list"></div>
<div class="field-dialog-actions achievement-dialog-actions">
<button id="achievementResetBtn" class="btn danger achievement-reset-btn" type="button">&#x5B9F;&#x7E3E;&#x3092;&#x30EA;&#x30BB;&#x30C3;&#x30C8;</button>
<button id="achievementUnlockAllBtn" class="btn" type="button" hidden>&#x5168;&#x5B9F;&#x7E3E;&#x3092;&#x7372;&#x5F97;</button>
<button id="achievementRefreshBtn" class="btn" type="button">&#x5171;&#x6709;&#x9054;&#x6210;&#x7387;&#x3092;&#x66F4;&#x65B0;</button>
<button id="achievementsCloseBtn" class="btn primary" type="button">&#x9589;&#x3058;&#x308B;</button>
</div>
</div>
</div>
<div id="achievementToast" class="achievement-toast hidden" role="status" aria-live="polite">
<div class="achievement-toast-icon" aria-hidden="true">&#x2605;</div>
<div class="achievement-toast-copy">
<small>&#x5B9F;&#x7E3E;&#x3092;&#x89E3;&#x9664;&#x3057;&#x307E;&#x3057;&#x305F;</small>
<strong id="achievementToastTitle"></strong>
<span id="achievementToastCondition" class="achievement-toast-condition"></span>
</div>
</div>
<div id="fieldDialog" class="field-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="fieldDialogTitle">
<div class="field-dialog-panel">
<h2 id="fieldDialogTitle">&#x30D5;&#x30A3;&#x30FC;&#x30EB;&#x30C9;&#x3092;&#x9078;&#x629E;</h2>
<div class="field-dialog-panel reset-dialog-panel">
<h2 id="fieldDialogTitle">&#x30EA;&#x30BB;&#x30C3;&#x30C8;&#x8A2D;&#x5B9A;</h2>
<p class="field-dialog-lead">&#x30B5;&#x30A4;&#x30BA;&#x3068;&#x30D7;&#x30EA;&#x30BB;&#x30C3;&#x30C8;&#x3092;&#x7D44;&#x307F;&#x5408;&#x308F;&#x305B;&#x3066;&#x89B3;&#x5BDF;&#x74B0;&#x5883;&#x3092;&#x751F;&#x6210;&#x3057;&#x307E;&#x3059;&#x3002;</p>
<div class="field-dialog-section-title">&#x30B5;&#x30A4;&#x30BA;</div>
<div class="field-choice-grid">
<button class="field-choice" data-field-type="park" type="button"><strong>&#x516C;&#x5712;</strong><span>&#x5927;</span></button>
<button class="field-choice" data-field-type="garden" type="button"><strong>&#x5EAD;</strong><span>&#x4E2D;</span></button>
<button class="field-choice" data-field-type="cage" type="button"><strong>&#x30B1;&#x30FC;&#x30B8;</strong><span>&#x5C0F;</span></button>
<button class="field-choice" data-field-type="park" type="button" aria-pressed="false"><strong>&#x516C;&#x5712;</strong><span>&#x5927;</span></button>
<button class="field-choice" data-field-type="garden" type="button" aria-pressed="true"><strong>&#x5EAD;</strong><span>&#x4E2D;</span></button>
<button class="field-choice" data-field-type="cage" type="button" aria-pressed="false"><strong>&#x30B1;&#x30FC;&#x30B8;</strong><span>&#x5C0F;</span></button>
</div>
<div class="field-dialog-section-title">&#x30D7;&#x30EA;&#x30BB;&#x30C3;&#x30C8;</div>
<div class="field-choice-grid reset-preset-choice-grid">
<button class="field-choice" data-reset-preset="empty" type="button" aria-pressed="false"><strong>&#x7A7A;&#x767D;</strong><span>&#x4F55;&#x3082;&#x7121;&#x3044;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="default" type="button" aria-pressed="true"><strong>&#x30C7;&#x30D5;&#x30A9;&#x30EB;&#x30C8;</strong><span>&#x521D;&#x671F;&#x751F;&#x6210;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="athletic" type="button" aria-pressed="false"><strong>&#x30A2;&#x30B9;&#x30EC;&#x30C1;&#x30C3;&#x30AF;</strong><span>&#x67F5;&#x3068;&#x30DC;&#x30FC;&#x30EB;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="crowded" type="button" aria-pressed="false"><strong>&#x904E;&#x5BC6;</strong><span>&#x5927;&#x91CF;&#x306E;&#x305F;&#x308A;&#x306A;&#x3044;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="family" type="button" aria-pressed="false"><strong>&#x5BB6;&#x65CF;</strong><span>2&#x5339;&#x3068;&#x3078;&#x3053;&#x9905;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="war" type="button" aria-pressed="false"><strong>&#x6226;&#x4E89;</strong><span>&#x3051;&#x3093;&#x304B;&#x3068;&#x67F5;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="happy" type="button" aria-pressed="false"><strong>&#x5E78;&#x798F;</strong><span>&#x6383;&#x9664;&#x6A5F;&#x3068;&#x5DE3;&#x7BB1;&#x3002;</span></button>
<button class="field-choice" data-reset-preset="hair_trigger" type="button" aria-pressed="false"><strong>&#x4E00;&#x89E6;&#x5373;&#x767A;</strong><span>&#x4E2D;&#x592E;&#x306E;&#x30DC;&#x30FC;&#x30EB;&#x67F5;&#x3002;</span></button>
</div>
<div class="field-dialog-actions">
<button id="fieldCancelBtn" class="btn" type="button">&#x30AD;&#x30E3;&#x30F3;&#x30BB;&#x30EB;</button>
<button id="fieldConfirmBtn" class="btn danger" type="button">&#x518D;&#x751F;&#x6210;</button>
</div>
</div>
</div>
@ -147,15 +275,14 @@
<div id="creditsDialog" class="field-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="creditsDialogTitle">
<div class="field-dialog-panel credits-dialog-panel">
<div class="field-dialog-panel">
<h2 id="creditsDialogTitle">&#x30AF;&#x30EC;&#x30B8;&#x30C3;&#x30C8;</h2>
<div class="credits-content">
<div>
<p><strong>&#x88FD;&#x4F5C;:</strong> 333</p>
<p><strong>&#x305F;&#x308A;&#x306A;&#x3044;:</strong> &#x3080;&#x3089;&#x3055;&#x304D;&#x6C0F;</p>
<p><strong>&#x30A4;&#x30E9;&#x30B9;&#x30C8;:</strong> &#x3080;&#x3089;&#x3055;&#x304D;&#x6C0F; / &#x3044;&#x3089;&#x3059;&#x3068;&#x3084;: <a href="https://www.irasutoya.com/" target="_blank" rel="noopener">https://www.irasutoya.com/</a></p>
<p><strong>&#x52B9;&#x679C;&#x97F3;:</strong> VOICEVOX: &#x305A;&#x3093;&#x3060;&#x3082;&#x3093;&#x3000;&#x52B9;&#x679C;&#x97F3;&#x30E9;&#x30DC;: <a href="https://soundeffect-lab.info/" target="_blank" rel="noopener">https://soundeffect-lab.info/</a></p>
<p><strong>&#x30A2;&#x30EA;&#x7D20;&#x6750;:</strong> &#x3044;&#x3089;&#x3059;&#x3068;&#x3084;: <a href="https://www.irasutoya.com/" target="_blank" rel="noopener">https://www.irasutoya.com/</a></p>
</div>
<div class="field-dialog-actions credits-actions">
<div class="field-dialog-actions">
<button id="creditsCloseBtn" class="btn" type="button">&#x9589;&#x3058;&#x308B;</button>
</div>
</div>
@ -183,66 +310,221 @@
</div>
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
<script src="js/version.js?v=15.24.0" defer></script>
<script src="js/event_bus.js?v=15.24.0" defer></script>
<script src="js/performance_manager.js?v=15.24.0" defer></script>
<script src="js/registry_base.js?v=15.24.0" defer></script>
<script src="js/disease_registry.js?v=15.24.0" defer></script>
<script src="js/effect_registry.js?v=15.24.0" defer></script>
<script src="js/sound_pack.js?v=15.24.0" defer></script>
<script src="js/data.js?v=15.24.0" defer></script>
<script src="js/item_visual_registry.js?v=15.24.0" defer></script>
<script src="js/food_registry.js?v=15.24.0" defer></script>
<script src="js/input_mode_manager.js?v=15.24.0" defer></script>
<script src="js/math.js?v=15.24.0" defer></script>
<script src="js/physics_helpers.js?v=15.24.0" defer></script>
<script src="js/assets.js?v=15.24.0" defer></script>
<script src="js/audio.js?v=15.24.0" defer></script>
<script src="js/render.js?v=15.24.0" defer></script>
<script src="js/sim_core.js?v=15.24.0" defer></script>
<script src="js/items.js?v=15.24.0" defer></script>
<script src="js/ants.js?v=15.24.0" defer></script>
<script src="js/health.js?v=15.24.0" defer></script>
<script src="js/tarinai.js?v=15.24.0" defer></script>
<script src="js/tarinai_identity_social.js?v=15.24.0" defer></script>
<script src="js/tarinai_action_state.js?v=15.24.0" defer></script>
<script src="js/tarinai_disease_nest.js?v=15.24.0" defer></script>
<script src="js/tarinai_item_effects.js?v=15.24.0" defer></script>
<script src="js/tarinai_needs_items.js?v=15.24.0" defer></script>
<script src="js/tarinai_social_move_life.js?v=15.24.0" defer></script>
<script src="js/tarinai_render.js?v=15.24.0" defer></script>
<script src="js/world.js?v=15.24.0" defer></script>
<script src="js/world_view.js?v=15.24.0" defer></script>
<script src="js/family_graph.js?v=15.24.0" defer></script>
<script src="js/world_family_social.js?v=15.24.0" defer></script>
<script src="js/world_combat_effects.js?v=15.24.0" defer></script>
<script src="js/world_environment.js?v=15.24.0" defer></script>
<script src="js/world_spatial_budget.js?v=15.24.0" defer></script>
<script src="js/world_ants_system.js?v=15.24.0" defer></script>
<script src="js/weather_system.js?v=15.24.0" defer></script>
<script src="js/world_update.js?v=15.24.0" defer></script>
<script src="js/world_placement_log.js?v=15.24.0" defer></script>
<script src="js/world_bootstrap.js?v=15.24.0" defer></script>
<script src="js/text_catalog.js?v=15.24.0" defer></script>
<script src="js/ui.js?v=15.24.0" defer></script>
<script src="js/ui_log.js?v=15.24.0" defer></script>
<script src="js/ui_helpers.js?v=15.24.0" defer></script>
<script src="js/ui_selected.js?v=15.24.0" defer></script>
<script src="js/snapshot_system.js?v=15.24.0" defer></script>
<script src="js/save_system.js?v=15.24.0" defer></script>
<script src="js/patch_helpers.js?v=15.24.0" defer></script>
<script src="js/freeze_system.js?v=15.24.0" defer></script>
<script src="js/relation_event_system.js?v=15.24.0" defer></script>
<script src="js/ui_layout_dialogs.js?v=15.24.0" defer></script>
<script src="js/ui_tools.js?v=15.24.0" defer></script>
<script src="js/ui_bind.js?v=15.24.0" defer></script>
<script src="js/ui_charts.js?v=15.24.0" defer></script>
<script src="js/ui_family_data.js?v=15.24.0" defer></script>
<script src="js/ui_family_async.js?v=15.24.0" defer></script>
<script src="js/ui_family_layout.js?v=15.24.0" defer></script>
<script src="js/ui_family_paths.js?v=15.24.0" defer></script>
<script src="js/ui_family_render.js?v=15.24.0" defer></script>
<script src="js/main.js?v=15.24.0" defer></script>
<script src="js/debug_tools.js?v=15.24.0" defer></script>
<script>
(() => {
const replacements = [
["端末報告ベース", "達成率"],
["報告プレイヤー", "達成人数"],
];
const replaceText = value => replacements.reduce((text, [from, to]) => text.replaceAll(from, to), String(value || ""));
const normalizeNode = node => {
if (!node) return;
if (node.nodeType === Node.TEXT_NODE) {
const next = replaceText(node.nodeValue);
if (next !== node.nodeValue) node.nodeValue = next;
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.hasAttribute("aria-label")) {
const current = node.getAttribute("aria-label");
const next = replaceText(current);
if (next !== current) node.setAttribute("aria-label", next);
}
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
while (walker.nextNode()) {
const textNode = walker.currentNode;
const next = replaceText(textNode.nodeValue);
if (next !== textNode.nodeValue) textNode.nodeValue = next;
}
node.querySelectorAll?.("[aria-label]").forEach(element => {
const current = element.getAttribute("aria-label");
const next = replaceText(current);
if (next !== current) element.setAttribute("aria-label", next);
});
};
const list = document.getElementById("achievementList");
if (!list) return;
normalizeNode(list);
new MutationObserver(records => {
for (const record of records) {
if (record.type === "characterData") normalizeNode(record.target);
for (const node of record.addedNodes || []) normalizeNode(node);
}
}).observe(list, { childList: true, subtree: true, characterData: true });
})();
</script>
<script src="js/version.js?v=39.18.18" defer></script>
<script src="js/event_bus.js?v=39.18.18" defer></script>
<script src="js/domain_ids.js?v=39.18.18" defer></script>
<script src="js/registry_base.js?v=39.18.18" defer></script>
<script src="js/disease_registry.js?v=39.18.18" defer></script>
<script src="js/sound_pack.js?v=39.18.18" defer></script>
<script src="js/deterministic_helpers.js?v=39.18.18" defer></script>
<script src="js/item_tool_metadata.js?v=39.18.18" defer></script>
<script src="js/item_tool_definitions.js?v=39.18.18" defer></script>
<script src="js/item_visual_definitions.js?v=39.18.18" defer></script>
<script src="js/item_food_definitions.js?v=39.18.18" defer></script>
<script src="js/item_effect_definitions.js?v=39.18.18" defer></script>
<script src="js/item_registry.js?v=39.18.18" defer></script>
<script src="js/data.js?v=39.18.18" defer></script>
<script src="js/ground_types.js?v=39.18.18" defer></script>
<script src="js/input_mode_manager.js?v=39.18.18" defer></script>
<script src="js/math.js?v=39.18.18" defer></script>
<script src="js/geometry_helpers.js?v=39.18.18" defer></script>
<script src="js/collision_footprint_system.js?v=39.18.18" defer></script>
<script src="js/physics_helpers.js?v=39.18.18" defer></script>
<script src="js/placement_preview_system.js?v=39.18.18" defer></script>
<script src="js/pin_attachment_system.js?v=39.18.18" defer></script>
<script src="js/physics_shape_editor_system.js?v=39.18.18" defer></script>
<script src="js/mechanical_system.js?v=39.18.18" defer></script>
<script src="js/mechanical_shape_bridge.js?v=39.18.18" defer></script>
<script src="js/constraint_system.js?v=39.18.18" defer></script>
<script src="js/physics_world_system.js?v=39.18.18" defer></script>
<script src="js/physics_projection_system.js?v=39.18.18" defer></script>
<script src="js/circuit_board_system.js?v=39.18.18" defer></script>
<script src="js/signal_system.js?v=39.18.18" defer></script>
<script src="js/assets.js?v=39.18.18" defer></script>
<script src="js/audio.js?v=39.18.18" defer></script>
<script src="js/perf_profiler.js?v=39.18.18" defer></script>
<script src="js/display_helpers.js?v=39.18.18" defer></script>
<script src="js/render.js?v=39.18.18" defer></script>
<script src="js/sim_core.js?v=39.18.18" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.18.18" defer></script>
<script src="js/structures.js?v=39.18.18" defer></script>
<script src="js/item_type_catalog.js?v=39.18.18" defer></script>
<script src="js/items.js?v=39.18.18" defer></script>
<script src="js/item_type_initializers.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_support.js?v=39.18.18" defer></script>
<script src="js/item_update_policy.js?v=39.18.18" defer></script>
<script src="js/fire_runtime_system.js?v=39.18.18" defer></script>
<script src="js/robot_cleaner_system.js?v=39.18.18" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.18.18" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.18.18" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.18.18" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.18.18" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.18.18" defer></script>
<script src="js/item_environment_hazard_system.js?v=39.18.18" defer></script>
<script src="js/item_dynamic_system.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.18.18" defer></script>
<script src="js/update_step_pipeline_runner.js?v=39.18.18" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.18.18" defer></script>
<script src="js/item_runtime.js?v=39.18.18" defer></script>
<script src="js/structure_lifecycle.js?v=39.18.18" defer></script>
<script src="js/memorial_bell_system.js?v=39.18.18" defer></script>
<script src="js/item_render_helpers.js?v=39.18.18" defer></script>
<script src="js/item_render_runtime.js?v=39.18.18" defer></script>
<script src="js/burn_motion_util.js?v=39.18.18" defer></script>
<script src="js/ants.js?v=39.18.18" defer></script>
<script src="js/health.js?v=39.18.18" defer></script>
<script src="js/tarinai.js?v=39.18.18" defer></script>
<script src="js/mamekusaredake_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_action_spec.js?v=39.18.18" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.18.18" defer></script>
<script src="js/tarinai_identity_social.js?v=39.18.18" defer></script>
<script src="js/tarinai_action_state.js?v=39.18.18" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.18.18" defer></script>
<script src="js/tarinai_item_effects.js?v=39.18.18" defer></script>
<script src="js/tarinai_needs_core.js?v=39.18.18" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.18.18" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.18.18" defer></script>
<script src="js/tarinai_nest_sleep_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.18.18" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.18.18" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.18.18" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.18.18" defer></script>
<script src="js/seesaw_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.18.18" defer></script>
<script src="js/tarinai_needs_items.js?v=39.18.18" defer></script>
<script src="js/tarinai_food_prototype_mixin.js?v=39.18.18" defer></script>
<script src="js/tarinai_direct_feeding_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.18.18" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.18.18" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.18.18" defer></script>
<script src="js/tarinai_runtime.js?v=39.18.18" defer></script>
<script src="js/tarinai_render.js?v=39.18.18" defer></script>
<script src="js/world.js?v=39.18.18" defer></script>
<script src="js/world_view.js?v=39.18.18" defer></script>
<script src="js/family_graph.js?v=39.18.18" defer></script>
<script src="js/world_reset_presets.js?v=39.18.18" defer></script>
<script src="js/world_family_social.js?v=39.18.18" defer></script>
<script src="js/impact_core_system.js?v=39.18.18" defer></script>
<script src="js/impact_response_system.js?v=39.18.18" defer></script>
<script src="js/collision_response_system.js?v=39.18.18" defer></script>
<script src="js/world_combat_effects.js?v=39.18.18" defer></script>
<script src="js/world_environment.js?v=39.18.18" defer></script>
<script src="js/world_temperature_system.js?v=39.18.18" defer></script>
<script src="js/world_pathfinding_system.js?v=39.18.18" defer></script>
<script src="js/world_grass_placement_system.js?v=39.18.18" defer></script>
<script src="js/world_spatial_budget.js?v=39.18.18" defer></script>
<script src="js/world_ants_system.js?v=39.18.18" defer></script>
<script src="js/weather_system.js?v=39.18.18" defer></script>
<script src="js/item_update_scheduler.js?v=39.18.18" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.18.18" defer></script>
<script src="js/tarinai_update_policy.js?v=39.18.18" defer></script>
<script src="js/simulation_environment_system.js?v=39.18.18" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.18.18" defer></script>
<script src="js/simulation_effects_system.js?v=39.18.18" defer></script>
<script src="js/simulation_creature_system.js?v=39.18.18" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.18.18" defer></script>
<script src="js/simulation_ambient_system.js?v=39.18.18" defer></script>
<script src="js/simulation_systems.js?v=39.18.18" defer></script>
<script src="js/system_order.js?v=39.18.18" defer></script>
<script src="js/simulation.js?v=39.18.18" defer></script>
<script src="js/colony_situation_system.js?v=39.18.18" defer></script>
<script src="js/world_update.js?v=39.18.18" defer></script>
<script src="js/world_tool_actions.js?v=39.18.18" defer></script>
<script src="js/world_placement_log.js?v=39.18.18" defer></script>
<script src="js/world_event_effects.js?v=39.18.18" defer></script>
<script src="js/command_dispatcher.js?v=39.18.18" defer></script>
<script src="js/text_catalog.js?v=39.18.18" defer></script>
<script src="js/ui.js?v=39.18.18" defer></script>
<script src="js/game_dialogs.js?v=39.18.18" defer></script>
<script src="js/achievement_catalog.js?v=39.18.18" defer></script>
<script src="js/achievements.js?v=39.18.18" defer></script>
<script src="js/ui_helpers.js?v=39.18.18" defer></script>
<script src="js/ui_log.js?v=39.18.18" defer></script>
<script src="js/ui_selected.js?v=39.18.18" defer></script>
<script src="js/save_schema.js?v=39.18.18" defer></script>
<script src="js/snapshot_system.js?v=39.18.18" defer></script>
<script src="js/restore_coordinator.js?v=39.18.18" defer></script>
<script src="js/history_system.js?v=39.18.18" defer></script>
<script src="js/save_codec.js?v=39.18.18" defer></script>
<script src="js/save_storage.js?v=39.18.18" defer></script>
<script src="js/save_system.js?v=39.18.18" defer></script>
<script src="js/ui_tooltips.js?v=39.18.18" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.18.18" defer></script>
<script src="js/ui_ground.js?v=39.18.18" defer></script>
<script src="js/ui_tools.js?v=39.18.18" defer></script>
<script src="js/ui_input_shared.js?v=39.18.18" defer></script>
<script src="js/ui_pointer_action_system.js?v=39.18.18" defer></script>
<script src="js/ui_input_touch.js?v=39.18.18" defer></script>
<script src="js/ui_input_mouse.js?v=39.18.18" defer></script>
<script src="js/ui_bind.js?v=39.18.18" defer></script>
<script src="js/ui_charts.js?v=39.18.18" defer></script>
<script src="js/ui_family_data.js?v=39.18.18" defer></script>
<script src="js/ui_family_async.js?v=39.18.18" defer></script>
<script src="js/ui_family_layout.js?v=39.18.18" defer></script>
<script src="js/ui_family_paths.js?v=39.18.18" defer></script>
<script src="js/ui_family_render.js?v=39.18.18" defer></script>
<script src="js/main.js?v=39.18.18" defer></script>
<script src="js/debug_tools.js?v=39.18.18" defer></script>
</body>
</html>

731
js/achievement_catalog.js Normal file
View file

@ -0,0 +1,731 @@
window.TARINAI_ACHIEVEMENT_CATALOG = {
"revision": "2026-07-25-achievement-cleanup-v1",
"completionistId": "true_tarinai_observer",
"categories": [
{
"id": "ecology",
"title": "生態"
},
{
"id": "population",
"title": "人口"
},
{
"id": "experiment",
"title": "実験"
},
{
"id": "construction",
"title": "建築"
},
{
"id": "operation",
"title": "操作"
},
{
"id": "other",
"title": "その他"
}
],
"medicineLedgerTypes": [
"first_aid",
"sedative",
"sleep_drug",
"laxative",
"protein",
"niteropu",
"ammo",
"mystery_drug",
"mercury",
"giant_drug",
"dwarf_drug"
],
"linkProgressTypes": [
"rope",
"rod",
"spring"
],
"additiveProgressCaps": {
"placementCount": 100,
"antKills": 100,
"birthCount": 3333,
"quickDeleteCount": 30,
"undoCount": 20,
"redoCount": 20,
"directFeedCount": 33,
"firstAidHeals": 50,
"fightMochiFightCount": 666,
"shotCount": 333,
"loveMochiBirthCount": 721,
"statsButtonPressCount": 30,
"totalDeathCount": 10000,
"manualTarinaiAddedCount": 100,
"robotCleanCount": 200
},
"definitions": [
{
"id": "first_birth",
"title": "はじめての繁殖",
"description": "たりないの繁殖を1回成功させる。",
"category": "population",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "birthCount",
"target": 1
}
},
{
"id": "natural_zunchi_slave",
"title": "ずんちどれい",
"description": "ずんちどれいが出現する。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "ochibi_natural_slave",
"title": "たりないより「たりない」",
"description": "おちびが鎖無しでずんちどれいになる。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "natural_tarinai_king",
"title": "たりない王",
"description": "たりない王が出現する。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "self_zunchi_death",
"title": "あぁ^~ たまらねぇぜ",
"description": "おしり鋲を抜いた時、自分のずんちで自爆する。",
"category": "experiment",
"showLockedDescription": true
},
{
"id": "zunchi_collision_death",
"title": "くちゃいのだ!",
"description": "ずんちとの衝突で死亡する個体が出現する。",
"category": "experiment",
"showLockedDescription": true
},
{
"id": "natural_zunchi_slave_5_generations",
"title": "末代までの恥",
"description": "5代連続でずんちどれい鎖なし",
"category": "population",
"showLockedDescription": true
},
{
"id": "natural_tarinai_king_3_generations",
"title": "華麗なる王族",
"description": "3代連続でたりない王王冠なし",
"category": "population",
"showLockedDescription": true
},
{
"id": "death_50_in_10_seconds",
"title": "6番目の大量絶滅",
"description": "10秒以内に50体のたりないが死亡する。",
"category": "experiment",
"showLockedDescription": true
},
{
"id": "birth_50_in_60_seconds",
"title": "ベビーブーム",
"description": "1分以内に30体のたりないが誕生する。",
"category": "population",
"showLockedDescription": true
},
{
"id": "great_mother_1000_births",
"title": "母なる大地",
"description": "たりないを繁殖で累計3333体誕生させる。",
"category": "population",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "birthCount",
"target": 3333,
"display": "percent",
"unit": "体"
}
},
{
"id": "lifespan_completed",
"title": "天寿",
"description": "たりない1体が天寿を全うする。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "soccer_ball_death",
"title": "豆野、殺ッカーやろうぜ!",
"description": "サッカーボールとの衝突で、たりない1体が死亡する。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "fight_pair_danger_kill",
"title": "喧嘩両成敗",
"description": "喧嘩中のたりないを、両方とも危険カテゴリのアイテムで倒す。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "all_non_sleep_diseased_25",
"title": "緊急事態宣言",
"description": "25体以上が存命し、フィールド上の全員がねむり病以外の病気にかかる。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "colony_happy",
"title": "管理された楽園",
"description": "コロニー状態をしばらく「幸福」にする。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "direct_feed_33",
"title": "餌やりじいさん",
"description": "手から直接33回餌を与える。",
"category": "ecology",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "directFeedCount",
"target": 33
}
},
{
"id": "ignite_during_birth_ritual",
"title": "燃え上がる恋",
"description": "たりないがヘコヘコ中に着火する。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "laxative_starvation",
"title": "内臓全部出た",
"description": "下剤の効果中に餓死する。",
"category": "experiment",
"showLockedDescription": true
},
{
"id": "idle_observer_5_minutes",
"title": "監視カメラ",
"description": "3ゲーム日、操作せず画面を見るだけ",
"category": "operation",
"showLockedDescription": false
},
{
"id": "placed_objects_100",
"title": "物でいっぱい",
"description": "配置物を累計100個置く。",
"category": "construction",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "placementCount",
"target": 100
}
},
{
"id": "mechanized_industry",
"title": "機械化産業",
"description": "検知器の信号で接続先を作動させ、ロープ・棒・ばねをそれぞれ1本以上設置する。",
"category": "construction",
"showLockedDescription": false,
"progress": {
"type": "mechanized"
}
},
{
"id": "safe_colony_25_5_minutes",
"title": "安全第一",
"description": "25体以上いる状態をゲーム内5分間維持し、その間寿命以外で死亡者を出さない。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "ants_alive_25",
"title": "アリの惑星",
"description": "生存しているアリが25体に到達する。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "ants_killed_100",
"title": "害虫駆除",
"description": "アリを累計100体倒す。",
"category": "experiment",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "antKills",
"target": 100
}
},
{
"id": "secret_collection_9_slots",
"title": "秘密のコレクション",
"description": "セーブスロット9つをすべて埋める。",
"category": "operation",
"showLockedDescription": false
},
{
"id": "pause_spam_4_in_1_second",
"title": "何やってるの?",
"description": "何もない部分を1秒間に4回連打する。",
"category": "operation",
"showLockedDescription": false
},
{
"id": "continuous_play_1_hour",
"title": "めっちゃたりない観察",
"description": "1時間連続プレイする。",
"category": "other",
"showLockedDescription": true
},
{
"id": "below_absolute_zero_item",
"title": "物理法則を下側に超越",
"description": "温度計算されるアイテムを1つ絶対零度を下回る温度にする。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "undo_mass_revival",
"title": "見なかったことにしよう",
"description": "一度のUndoで10体以上の死亡をなかったことにする。",
"category": "operation",
"showLockedDescription": false
},
{
"id": "robot_cleaner_100",
"title": "清掃業者",
"description": "ロボ掃除機にずんち・死骸・汚れを累計200個処理させる。",
"category": "construction",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "robotCleanCount",
"target": 200,
"display": "count",
"unit": "個"
}
},
{
"id": "held_30_seconds",
"title": "空中生活",
"description": "同じたりないを30秒間つまみ続ける。",
"category": "operation",
"showLockedDescription": false
},
{
"id": "minimalist_happy",
"title": "ミニマリスト",
"description": "プレイ開始6日目以降、配置物を10個以下に抑えてコロニー状態を「幸福」にする。",
"category": "construction",
"showLockedDescription": false
},
{
"id": "overprotective",
"title": "過保護",
"description": "同じたりないに手から10回餌を与え、3回以上薬で治療する。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "self_sufficient",
"title": "自給自足",
"description": "複製機がない状態で、20体以上を直接給餌せず10分間維持する。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "unplanned_city_30",
"title": "無計画都市",
"description": "配置後30秒以内に削除する操作を累計30回行う。",
"category": "construction",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "quickDeleteCount",
"target": 30
}
},
{
"id": "sauna_cold_plunge",
"title": "サウナ水風呂",
"description": "暑すぎる状態になったたりないを、15秒以内に寒すぎる状態にする。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "rain_shelter_all",
"title": "雨宿り完了",
"description": "雨天時、20体以上の全員をパラソル・巣箱・土管で雨宿りさせる。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "medicine_ledger_all",
"title": "おくすり手帳全埋め",
"description": "全種類の薬を少なくとも1回使用する。",
"category": "ecology",
"showLockedDescription": false,
"progress": {
"type": "set",
"key": "medicineTypes",
"required": "medicineLedgerTypes"
}
},
{
"id": "mercury_lifespan",
"title": "不老不死まであと一歩",
"description": "水銀を使用したたりないが天寿を全うする。",
"category": "experiment",
"showLockedDescription": true
},
{
"id": "enemy_enemy_friend",
"title": "敵の敵は味方",
"description": "たりない30体以上を無傷で保ちながら、危険アイテムでアリを10体倒す。",
"category": "ecology",
"showLockedDescription": true
},
{
"id": "revolution",
"title": "革命",
"description": "ずんちどれいが、たりない王を喧嘩で死亡させる。",
"category": "experiment",
"showLockedDescription": true
},
{
"id": "fuel_to_fire",
"title": "火に油",
"description": "けんか餅を与えたたりないが30秒以内に喧嘩を始める。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "undo_20",
"title": "やっぱなし",
"description": "Undoを累計20回成功させる。",
"category": "operation",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "undoCount",
"target": 20
}
},
{
"id": "redo_20",
"title": "やっぱあり",
"description": "Redoを累計20回成功させる。",
"category": "operation",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "redoCount",
"target": 20
}
},
{
"id": "eternal_history_generation_10",
"title": "悠久の歴史",
"description": "世代が10に到達する。",
"category": "population",
"showLockedDescription": true
},
{
"id": "king_full_satisfaction",
"title": "五体満足",
"description": "たりない王1個体にプロテインと巨大薬の効果を同時に付与する。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "slave_zero_satisfaction",
"title": "零体満足",
"description": "ずんちどれい1個体にンイテロプと矮小薬の効果を同時に付与する。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "town_doctor_50",
"title": "町のお医者さん",
"description": "救急薬を累計50回使用し、体力を回復させる。",
"category": "ecology",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "firstAidHeals",
"target": 50,
"display": "count",
"unit": "回"
}
},
{
"id": "poke_plushie_fling",
"title": "割とふわふわ",
"description": "ぬいぐるみを「つつく」で弾き飛ばす。",
"category": "operation",
"showLockedDescription": false
},
{
"id": "chaos_seeker_666_fights",
"title": "混沌ヲ希求スル者",
"description": "けんか餅の影響下で喧嘩を累計666回発生させる。",
"category": "experiment",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "fightMochiFightCount",
"target": 666,
"display": "percent",
"unit": "回"
}
},
{
"id": "clean_freak_robot_only",
"title": "潔癖症",
"description": "ロボット掃除機の設定全項目にチェックを入れて稼働させ、フィールド上にロボット掃除機以外を存在させない。",
"category": "construction",
"showLockedDescription": false
},
{
"id": "true_tarinai_observer",
"title": "真・たりない観察",
"description": "これ以外の全ての実績を達成する。",
"category": "other",
"showLockedDescription": false
},
{
"id": "sniper_333_shots",
"title": "狙撃手",
"description": "累計333発撃つ。",
"category": "operation",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "shotCount",
"target": 333
}
},
{
"id": "megalopolis",
"title": "メガロポリス",
"description": "複製機を10個以上、干草寝床・巣箱・土管を合計15個以上設置し、たりないを100体以上生存させる。",
"category": "construction",
"showLockedDescription": true
},
{
"id": "fertility_seeker_721_love_births",
"title": "豊穣ヲ希求スル者",
"description": "へこ餅の影響下で繁殖を721回成功させる。",
"category": "population",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "loveMochiBirthCount",
"target": 721,
"display": "percent",
"unit": "回"
}
},
{
"id": "low_fps_single_digit",
"title": "スペックがたりない",
"description": "fpsを1桁にする。",
"category": "other",
"showLockedDescription": true
},
{
"id": "continuous_play_24_hours",
"title": "寝ろ",
"description": "24時間連続でプレイする。",
"category": "other",
"showLockedDescription": true
},
{
"id": "sandbox_five_toilets",
"title": "砂場",
"description": "トイレの砂をフィールドに5個設置する。",
"category": "construction",
"showLockedDescription": false
},
{
"id": "park_ground_changed",
"title": "そこ公共空間だよ?",
"description": "公園の地面を勝手に変更する。",
"category": "construction",
"showLockedDescription": false
},
{
"id": "ground_change_4_in_1_second",
"title": "破格の工事費用",
"description": "地面を変えすぎて破産する。",
"category": "construction",
"showLockedDescription": false
},
{
"id": "ant_nest_without_tarinai",
"title": "アリ観察",
"description": "アリの巣があるが、たりないがいない。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "sticky_bomb_15_passes",
"title": "命のバトン",
"description": "粘着ボムを爆発までに15回以上パスする。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "daily_play_7_days",
"title": "毎日たりない観察",
"description": "7日連続でプレイする。",
"category": "other",
"showLockedDescription": true,
"progress": {
"type": "value",
"key": "dailyPlayStreak",
"target": 7,
"display": "percent",
"unit": "日"
}
},
{
"id": "wire_shock_7_tarinai",
"title": "抵抗器じゃない",
"description": "電流が流れている1本の電線で7体のたりないを感電させる。",
"category": "experiment",
"showLockedDescription": false
},
{
"id": "information_industry",
"title": "情報化産業",
"description": "基盤同士を電線類でつなぎ、信号を流す。",
"category": "construction",
"showLockedDescription": false
},
{
"id": "strength_in_numbers",
"title": "数こそ力",
"description": "生存しているたりないが100体に到達する。",
"category": "population",
"showLockedDescription": false
},
{
"id": "elite_few",
"title": "少数精鋭",
"description": "たりないが25体以下の状態を5日間維持する。",
"category": "population",
"showLockedDescription": true
},
{
"id": "zunchi_overflow",
"title": "踩屎感",
"description": "たりないが20体以上いる状態で、ずんちの数がたりないの数を上回る。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "comfortable_beds",
"title": "快適な寝床",
"description": "生存している全てのたりないに行き渡る数の寝床を用意する。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "stone_pillow",
"title": "石に枕す",
"description": "たりないが30体以上いる状態で、寝床充足率を0%にする。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "across_seasons",
"title": "季節を越えて",
"description": "春夏秋冬の四季を一巡する。",
"category": "other",
"showLockedDescription": false
},
{
"id": "favorite_one",
"title": "推し",
"description": "ある個体をお気に入りに登録する。",
"category": "ecology",
"showLockedDescription": false
},
{
"id": "statistician",
"title": "統計学者",
"description": "統計の個体数・欲求・環境・グラフ表示切替ボタンを累計30回押す。",
"category": "operation",
"showLockedDescription": false,
"progress": {
"type": "counter",
"key": "statsButtonPressCount",
"target": 30,
"display": "count",
"unit": "回"
}
},
{
"id": "well_informed",
"title": "情報通",
"description": "プッシュ通知をONにする。",
"category": "operation",
"showLockedDescription": false
},
{
"id": "lively_making",
"title": "賑わいづくり",
"description": "「たりないを追加」で100体追加する。",
"category": "population",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "manualTarinaiAddedCount",
"target": 100,
"display": "count",
"unit": "体"
}
},
{
"id": "memento_mori",
"title": "多数の死は統計上の数字でしかない",
"description": "たりないが合計10000体死亡する。",
"category": "population",
"showLockedDescription": true,
"progress": {
"type": "counter",
"key": "totalDeathCount",
"target": 10000,
"display": "percent",
"unit": "体"
}
},
{
"id": "mad_scientist",
"title": "マッドサイエンティスト",
"description": "異なる10匹のたりないに怪しい薬を投与する。",
"category": "experiment",
"showLockedDescription": false,
"progress": {
"type": "unique",
"key": "mysteryDrugTarinaiIds",
"target": 10,
"display": "count",
"unit": "匹"
}
}
]
};

3157
js/achievements.js Normal file

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more