prts-terrarchive 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GAME_ASSETS.md +26 -0
- package/LICENSE +21 -0
- package/README.en.md +274 -0
- package/README.md +423 -0
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/bin/bake-rhine-details.mjs +34 -0
- package/bin/install.js +273 -0
- package/bin/pack-map-assets.mjs +110 -0
- package/bin/preview-rhine.mjs +71 -0
- package/bin/rhine-detail-baker.ts +84 -0
- package/contracts/cloud_tools_v1.schema.json +243 -0
- package/contracts/corpus_i18n_v1.schema.json +179 -0
- package/contracts/corpus_tools_v1.schema.json +1801 -0
- package/cordis.patch.yml +32 -0
- package/docs/endfield-official-i18n.md +103 -0
- package/lib/client.js +3912 -0
- package/lib/endfield-map/map.js.br +0 -0
- package/lib/endfield-map/map.js.gz +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.br +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.gz +0 -0
- package/lib/endfield-map/resources/map-01616f534aaaeca1.png +0 -0
- package/lib/endfield-map/resources/map-1562e0dff4be3d23.png +0 -0
- package/lib/endfield-map/resources/map-160fe040f080f6a3.png +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.br +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.gz +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.br +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.gz +0 -0
- package/lib/endfield-map/resources/map-1d30baaaef3e29cc.png +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.br +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.gz +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.br +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.gz +0 -0
- package/lib/endfield-map/resources/map-26caf8d71247abb7.png +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.br +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.gz +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.br +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.br +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.br +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.gz +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.br +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.gz +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.br +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.gz +0 -0
- package/lib/endfield-map/resources/map-3d486e887ae121ac.png +0 -0
- package/lib/endfield-map/resources/map-418dc4865608fce5.png +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.br +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.gz +0 -0
- package/lib/endfield-map/resources/map-4371259d3ab7aac7.png +0 -0
- package/lib/endfield-map/resources/map-48dcc0fd55e13f7f.png +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.br +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.gz +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.br +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.gz +0 -0
- package/lib/endfield-map/resources/map-5177e9d1d46f47e2.png +0 -0
- package/lib/endfield-map/resources/map-51baeb59b7f8c28d.png +0 -0
- package/lib/endfield-map/resources/map-549561b46e621874.png +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.br +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.gz +0 -0
- package/lib/endfield-map/resources/map-5a6963909423b674.png +0 -0
- package/lib/endfield-map/resources/map-5a9c533c0552d7b7.png +0 -0
- package/lib/endfield-map/resources/map-5d20ae9ffa9a02b0.png +0 -0
- package/lib/endfield-map/resources/map-626c8847393f60bf.png +0 -0
- package/lib/endfield-map/resources/map-63d43cbec3bed3b8.png +0 -0
- package/lib/endfield-map/resources/map-661b6ec56e722574.png +0 -0
- package/lib/endfield-map/resources/map-6ddd384eca190828.png +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.br +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.gz +0 -0
- package/lib/endfield-map/resources/map-738ebaa002c33cfc.png +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.br +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.gz +0 -0
- package/lib/endfield-map/resources/map-78929f26d6844509.png +0 -0
- package/lib/endfield-map/resources/map-7f09c142950a0f74.png +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.br +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.gz +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.br +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.gz +0 -0
- package/lib/endfield-map/resources/map-8d1ade3f01d952b0.png +0 -0
- package/lib/endfield-map/resources/map-8fef3495bb7ed60f.png +0 -0
- package/lib/endfield-map/resources/map-907a7ecf8a0ce352.png +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.br +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.gz +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.br +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-99722e61da5f3e53.png +0 -0
- package/lib/endfield-map/resources/map-9be9596cc059abe3.png +0 -0
- package/lib/endfield-map/resources/map-a4db9a4763e47fbf.png +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.br +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.gz +0 -0
- package/lib/endfield-map/resources/map-ab35d06875ee418f.png +0 -0
- package/lib/endfield-map/resources/map-ac51bb3e7623cd8b.png +0 -0
- package/lib/endfield-map/resources/map-ac830a66296bc83a.png +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.br +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8164f1af7c5d895.png +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.br +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.br +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.gz +0 -0
- package/lib/endfield-map/resources/map-bfc58277d5ba649d.png +0 -0
- package/lib/endfield-map/resources/map-c013f82048c6891a.png +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.br +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.gz +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.br +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.gz +0 -0
- package/lib/endfield-map/resources/map-c607f8b4c8ad8967.png +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.br +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.gz +0 -0
- package/lib/endfield-map/resources/map-d51adf5952af1e8e.png +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.br +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.gz +0 -0
- package/lib/endfield-map/resources/map-db11d394456a7465.png +0 -0
- package/lib/endfield-map/resources/map-dcc4d218581e3dc6.png +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.br +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-defe5db7dabf748a.png +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.br +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.gz +0 -0
- package/lib/endfield-map/resources/map-e25ee9af6e493500.png +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.br +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.gz +0 -0
- package/lib/endfield-map/resources/map-e694a91e630b1bd6.png +0 -0
- package/lib/endfield-map/resources/map-eaa93af79d12a87d.png +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.br +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.gz +0 -0
- package/lib/endfield-map/resources/map-ede421a329dc78a4.png +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.br +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.gz +0 -0
- package/lib/endfield-map/resources/map-fb5f4a59a181452c.png +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.br +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.gz +0 -0
- package/lib/rhine/assets/archive-assembly.glb +0 -0
- package/lib/rhine/assets/archive-cassette.glb +0 -0
- package/lib/rhine/fonts/MiSans-Bold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Demibold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Light.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Regular.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-license.pdf +0 -0
- package/lib/rhine/fonts/NOTICE.txt +6 -0
- package/lib/rhine/licenses/RhineLabUI-MIT.txt +21 -0
- package/lib/rhine/licenses/entities-BSD-2-Clause.txt +11 -0
- package/lib/rhine/licenses/linkify-it-MIT.txt +22 -0
- package/lib/rhine/licenses/markdown-it-MIT.txt +22 -0
- package/lib/rhine/licenses/mdurl-MIT.txt +45 -0
- package/lib/rhine/licenses/punycode.js-MIT.txt +20 -0
- package/lib/rhine/licenses/rolling-number-MIT.txt +21 -0
- package/lib/rhine/licenses/three-MIT.txt +21 -0
- package/lib/rhine/licenses/uc.micro-MIT.txt +20 -0
- package/lib/rhine/rhine.css +1 -0
- package/lib/rhine/rhine.js +5106 -0
- package/lib/skins/common.css +31 -0
- package/lib/skins/endfield-aic.css +141 -0
- package/lib/skins/prts-agent.css +136 -0
- package/lib/skins/rhine-lab.css +70 -0
- package/package.json +112 -0
- package/presets/definition.js +26 -0
- package/presets/prts/agent.cordis.yml +25 -0
- package/presets/prts/preset.yml +3 -0
- package/presets/register.js +86 -0
- package/resources/entity-routing.json +21 -0
- package/skills/prts-investigation/SKILL.md +121 -0
- package/skills/prts-retrieval/SKILL.md +35 -0
- package/skills/prts-retrieval/references/cloud-tools.md +13 -0
- package/skills/prts-retrieval/references/diagnostics.md +15 -0
- package/skills/prts-retrieval/references/local-tools.md +24 -0
- package/skills/prts-retrieval/references/module-arknights.md +23 -0
- package/skills/prts-retrieval/references/module-dual.md +15 -0
- package/skills/prts-retrieval/references/module-endfield.md +21 -0
- package/skills/prts-retrieval/references/retrieval-process.md +44 -0
- package/skills/prts-retrieval/references/retrieval-recipes-dual.md +34 -0
- package/skills/prts-retrieval/references/retrieval-recipes-endfield.md +37 -0
- package/skills/prts-retrieval/references/retrieval-recipes.md +39 -0
- package/skills/prts-retrieval/references/tools-runtime.md +48 -0
- package/skills/prts-retrieval/references/wiki-schema.md +38 -0
- package/src/archive-presentation.js +287 -0
- package/src/cloud-projection.js +124 -0
- package/src/cloud.js +498 -0
- package/src/entity-recognizer.js +311 -0
- package/src/entity-routing.js +82 -0
- package/src/evidence-state.js +332 -0
- package/src/i18n.js +365 -0
- package/src/index.js +1261 -0
- package/src/installer.js +1362 -0
- package/src/investigation-review.js +87 -0
- package/src/investigation-store.js +579 -0
- package/src/investigation-tools.js +113 -0
- package/src/localization-format.js +23 -0
- package/src/manual-search.js +103 -0
- package/src/read.js +1097 -0
- package/src/release-compatibility.js +73 -0
- package/src/release-watch.js +40 -0
- package/src/search-projection.js +139 -0
- package/src/search.js +1522 -0
- package/src/skill.js +98 -0
- package/src/source-map.js +348 -0
- package/src/state.js +314 -0
- package/src/store.js +1569 -0
- package/src/timeline.js +386 -0
- package/src/ui.js +1353 -0
- package/src/wiki.js +111 -0
- package/tsconfig.rhine.json +15 -0
- package/ui/rhine/archive-navigation-limit.ts +31 -0
- package/ui/rhine/archive-rack.ts +83 -0
- package/ui/rhine/assets/evidence-observatory.webp +0 -0
- package/ui/rhine/board-plane-transform.ts +24 -0
- package/ui/rhine/catalogue.ts +72 -0
- package/ui/rhine/deferred-preparation.ts +146 -0
- package/ui/rhine/evidence-board-controls.css +22 -0
- package/ui/rhine/evidence-board-history.ts +73 -0
- package/ui/rhine/evidence-board-model.ts +189 -0
- package/ui/rhine/evidence-board-panel.ts +762 -0
- package/ui/rhine/evidence-board-scene.ts +548 -0
- package/ui/rhine/evidence-board.css +191 -0
- package/ui/rhine/evidence-card-art.ts +470 -0
- package/ui/rhine/evidence-card-style.ts +17 -0
- package/ui/rhine/evidence-inbox-scene.ts +43 -0
- package/ui/rhine/evidence-inbox.css +29 -0
- package/ui/rhine/evidence-inbox.ts +104 -0
- package/ui/rhine/index.ts +12 -0
- package/ui/rhine/interaction.css +20 -0
- package/ui/rhine/investigation-board-plane.ts +5 -0
- package/ui/rhine/investigation-board.css +34 -0
- package/ui/rhine/investigation-board.ts +348 -0
- package/ui/rhine/investigation-edits.ts +46 -0
- package/ui/rhine/investigation-layout.ts +29 -0
- package/ui/rhine/label-prefetch.ts +34 -0
- package/ui/rhine/looping-title.ts +40 -0
- package/ui/rhine/manual-search.css +41 -0
- package/ui/rhine/manual-search.ts +89 -0
- package/ui/rhine/original/appearance.ts +180 -0
- package/ui/rhine/original/archive-label.ts +84 -0
- package/ui/rhine/original/archive-lighting.ts +39 -0
- package/ui/rhine/original/archive-loop.ts +82 -0
- package/ui/rhine/original/archive-refill.ts +64 -0
- package/ui/rhine/original/array-detail-data.ts +2 -0
- package/ui/rhine/original/array-detail.ts +41 -0
- package/ui/rhine/original/array-interior.ts +411 -0
- package/ui/rhine/original/array-occlusion.ts +166 -0
- package/ui/rhine/original/array-shell.ts +38 -0
- package/ui/rhine/original/array-visibility.ts +309 -0
- package/ui/rhine/original/asset-url.ts +4 -0
- package/ui/rhine/original/brand.ts +12 -0
- package/ui/rhine/original/cassette-lod.ts +81 -0
- package/ui/rhine/original/data.ts +21 -0
- package/ui/rhine/original/decryption.ts +185 -0
- package/ui/rhine/original/detail-signature.ts +18 -0
- package/ui/rhine/original/glass-reveal.ts +45 -0
- package/ui/rhine/original/internal-optics.ts +70 -0
- package/ui/rhine/original/label-renderer.ts +119 -0
- package/ui/rhine/original/label-textures.ts +117 -0
- package/ui/rhine/original/model-viewer.ts +658 -0
- package/ui/rhine/original/motion.ts +116 -0
- package/ui/rhine/original/opaque-backfaces.ts +64 -0
- package/ui/rhine/original/program-preparation.ts +111 -0
- package/ui/rhine/original/quality-renderer.ts +97 -0
- package/ui/rhine/original/quality-settings.ts +111 -0
- package/ui/rhine/original/render-quality.ts +163 -0
- package/ui/rhine/original/scene.ts +2051 -0
- package/ui/rhine/original/shelf-interior.ts +142 -0
- package/ui/rhine/original/ui-transitions.ts +111 -0
- package/ui/rhine/original/viewer-camera.ts +92 -0
- package/ui/rhine/original/viewport-layout.ts +41 -0
- package/ui/rhine/original-ui.css +2334 -0
- package/ui/rhine/readability.css +46 -0
- package/ui/rhine/reading-object.ts +96 -0
- package/ui/rhine/reading-origin.ts +28 -0
- package/ui/rhine/reading-room.css +55 -0
- package/ui/rhine/report-markdown.ts +98 -0
- package/ui/rhine/scene-edge-navigation.ts +84 -0
- package/ui/rhine/scene.ts +1726 -0
- package/ui/rhine/shelf-layout.ts +38 -0
- package/ui/rhine/snapshot-update-gate.ts +22 -0
- package/ui/rhine/source-reading.ts +15 -0
- package/ui/rhine/source-selection.css +164 -0
- package/ui/rhine/temporary-performance-observers.ts +163 -0
- package/ui/rhine/temporary-performance-panel.ts +124 -0
- package/ui/rhine/temporary-performance.css +19 -0
- package/ui/rhine/temporary-performance.ts +552 -0
- package/ui/rhine/temporary-render-diagnostics.ts +105 -0
- package/ui/rhine/tool-activity.ts +69 -0
- package/ui/rhine/types.ts +219 -0
- package/ui/rhine/visible-scene-hit.ts +13 -0
- package/ui/rhine/workbench.css +818 -0
- package/ui/rhine/workbench.ts +2245 -0
- package/ui/rhine/workspace-travel.ts +32 -0
- package/vite.rhine.config.ts +19 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Reading and shelf text have their own scale; the room keeps its camera framing. */
|
|
2
|
+
.rhine-workbench .rhine-original-stage{
|
|
3
|
+
--reader-font:max(19px,calc(16px / var(--stage-scale,1)));
|
|
4
|
+
--reader-action-font:max(14px,calc(12px / var(--stage-scale,1)));
|
|
5
|
+
--reader-title-font:34px;
|
|
6
|
+
}
|
|
7
|
+
.rhine-workbench [data-layout=desktop] .rhine-reader{
|
|
8
|
+
left:56%;width:min(780px,calc(44% - var(--edge)));top:24%;bottom:128px;
|
|
9
|
+
max-height:calc(76% - 128px)!important;
|
|
10
|
+
}
|
|
11
|
+
.rhine-workbench .rhine-reader .rhine-reader-title{
|
|
12
|
+
font-size:var(--reader-title-font)!important;max-width:none;overflow-wrap:anywhere;
|
|
13
|
+
}
|
|
14
|
+
.rhine-workbench .rhine-reader :is(.rhine-line-text,.rhine-excerpt-text){
|
|
15
|
+
font-size:var(--reader-font);line-height:1.85;min-width:0;
|
|
16
|
+
}
|
|
17
|
+
.rhine-workbench .rhine-reader .rhine-line-text{flex:1}
|
|
18
|
+
.rhine-workbench .rhine-reader :is(.rhine-reader-actions,.rhine-reader-more){font-size:var(--reader-action-font)}
|
|
19
|
+
.rhine-workbench .rhine-reader .rhine-reader-actions{flex-wrap:wrap;gap:10px 18px}
|
|
20
|
+
.rhine-workbench .rhine-reader :is(.rhine-reader-meta,.rhine-reader-back-report,.rhine-close-reader){font-size:13px}
|
|
21
|
+
.rhine-workbench .rhine-reader :is(.rhine-reader-provenance,.rhine-line-number,.rhine-reader-footer){font-size:11px}
|
|
22
|
+
.rhine-workbench .rhine-reader :is(.rhine-reader-ranges button,.rhine-excerpt-label){font-size:12px}
|
|
23
|
+
.rhine-workbench .rhine-reader .rhine-speaker{font-size:15px}
|
|
24
|
+
.rhine-workbench [data-layout=desktop] .rhine-desk-panel{width:min(440px,calc(37% - var(--edge)))}
|
|
25
|
+
.rhine-workbench [data-layout=desktop] .rhine-shelf-title{font-size:25px;line-height:1.55}
|
|
26
|
+
.rhine-workbench [data-layout=desktop] :is(.rhine-rack-intro,.rhine-shelf-excerpt){font-size:14px}
|
|
27
|
+
.rhine-workbench [data-layout=desktop] :is(.rhine-shelf-meta,.rhine-shelf-number,.rhine-desk-item,.rhine-desk-pagination,.rhine-rack-legend){font-size:12px}
|
|
28
|
+
.rhine-workbench [data-layout=desktop] :is(.rhine-shelf-open,.rhine-shelf-report){font-size:14px}
|
|
29
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]){
|
|
30
|
+
--reader-font:16px;--reader-action-font:12px;--reader-title-font:24px;
|
|
31
|
+
}
|
|
32
|
+
.rhine-workbench [data-layout=portrait]{--reader-title-font:23px}
|
|
33
|
+
.rhine-workbench [data-layout=portrait] .rhine-desk-panel{top:51%;bottom:205px;max-height:none;background:rgba(234,229,225,.95)}
|
|
34
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-shelf-title{font-size:21px}
|
|
35
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) :is(.rhine-rack-intro,.rhine-shelf-meta,.rhine-shelf-excerpt,.rhine-shelf-open){font-size:12px}
|
|
36
|
+
@media(max-height:500px) and (orientation:landscape){
|
|
37
|
+
.rhine-workbench [data-layout=compact]{--reader-title-font:20px}
|
|
38
|
+
.rhine-workbench [data-layout=compact][data-mode=detail] .rhine-reader{top:76px;bottom:32px}
|
|
39
|
+
.rhine-workbench [data-layout=compact] .rhine-reader .detail-kicker{min-height:18px}
|
|
40
|
+
.rhine-workbench .rhine-reader .rhine-reader-title{margin-bottom:2px!important}
|
|
41
|
+
.rhine-workbench .rhine-reader .rhine-reader-meta{font-size:11px}
|
|
42
|
+
.rhine-workbench .rhine-reader .detail-rule{margin-top:4px}
|
|
43
|
+
.rhine-workbench .rhine-reader .rhine-reader-provenance{max-height:18px;margin-top:4px}
|
|
44
|
+
.rhine-workbench .rhine-reader .rhine-reader-actions{gap:5px 10px;padding:5px 0;margin-top:5px}
|
|
45
|
+
.rhine-workbench [data-layout=compact] .rhine-reader-body{padding:4px 0 6px}
|
|
46
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { ARCHIVE_LABEL_LAYER } from './original/label-renderer';
|
|
3
|
+
export const READING_ENTER_MS = 420;
|
|
4
|
+
export const READING_RETURN_MS = 360;
|
|
5
|
+
// Linear x control points make this curve match smoothstep in the scene.
|
|
6
|
+
export const READING_FADE_EASING = 'cubic-bezier(0.333333,0,0.666667,1)';
|
|
7
|
+
export function readingFrameTime() { return Number(document.timeline.currentTime ?? performance.now()); }
|
|
8
|
+
export interface ReadingObject { title: string; code: string; kind: 'report' | 'clue'; enterStartedAt?: number; returnStartedAt?: number }
|
|
9
|
+
|
|
10
|
+
/** A report folio in the existing renderer, distinct from the source cassettes. */
|
|
11
|
+
export function createReadingObject(printMaterial: (texture: THREE.Texture) => THREE.MeshBasicMaterial) {
|
|
12
|
+
const group = new THREE.Group(); group.name = 'Rhine_Research_Folio'; group.visible = false;
|
|
13
|
+
const folio = new THREE.Group(); group.add(folio); folio.rotation.set(.035, -.20, -.045);
|
|
14
|
+
const geometry = new THREE.BoxGeometry(1, 1, 1), face = new THREE.PlaneGeometry(3.45, 4.98);
|
|
15
|
+
const materials: THREE.Material[] = [];
|
|
16
|
+
const material = (color: string, roughness = .8, metalness = 0) => {
|
|
17
|
+
const value = new THREE.MeshStandardMaterial({color, roughness, metalness, fog: false}); materials.push(value); return value;
|
|
18
|
+
};
|
|
19
|
+
const ivory = material('#e8e1d4'), paper = material('#faf8ee'), spine = material('#9b9f89'), metal = material('#b8aa8d', .38, .65);
|
|
20
|
+
const box = (w: number, h: number, d: number, x: number, y: number, z: number, mat: THREE.Material) => {
|
|
21
|
+
const mesh = new THREE.Mesh(geometry, mat); mesh.scale.set(w, h, d); mesh.position.set(x, y, z); folio.add(mesh); return mesh;
|
|
22
|
+
};
|
|
23
|
+
box(3.95,5.55,.16,0,0,-.14,ivory);
|
|
24
|
+
for(let i=0;i<7;i++) box(3.72,5.25,.025,.035+i*.006,-.015+i*.008,-.045+i*.032,paper);
|
|
25
|
+
box(.22,5.52,.39,-1.84,0,.01,spine);
|
|
26
|
+
box(3.86,5.45,.06,.055,.035,.22,ivory);
|
|
27
|
+
box(.07,5.40,.022,-1.48,.035,.257,metal);
|
|
28
|
+
for(let i=0;i<3;i++) box(.20,.44,.065,2.01,1.65-i*.65,.11,i===0?spine:ivory);
|
|
29
|
+
box(1.00,.21,.12,.02,2.67,.30,metal);
|
|
30
|
+
box(.70,.055,.16,.02,2.80,.27,metal);
|
|
31
|
+
const canvas=document.createElement('canvas'); canvas.width=896; canvas.height=1296;
|
|
32
|
+
const context=canvas.getContext('2d')!;
|
|
33
|
+
const texture=new THREE.CanvasTexture(canvas);texture.colorSpace=THREE.SRGBColorSpace;
|
|
34
|
+
const ink=printMaterial(texture);materials.push(ink);
|
|
35
|
+
const cover=new THREE.Mesh(face,ink);cover.layers.set(ARCHIVE_LABEL_LAYER);cover.position.set(.11,.015,.259);folio.add(cover);
|
|
36
|
+
const materialState=materials.map(material=>({material,opacity:material.opacity,transparent:material.transparent,depthWrite:material.depthWrite}));
|
|
37
|
+
let signature='', enterStartedAt=0, returnStartedAt:number|undefined, closingFrom=1, appliedOpacity=-1, reduced=false;
|
|
38
|
+
const ease=(value:number)=>value*value*(3-2*value);
|
|
39
|
+
const enterProgress=(now=readingFrameTime())=>reduced?1:THREE.MathUtils.clamp((now-enterStartedAt)/READING_ENTER_MS,0,1);
|
|
40
|
+
const progress=(now=readingFrameTime())=>returnStartedAt===undefined?0:reduced?1:THREE.MathUtils.clamp((now-returnStartedAt)/READING_RETURN_MS,0,1);
|
|
41
|
+
const opacity=(now=readingFrameTime())=>returnStartedAt===undefined?ease(enterProgress(now)):closingFrom*(1-ease(progress(now)));
|
|
42
|
+
function applyOpacity(value:number){
|
|
43
|
+
if(value===appliedOpacity)return;appliedOpacity=value;
|
|
44
|
+
for(const state of materialState){
|
|
45
|
+
const {material}=state, fading=value<1;
|
|
46
|
+
material.opacity=state.opacity*value;material.depthWrite=fading?false:state.depthWrite;
|
|
47
|
+
const transparent=fading||state.transparent;
|
|
48
|
+
if(material.transparent!==transparent){material.transparent=transparent;material.needsUpdate=true;}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const boardReveal=()=>group.visible?1-opacity():1;
|
|
52
|
+
const local=new THREE.Vector3();
|
|
53
|
+
function screenBounds(camera?:THREE.PerspectiveCamera){
|
|
54
|
+
if(!camera)return undefined;
|
|
55
|
+
cover.updateWorldMatrix(true,false);
|
|
56
|
+
const corners=[[-1.725,-2.49],[1.725,-2.49],[1.725,2.49],[-1.725,2.49]].map(([x,y])=>new THREE.Vector3(x,y,0).applyMatrix4(cover.matrixWorld).project(camera));
|
|
57
|
+
return [Math.min(...corners.map(p=>p.x)),Math.min(...corners.map(p=>p.y)),Math.max(...corners.map(p=>p.x)),Math.max(...corners.map(p=>p.y))];
|
|
58
|
+
}
|
|
59
|
+
return {group,
|
|
60
|
+
set(value:ReadingObject|null,skipMotion=false){
|
|
61
|
+
reduced=skipMotion;
|
|
62
|
+
if(!value){group.visible=false;returnStartedAt=undefined;applyOpacity(1);return;}
|
|
63
|
+
if(!group.visible||value.enterStartedAt!==undefined&&value.enterStartedAt!==enterStartedAt){
|
|
64
|
+
enterStartedAt=value.enterStartedAt??readingFrameTime();returnStartedAt=undefined;
|
|
65
|
+
}
|
|
66
|
+
if(value.returnStartedAt!==undefined&&value.returnStartedAt!==returnStartedAt)closingFrom=opacity(value.returnStartedAt);
|
|
67
|
+
returnStartedAt=value.returnStartedAt;group.visible=true;applyOpacity(opacity());
|
|
68
|
+
const next=JSON.stringify([value.title,value.code,value.kind]);if(next===signature)return;signature=next;
|
|
69
|
+
context.fillStyle='#e8e1d4';context.fillRect(0,0,896,1296);
|
|
70
|
+
context.fillStyle='#343a30';context.font='600 52px MiSans, sans-serif';context.fillText('RHINE LAB',56,112);
|
|
71
|
+
context.fillStyle='#7f826e';context.font='20px MiSans, sans-serif';context.fillText('RESEARCH / INFORMATION ANALYSIS',59,154);
|
|
72
|
+
context.strokeStyle='#b2ad99';context.lineWidth=2;context.beginPath();context.moveTo(58,207);context.lineTo(826,207);context.stroke();
|
|
73
|
+
context.font='22px MiSans, sans-serif';context.fillText(value.kind==='report'?'INVESTIGATION REPORT':'EVIDENCE / RESEARCH NOTE',58,295);
|
|
74
|
+
context.fillStyle='#343a30';context.font='500 44px MiSans, sans-serif';
|
|
75
|
+
let line='',row=0;for(const char of value.title){if(context.measureText(line+char).width>732){context.fillText(line,58,386+row*70);line='';row++;if(row===4)break;}line+=char;}
|
|
76
|
+
if(row<4)context.fillText(line,58,386+row*70);
|
|
77
|
+
context.fillStyle='#8a8e79';context.fillRect(58,840,62,5);context.font='27px MiSans, sans-serif';context.fillText(value.kind==='report'?'调查报告':'线索记录',58,900);
|
|
78
|
+
context.font='18px MiSans, sans-serif';context.fillText('TERRARCHIVE / RESEARCH DIVISION',58,942);
|
|
79
|
+
context.strokeStyle='#b2ad99';context.beginPath();context.moveTo(58,1110);context.lineTo(826,1110);context.stroke();
|
|
80
|
+
context.fillStyle='#4f5943';context.font='32px MiSans, sans-serif';context.fillText(value.code,58,1180);
|
|
81
|
+
context.font='16px MiSans, sans-serif';context.fillText('ARCHIVE COPY',665,1180);texture.needsUpdate=true;
|
|
82
|
+
},
|
|
83
|
+
update(camera:THREE.PerspectiveCamera,width:number,height:number,skipMotion=false){
|
|
84
|
+
if(!group.visible)return;
|
|
85
|
+
reduced=skipMotion;applyOpacity(opacity());
|
|
86
|
+
const distance=14,span=2*Math.tan(THREE.MathUtils.degToRad(camera.fov/2))*distance,wide=span*camera.aspect;
|
|
87
|
+
const portrait=width/height<.82;
|
|
88
|
+
const scale=portrait?Math.min(span*.25/5.55,wide*.5/4.2):Math.min(span*.61/5.55,wide*.37/4.2);
|
|
89
|
+
local.set(portrait?0:-wide*.245,portrait?span*.245:-span*.03,-distance).applyMatrix4(camera.matrixWorld);
|
|
90
|
+
group.position.copy(local);group.quaternion.copy(camera.quaternion);group.scale.setScalar(scale);
|
|
91
|
+
},
|
|
92
|
+
boardReveal(skipMotion=false){reduced=skipMotion;return boardReveal();},
|
|
93
|
+
stats(camera?:THREE.PerspectiveCamera){return {screenBounds:screenBounds(camera),visible:group.visible,kind:'research-folio',meshes:folio.children.length,enterProgress:enterProgress(),returnProgress:progress(),opacity:group.visible?appliedOpacity:0,boardReveal:boardReveal(),scale:group.scale.x,position:group.position.toArray(),rotation:folio.rotation.toArray(),materialsRestored:materialState.every(s=>s.material.transparent===s.transparent&&s.material.depthWrite===s.depthWrite&&s.material.opacity===s.opacity)};},
|
|
94
|
+
dispose(){group.removeFromParent();geometry.dispose();face.dispose();texture.dispose();materials.forEach(m=>m.dispose());}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RhineLocation } from './types';
|
|
2
|
+
|
|
3
|
+
export type ReadingOriginKind = 'search' | 'rack' | 'report' | 'basket' | 'log' | 'investigation' | 'archive' | 'desk' | 'board';
|
|
4
|
+
export interface ReadingOrigin {
|
|
5
|
+
kind: ReadingOriginKind;
|
|
6
|
+
location: RhineLocation;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
sourceId: string;
|
|
9
|
+
focus: HTMLElement | null;
|
|
10
|
+
scroll: { element: HTMLElement; top: number; left: number }[];
|
|
11
|
+
arrayExpanded: boolean;
|
|
12
|
+
}
|
|
13
|
+
export const readingOriginLabel = (kind: ReadingOriginKind, boardKind?: 'report' | 'clue' | 'inbox') =>
|
|
14
|
+
kind === 'investigation' ? boardKind === 'inbox' ? '返回证据盒' : boardKind === 'clue' ? '返回线索' : '返回报告'
|
|
15
|
+
: ({ search: '返回搜索结果', rack: '返回档案目录', report: '返回报告', basket: '返回摘录', log: '返回调查记录', archive: '返回检索阵列', desk: '返回档案架', board: '返回调查板' } as const)[kind];
|
|
16
|
+
|
|
17
|
+
export function captureReadingOrigin(root: HTMLElement, value: Omit<ReadingOrigin, 'focus' | 'scroll'>): ReadingOrigin {
|
|
18
|
+
return { ...value, focus: document.activeElement instanceof HTMLElement ? document.activeElement : null,
|
|
19
|
+
scroll: [...root.querySelectorAll<HTMLElement>('*')].filter(element => element.scrollTop || element.scrollLeft)
|
|
20
|
+
.map(element => ({ element, top: element.scrollTop, left: element.scrollLeft })) };
|
|
21
|
+
}
|
|
22
|
+
export function restoreReadingOrigin(root: HTMLElement, origin: ReadingOrigin) {
|
|
23
|
+
const target = origin.focus?.isConnected && origin.focus.getClientRects().length && !origin.focus.closest('[hidden],[inert]')
|
|
24
|
+
? origin.focus : [...root.querySelectorAll<HTMLElement>('[data-source-id]')].find(element =>
|
|
25
|
+
element.dataset.sourceId === origin.sourceId && element.getClientRects().length && !element.closest('[hidden],[inert]'));
|
|
26
|
+
target?.focus({ preventScroll: true });
|
|
27
|
+
for (const { element, top, left } of origin.scroll) if (element.isConnected) { element.scrollTop = top; element.scrollLeft = left; }
|
|
28
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* A physical document at left, a quiet transparent reading surface at right. */
|
|
2
|
+
.rhine-workbench{--reading-ink:#30362b;--reading-muted:#7d806e;--reading-rule:#aca78f88}
|
|
3
|
+
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer){position:absolute;inset:0;z-index:45;width:auto;max-width:none;padding:0;border:0;box-shadow:none;backdrop-filter:none;overflow:hidden;color:var(--reading-ink);background:linear-gradient(90deg,transparent 38%,#ebe8e04d 50%,#ebe8e0e0 72%,#ebe8e0eb 100%)}
|
|
4
|
+
.rhine-workbench .rhine-investigation-report>header,.rhine-workbench .rhine-investigation-drawer-top{position:absolute;left:56%;right:5%;top:22%;height:36px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:0;border:0;font-size:11px;letter-spacing:.06em;color:var(--reading-muted)}
|
|
5
|
+
.rhine-workbench .rhine-investigation-report>header{flex-wrap:wrap;height:auto;min-height:36px}
|
|
6
|
+
.rhine-workbench .rhine-investigation-report>header select{max-width:42%;min-width:0;letter-spacing:0}
|
|
7
|
+
.rhine-workbench .rhine-investigation-report>header button,.rhine-workbench .rhine-investigation-drawer-top button{margin-left:auto;white-space:nowrap;color:var(--reading-ink);font-size:12px;min-height:32px}
|
|
8
|
+
.rhine-workbench .rhine-investigation-report-scroll,.rhine-workbench .rhine-investigation-drawer-body{position:absolute;left:56%;right:5%;top:29%;bottom:12%;overflow:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#9a9b8170 transparent;padding:0 12px 20px 0;outline-offset:-2px}
|
|
9
|
+
.rhine-workbench .rhine-investigation-report-intro>h2,.rhine-workbench .rhine-investigation-drawer-body>h2{font-size:30px;line-height:1.5;font-weight:550;margin:0 0 15px;text-wrap:pretty}
|
|
10
|
+
.rhine-workbench .rhine-investigation-report-notice{font-size:11px;line-height:1.8;color:var(--reading-muted);margin:0 0 24px;padding:0 0 20px;border-bottom:1px solid var(--reading-rule)}
|
|
11
|
+
.rhine-workbench .rhine-investigation-report .rhine-report-body{max-width:none;width:auto;height:auto;overflow:visible;padding:0;font-size:17px;line-height:1.95}
|
|
12
|
+
.rhine-workbench .rhine-investigation-clue-text{font-size:17px;line-height:1.95;white-space:normal}
|
|
13
|
+
.rhine-workbench .rhine-investigation-drawer-body>h3{font-size:12px;letter-spacing:.08em;border-top:1px solid var(--reading-rule);padding-top:18px;margin-top:30px}
|
|
14
|
+
.rhine-workbench .rhine-investigation-report .rhine-report-body>:not(.rhine-report-table){max-width:none}
|
|
15
|
+
.rhine-workbench .rhine-investigation-provenance{padding-bottom:20px;border-bottom:1px solid var(--reading-rule);color:var(--reading-muted)}
|
|
16
|
+
.rhine-workbench .rhine-report.modal-backdrop{background:transparent;backdrop-filter:none}
|
|
17
|
+
.rhine-workbench .rhine-report-modal{position:absolute;left:56%;right:5%;top:22%;bottom:12%;width:auto;height:auto;max-height:none;padding:0;background:linear-gradient(90deg,#ebe8e0b3,#ebe8e0eb);box-shadow:none;border:0}
|
|
18
|
+
.rhine-workbench .rhine-report-layout{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;gap:16px;padding-top:16px}
|
|
19
|
+
.rhine-workbench .rhine-report-outline{max-height:68px;overflow:auto;border:0}
|
|
20
|
+
.rhine-workbench .rhine-report-toc{display:flex;gap:16px;flex-wrap:wrap}
|
|
21
|
+
.rhine-workbench .rhine-report-references{max-height:100px;overflow:auto;border-top:1px solid var(--reading-rule);padding-top:8px}
|
|
22
|
+
.rhine-workbench .rhine-report-modal .rhine-report-body{max-width:none;font-size:17px}
|
|
23
|
+
.rhine-workbench .rhine-markdown{white-space:normal;min-width:0;overflow-wrap:anywhere}
|
|
24
|
+
.rhine-workbench .rhine-line-text.rhine-markdown{font-size:var(--reader-font);line-height:1.85}
|
|
25
|
+
.rhine-workbench .rhine-line-text.rhine-markdown>:last-child{margin-bottom:0}
|
|
26
|
+
.rhine-workbench .rhine-line-text.rhine-markdown :is(h1,h2,h3,h4,h5,h6){font-size:1.12em;margin-top:.7em}
|
|
27
|
+
.rhine-workbench .rhine-line-text.rhine-markdown :is(ul,ol){margin-top:0}
|
|
28
|
+
.rhine-workbench .rhine-line-number{width:38px;font-variant-numeric:tabular-nums;line-height:1.8;padding-top:4px}
|
|
29
|
+
.rhine-workbench .rhine-reader-body{scrollbar-color:#9a9b8170 transparent}
|
|
30
|
+
.rhine-workbench .rhine-reader-body::-webkit-scrollbar,.rhine-workbench .rhine-investigation-report-scroll::-webkit-scrollbar{width:4px;height:4px}
|
|
31
|
+
.rhine-workbench .rhine-reader-body::-webkit-scrollbar-thumb,.rhine-workbench .rhine-investigation-report-scroll::-webkit-scrollbar-thumb{background:#9a9b8170}
|
|
32
|
+
.rhine-workbench .rhine-reader-body::-webkit-scrollbar-button,.rhine-workbench .rhine-investigation-report-scroll::-webkit-scrollbar-button{display:none}
|
|
33
|
+
.rhine-workbench .rhine-speaker+.rhine-markdown{display:inline}
|
|
34
|
+
.rhine-workbench .rhine-markdown .rhine-report-table{max-width:100%;overflow:auto;scrollbar-width:thin}
|
|
35
|
+
.rhine-workbench .has-reading-object .rhine-zone-nav{pointer-events:none}
|
|
36
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-investigation-report>header,.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-investigation-drawer-top{top:17%;left:52%;right:4%;font-size:9px}
|
|
37
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-investigation-report-scroll,.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-investigation-drawer-body{left:52%;right:4%;top:27%;bottom:9%}
|
|
38
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-investigation-report-intro>h2,.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) .rhine-investigation-drawer-body>h2{font-size:23px}
|
|
39
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) :is(.rhine-investigation-clue-text,.rhine-investigation-report .rhine-report-body){font-size:15px}
|
|
40
|
+
.rhine-workbench [data-layout=portrait] :is(.rhine-investigation-report,.rhine-investigation-drawer){background:linear-gradient(180deg,transparent 20%,#ebe8e0e8 42%,#ebe8e0f0)}
|
|
41
|
+
.rhine-workbench [data-layout=portrait] .rhine-investigation-report>header,.rhine-workbench [data-layout=portrait] .rhine-investigation-drawer-top{left:6%;right:6%;top:40%}
|
|
42
|
+
.rhine-workbench [data-layout=portrait] :is(.rhine-investigation-report-scroll,.rhine-investigation-drawer-body){left:6%;right:6%;top:48%;bottom:6%}
|
|
43
|
+
.rhine-workbench [data-layout=portrait] .rhine-report-modal{left:6%;right:6%;top:40%;bottom:6%;width:auto;height:auto;padding:0}
|
|
44
|
+
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer)[hidden]{display:none}
|
|
45
|
+
/* The controller drives both surfaces; disable the older CSS slide-in. */
|
|
46
|
+
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer){animation:none}
|
|
47
|
+
|
|
48
|
+
/* Board controls stay in place behind the reading mode, then return unchanged. */
|
|
49
|
+
.rhine-workbench.has-reading-object :is(.rhine-investigation-hud,.rhine-board-toolbar,.rhine-board-chalk-hit,.rhine-evidence-panel,.rhine-zone-nav,.rhine-scene-edges,.rhine-board-tool-status){visibility:hidden;pointer-events:none}
|
|
50
|
+
.rhine-workbench .rhine-investigation-report .rhine-report-body{box-sizing:border-box;margin:0;justify-self:stretch}
|
|
51
|
+
.rhine-workbench .rhine-investigation-report .rhine-report-body>:not(.rhine-report-table){margin-left:0;margin-right:0;max-width:none}
|
|
52
|
+
|
|
53
|
+
/* Keep the fading layer as the pointer shield until the fade completes. */
|
|
54
|
+
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer)[data-transition=closing]{animation:none;pointer-events:auto}
|
|
55
|
+
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer)[data-transition=closing]>*{pointer-events:none}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
|
|
3
|
+
// Reports are model text, never executable HTML. Keep rendering local, including
|
|
4
|
+
// images: a streamed answer must not trigger arbitrary background requests.
|
|
5
|
+
const markdown = new MarkdownIt({ html: false, linkify: false, typographer: false });
|
|
6
|
+
markdown.renderer.rules.image = (tokens, index) => `<span class="rhine-report-image-caption">${markdown.utils.escapeHtml(tokens[index].content || '图片')}</span>`;
|
|
7
|
+
|
|
8
|
+
export interface ReportHeading { id: string; label: string; level: number }
|
|
9
|
+
|
|
10
|
+
export function renderReportMarkdown(target: HTMLElement, text: string,
|
|
11
|
+
measure: <T>(kind: string, work: () => T) => T = (_kind, work) => work()): ReportHeading[] {
|
|
12
|
+
const { fragment, headings } = measure('report-markdown', () => {
|
|
13
|
+
const template = document.createElement('template');
|
|
14
|
+
template.innerHTML = markdown.render(text);
|
|
15
|
+
const fragment = template.content;
|
|
16
|
+
const headings = [...fragment.querySelectorAll<HTMLElement>('h1,h2,h3,h4,h5,h6')].map((heading, index) => {
|
|
17
|
+
heading.id = `rhine-report-section-${index + 1}`;
|
|
18
|
+
heading.tabIndex = -1;
|
|
19
|
+
return { id: heading.id, label: heading.textContent || `章节 ${index + 1}`, level: Number(heading.tagName.slice(1)) };
|
|
20
|
+
});
|
|
21
|
+
for (const link of fragment.querySelectorAll<HTMLAnchorElement>('a')) {
|
|
22
|
+
const href = link.getAttribute('href') || '';
|
|
23
|
+
if (href.startsWith('#')) continue;
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(href);
|
|
26
|
+
if (!['https:', 'http:', 'mailto:'].includes(url.protocol)) { link.removeAttribute('href'); continue; }
|
|
27
|
+
link.target = '_blank'; link.rel = 'noopener noreferrer';
|
|
28
|
+
} catch { link.removeAttribute('href'); }
|
|
29
|
+
}
|
|
30
|
+
for (const table of fragment.querySelectorAll('table')) {
|
|
31
|
+
const wrapper = document.createElement('div');
|
|
32
|
+
wrapper.className = 'rhine-report-table';
|
|
33
|
+
wrapper.tabIndex = 0;
|
|
34
|
+
wrapper.setAttribute('role', 'region');
|
|
35
|
+
wrapper.setAttribute('aria-label', '报告表格,可横向滚动');
|
|
36
|
+
table.replaceWith(wrapper); wrapper.append(table);
|
|
37
|
+
}
|
|
38
|
+
return { fragment, headings };
|
|
39
|
+
});
|
|
40
|
+
measure('report-body-patch', () => {
|
|
41
|
+
// Leave completed paragraphs in place while the final paragraph streams.
|
|
42
|
+
// This preserves selection, focus and the reader's position in earlier text.
|
|
43
|
+
const next = [...fragment.childNodes];
|
|
44
|
+
next.forEach((node, index) => {
|
|
45
|
+
const previous = target.childNodes[index];
|
|
46
|
+
if (!previous) target.append(node);
|
|
47
|
+
else if (!previous.isEqualNode(node)) target.replaceChild(node, previous);
|
|
48
|
+
});
|
|
49
|
+
while (target.childNodes.length > next.length) target.lastChild!.remove();
|
|
50
|
+
});
|
|
51
|
+
return headings;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SourceMarkdownLine { line_number: number; text: string; speaker_raw?: string }
|
|
55
|
+
|
|
56
|
+
/** Block rendering retains source ranges, including lists/tables spanning returned lines. */
|
|
57
|
+
export function renderSourceMarkdown(lines: readonly SourceMarkdownLine[]): HTMLElement[] {
|
|
58
|
+
const rows:HTMLElement[]=[];
|
|
59
|
+
const add=(start:number,end:number,text:string,speaker?:string)=>{
|
|
60
|
+
const row=document.createElement('div');row.className='rhine-reader-line';
|
|
61
|
+
row.dataset.line=String(start);row.dataset.lineEnd=String(end);row.id=`rhine-line-${start}`;
|
|
62
|
+
const number=document.createElement('span');number.className='rhine-line-number';
|
|
63
|
+
number.textContent=start===end?String(start).padStart(3,'0'):`${start}–${end}`;
|
|
64
|
+
number.title=start===end?`原文第 ${start} 行`:`原文第 ${start}–${end} 行`;
|
|
65
|
+
const body=document.createElement('div');body.className='rhine-line-text rhine-markdown';
|
|
66
|
+
// Local lines may carry both speaker_raw and the same serialized prefix.
|
|
67
|
+
// Strip only an exact leading identity followed by a colon; speech stays intact.
|
|
68
|
+
const identity=speaker?.trim();
|
|
69
|
+
if(identity){const escaped=identity.replace(/[.*+?^${}()|[\]\\]/gu,'\\$&');text=text.replace(new RegExp(`^\\s*${escaped}\\s*[::]\\s*`,'u'),'');}
|
|
70
|
+
renderReportMarkdown(body,text);
|
|
71
|
+
// Speaker identities remain literal, even if they contain Markdown punctuation.
|
|
72
|
+
if(speaker){const label=document.createElement('span');label.className='rhine-speaker';label.textContent=`${speaker} `;body.prepend(label);}
|
|
73
|
+
row.append(number,body);rows.push(row);
|
|
74
|
+
};
|
|
75
|
+
let offset=0;
|
|
76
|
+
while(offset<lines.length){
|
|
77
|
+
if(lines[offset].speaker_raw){const line=lines[offset++];add(line.line_number,line.line_number,line.text,line.speaker_raw);continue;}
|
|
78
|
+
let end=offset+1;while(end<lines.length&&!lines[end].speaker_raw&&lines[end].line_number===lines[end-1].line_number+1)end++;
|
|
79
|
+
const chunk=lines.slice(offset,end), virtual:string[]=[],owners:number[]=[];
|
|
80
|
+
for(let i=0;i<chunk.length;i++)for(const part of chunk[i].text.split(/\r?\n/)){virtual.push(part);owners.push(i);}
|
|
81
|
+
const tokens=markdown.parse(virtual.join('\n'),{});
|
|
82
|
+
for(let i=0;i<tokens.length;){
|
|
83
|
+
const token=tokens[i];let j=i+1;
|
|
84
|
+
if(token.nesting===1){let nesting=1;while(j<tokens.length&&nesting){nesting+=tokens[j].nesting;j++;}}
|
|
85
|
+
if(token.map){
|
|
86
|
+
const a=token.map[0];let b=token.map[1];
|
|
87
|
+
while(b>a+1&&!virtual[b-1].trim())b--;
|
|
88
|
+
const first=owners[a],last=owners[Math.max(a,b-1)];
|
|
89
|
+
if(token.type==='paragraph_open'&&first!==last){
|
|
90
|
+
for(let n=first;n<=last;n++)add(chunk[n].line_number,chunk[n].line_number,chunk[n].text);
|
|
91
|
+
}else add(chunk[first].line_number,chunk[last].line_number,virtual.slice(a,b).join('\n'));
|
|
92
|
+
}
|
|
93
|
+
i=j;
|
|
94
|
+
}
|
|
95
|
+
offset=end;
|
|
96
|
+
}
|
|
97
|
+
return rows;
|
|
98
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { RhineLocation } from './types';
|
|
2
|
+
|
|
3
|
+
const STOPS: { id: RhineLocation; label: string }[] = [
|
|
4
|
+
{ id: 'archive', label: '检索阵列' }, { id: 'board', label: '证据板' }, { id: 'desk', label: '档案收录架' },
|
|
5
|
+
];
|
|
6
|
+
type Options = {
|
|
7
|
+
location: () => RhineLocation;
|
|
8
|
+
available: () => boolean;
|
|
9
|
+
navigate: (location: RhineLocation) => void;
|
|
10
|
+
returnFocus: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Screen-edge buttons share the scene's existing navigation and never consume canvas drags. */
|
|
14
|
+
export function mountSceneEdgeNavigation(host: HTMLElement, options: Options) {
|
|
15
|
+
const events = new AbortController(), signal = events.signal;
|
|
16
|
+
const nav = document.createElement('nav');
|
|
17
|
+
nav.className = 'rhine-scene-edges'; nav.setAttribute('aria-label', '相邻场景'); nav.hidden = true;
|
|
18
|
+
let disposed = false;
|
|
19
|
+
let press: { button: HTMLButtonElement; pointer: number; origin: RhineLocation; target: RhineLocation;
|
|
20
|
+
x: number; y: number; moved: boolean; released: boolean } | null = null;
|
|
21
|
+
const reset = () => { press = null; };
|
|
22
|
+
const controls = ([-1, 1] as const).map(direction => {
|
|
23
|
+
const button = document.createElement('button'); button.type = 'button';
|
|
24
|
+
button.className = `rhine-scene-edge is-${direction < 0 ? 'left' : 'right'}`;
|
|
25
|
+
button.dataset.direction = direction < 0 ? 'left' : 'right';
|
|
26
|
+
button.innerHTML = `<span class="rhine-scene-edge-marker" aria-hidden="true"></span>
|
|
27
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="${direction < 0 ? 'M15 5l-7 7 7 7' : 'M9 5l7 7-7 7'}"/></svg>
|
|
28
|
+
<span class="rhine-scene-edge-label" aria-hidden="true"></span>`;
|
|
29
|
+
button.addEventListener('pointerdown', event => {
|
|
30
|
+
if (!event.isPrimary || event.button !== 0 || !options.available()) return;
|
|
31
|
+
const target = destination(direction); if (!target) return;
|
|
32
|
+
press = { button, pointer: event.pointerId, origin: options.location(), target: target.id,
|
|
33
|
+
x: event.clientX, y: event.clientY, moved: false, released: false };
|
|
34
|
+
}, { signal });
|
|
35
|
+
button.addEventListener('click', event => {
|
|
36
|
+
const gesture = press; reset();
|
|
37
|
+
if (disposed || !options.available() || event.detail > 1) return;
|
|
38
|
+
const target = destination(direction); if (!target) return;
|
|
39
|
+
// Keyboard and assistive activation have detail 0. Pointer clicks must
|
|
40
|
+
// begin here and stay a click, even if dragged out and back into the strip.
|
|
41
|
+
if (event.detail !== 0 && (!gesture || gesture.button !== button || !gesture.released || gesture.moved
|
|
42
|
+
|| gesture.origin !== options.location() || gesture.target !== target.id)) return;
|
|
43
|
+
const focused = document.activeElement === button;
|
|
44
|
+
options.navigate(target.id); sync();
|
|
45
|
+
if (focused && button.hidden) options.returnFocus();
|
|
46
|
+
}, { signal });
|
|
47
|
+
button.addEventListener('keydown', event => {
|
|
48
|
+
if (event.repeat && (event.key === 'Enter' || event.key === ' ')) event.preventDefault();
|
|
49
|
+
}, { signal });
|
|
50
|
+
nav.append(button);
|
|
51
|
+
return { button, direction };
|
|
52
|
+
});
|
|
53
|
+
host.append(nav);
|
|
54
|
+
function destination(direction: number) { return STOPS[STOPS.findIndex(stop => stop.id === options.location()) + direction]; }
|
|
55
|
+
function sync() {
|
|
56
|
+
if (disposed) return;
|
|
57
|
+
nav.hidden = !options.available();
|
|
58
|
+
if (nav.hidden) reset();
|
|
59
|
+
for (const { button, direction } of controls) {
|
|
60
|
+
const target = destination(direction); button.hidden = !target;
|
|
61
|
+
if (!target) { delete button.dataset.target; continue; }
|
|
62
|
+
button.dataset.target = target.id;
|
|
63
|
+
button.setAttribute('aria-label', `前往${target.label}`);
|
|
64
|
+
button.querySelector('.rhine-scene-edge-label')!.textContent = target.label;
|
|
65
|
+
button.querySelector('.rhine-scene-edge-marker')!.textContent = String(STOPS.indexOf(target) + 1).padStart(2, '0');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Observe the full gesture without capturing it away from native buttons or
|
|
69
|
+
// the canvas. A canceled pointer can never arm a later synthetic mouse click.
|
|
70
|
+
window.addEventListener('pointerdown', reset, { capture: true, signal });
|
|
71
|
+
window.addEventListener('pointermove', event => {
|
|
72
|
+
if (press?.pointer === event.pointerId && Math.hypot(event.clientX - press.x, event.clientY - press.y) >= 6) press.moved = true;
|
|
73
|
+
}, { capture: true, signal });
|
|
74
|
+
window.addEventListener('pointerup', event => {
|
|
75
|
+
if (press?.pointer !== event.pointerId) return;
|
|
76
|
+
press.released = true;
|
|
77
|
+
const bounds = press.button.getBoundingClientRect();
|
|
78
|
+
if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) press.moved = true;
|
|
79
|
+
}, { capture: true, signal });
|
|
80
|
+
window.addEventListener('pointercancel', reset, { signal });
|
|
81
|
+
window.addEventListener('blur', reset, { signal });
|
|
82
|
+
sync();
|
|
83
|
+
return { sync, dispose() { disposed = true; reset(); events.abort(); nav.remove(); } };
|
|
84
|
+
}
|