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,470 @@
|
|
|
1
|
+
import { evidenceCardAppearance } from './evidence-card-style';
|
|
2
|
+
import type { EvidenceCard } from './evidence-board-model';
|
|
3
|
+
|
|
4
|
+
const WIDTH = 768;
|
|
5
|
+
// Match the original archive: ivory polymer, warm silver trim and champagne accents.
|
|
6
|
+
// Palette sources: original/scene.ts materials and original-ui.css inspection marks.
|
|
7
|
+
const INK = '#24221f';
|
|
8
|
+
const MUTED = '#726f67';
|
|
9
|
+
const RED = '#a23b30';
|
|
10
|
+
type Context = CanvasRenderingContext2D;
|
|
11
|
+
type ArtCard = EvidenceCard & { visual?: 'observatory' | 'schematic' | 'signal' };
|
|
12
|
+
|
|
13
|
+
function font(context: Context, size: number, weight = 400) {
|
|
14
|
+
context.font = `${weight} ${size}px MiSans, sans-serif`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function fitted(context: Context, value: string, width: number) {
|
|
18
|
+
if (context.measureText(value).width <= width) return value;
|
|
19
|
+
const characters = Array.from(value);
|
|
20
|
+
let low = 0, high = characters.length;
|
|
21
|
+
while (low < high) {
|
|
22
|
+
const middle = Math.ceil((low + high) / 2);
|
|
23
|
+
if (context.measureText(characters.slice(0, middle).join('') + '…').width <= width) low = middle;
|
|
24
|
+
else high = middle - 1;
|
|
25
|
+
}
|
|
26
|
+
return characters.slice(0, low).join('') + '…';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Preserve explicit line breaks and never paint beyond the available rows. */
|
|
30
|
+
function wrap(context: Context, value: string, width: number, maximum: number) {
|
|
31
|
+
if (maximum < 1) return [];
|
|
32
|
+
const characters = Array.from(value.replace(/\r/g, ''));
|
|
33
|
+
const result: string[] = [];
|
|
34
|
+
let line = '';
|
|
35
|
+
for (let index = 0; index < characters.length; index++) {
|
|
36
|
+
const character = characters[index];
|
|
37
|
+
if (character === '\n' || context.measureText(line + character).width > width) {
|
|
38
|
+
if (result.length === maximum - 1) {
|
|
39
|
+
result.push(fitted(context, line + characters.slice(index).join('').replace(/\n/g, ' '), width));
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
result.push(line);
|
|
43
|
+
line = character === '\n' ? '' : character;
|
|
44
|
+
} else line += character;
|
|
45
|
+
}
|
|
46
|
+
if (line) result.push(line);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function paragraph(context: Context, value: string, x: number, y: number,
|
|
51
|
+
width: number, size: number, leading: number, maximum: number, weight = 400) {
|
|
52
|
+
font(context, size, weight);
|
|
53
|
+
const rows = wrap(context, value, width, maximum);
|
|
54
|
+
rows.forEach((row, index) => context.fillText(row, x, y + index * leading));
|
|
55
|
+
return rows.length * leading;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function path(context: Context, points: number[][], color: string, width = 1.5, close = false) {
|
|
59
|
+
if (!points.length) return;
|
|
60
|
+
context.beginPath();
|
|
61
|
+
points.forEach(([x, y], index) => index ? context.lineTo(x, y) : context.moveTo(x, y));
|
|
62
|
+
if (close) context.closePath();
|
|
63
|
+
context.strokeStyle = color; context.lineWidth = width; context.stroke();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function circle(context: Context, x: number, y: number, radius: number, color: string, width = 1.5) {
|
|
67
|
+
context.beginPath(); context.arc(x, y, radius, 0, Math.PI * 2);
|
|
68
|
+
context.strokeStyle = color; context.lineWidth = width; context.stroke();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function randomFor(key: string) {
|
|
72
|
+
let seed = 2166136261;
|
|
73
|
+
for (let index = 0; index < key.length; index++) seed = Math.imul(seed ^ key.charCodeAt(index), 16777619);
|
|
74
|
+
return () => {
|
|
75
|
+
seed += 0x6d2b79f5;
|
|
76
|
+
let value = seed;
|
|
77
|
+
value = Math.imul(value ^ value >>> 15, value | 1);
|
|
78
|
+
value ^= value + Math.imul(value ^ value >>> 7, value | 61);
|
|
79
|
+
return ((value ^ value >>> 14) >>> 0) / 4294967296;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function paper(context: Context, height: number, color: string, key: string) {
|
|
84
|
+
context.fillStyle = color; context.fillRect(0, 0, WIDTH, height);
|
|
85
|
+
const random = randomFor(key);
|
|
86
|
+
context.fillStyle = 'rgba(98,89,80,.035)';
|
|
87
|
+
// Fixed grain is drawn before the ink; it never flickers when a card is edited.
|
|
88
|
+
for (let index = 0; index < 1050; index++) {
|
|
89
|
+
context.fillRect(random() * WIDTH, random() * height, 0.45 + random() * 1.2, 0.3 + random() * 0.8);
|
|
90
|
+
}
|
|
91
|
+
const edge = context.createLinearGradient(0, 0, WIDTH, 0);
|
|
92
|
+
edge.addColorStop(0, 'rgba(112,102,91,.055)'); edge.addColorStop(.016, 'rgba(112,102,91,0)');
|
|
93
|
+
edge.addColorStop(.986, 'rgba(112,102,91,0)'); edge.addColorStop(1, 'rgba(112,102,91,.045)');
|
|
94
|
+
context.fillStyle = edge; context.fillRect(0, 0, WIDTH, height);
|
|
95
|
+
context.strokeStyle = 'rgba(112,102,91,.1)'; context.lineWidth = 1;
|
|
96
|
+
for (let index = 0; index < 20; index++) {
|
|
97
|
+
const x = random() * WIDTH, y = index % 2 ? 1.5 : height - 1.5;
|
|
98
|
+
context.beginPath(); context.moveTo(x, y); context.lineTo(Math.min(WIDTH, x + 3 + random() * 13), y); context.stroke();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function sample(context: Context, card: ArtCard, x: number, y: number, align: CanvasTextAlign = 'left') {
|
|
103
|
+
if (!card.id.startsWith('preview:')) return;
|
|
104
|
+
context.save(); context.textAlign = align; font(context, 18, 600);
|
|
105
|
+
context.fillStyle = MUTED; context.fillText('LONE TRAIL / 示例', x, y); context.restore();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function imageSize(image: CanvasImageSource) {
|
|
109
|
+
const source = image as unknown as Record<string, unknown>;
|
|
110
|
+
const dimension = (...names: string[]) => {
|
|
111
|
+
for (const name of names) {
|
|
112
|
+
const value = source[name];
|
|
113
|
+
if (typeof value === 'number' && value > 0) return value;
|
|
114
|
+
if (value && typeof value === 'object' && 'baseVal' in value) {
|
|
115
|
+
const length = (value as { baseVal: { value: number } }).baseVal.value;
|
|
116
|
+
if (length > 0) return length;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
};
|
|
121
|
+
return { width: dimension('naturalWidth', 'videoWidth', 'displayWidth', 'width'),
|
|
122
|
+
height: dimension('naturalHeight', 'videoHeight', 'displayHeight', 'height') };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function photoCard(context: Context, card: ArtCard, height: number, image: CanvasImageSource) {
|
|
126
|
+
const dimensions = imageSize(image);
|
|
127
|
+
if (!dimensions.width || !dimensions.height) return false;
|
|
128
|
+
paper(context, height, '#fffdfa', card.id);
|
|
129
|
+
const margin = 27, imageWidth = WIDTH - margin * 2;
|
|
130
|
+
const imageHeight = Math.max(80, height - 144);
|
|
131
|
+
const scale = Math.max(imageWidth / dimensions.width, imageHeight / dimensions.height);
|
|
132
|
+
const cropWidth = imageWidth / scale, cropHeight = imageHeight / scale;
|
|
133
|
+
// Only supplied pixels are used. A missing photograph falls back to a text document.
|
|
134
|
+
context.drawImage(image, (dimensions.width - cropWidth) / 2, (dimensions.height - cropHeight) / 2,
|
|
135
|
+
cropWidth, cropHeight, margin, margin, imageWidth, imageHeight);
|
|
136
|
+
context.fillStyle = INK; font(context, 48, 600);
|
|
137
|
+
context.fillText(fitted(context, card.title || '未命名线索', WIDTH - 82), 40, height - 94);
|
|
138
|
+
context.fillStyle = MUTED; font(context, 22);
|
|
139
|
+
const caption = `主题意象 · ${card.sourceTitle || card.body.replace(/\s+/g, ' ').trim()}`;
|
|
140
|
+
context.fillText(fitted(context, caption, WIDTH - (card.id.startsWith('preview:') ? 285 : 84)), 42, height - 35);
|
|
141
|
+
sample(context, card, WIDTH - 37, height - 34, 'right');
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function diagram(context: Context, top: number, height: number, loneTrail: boolean) {
|
|
146
|
+
const scale = Math.min(680 / 680, height / 380);
|
|
147
|
+
if (scale <= 0) return;
|
|
148
|
+
context.save(); context.translate((WIDTH - 680 * scale) / 2, top + (height - 380 * scale) / 2);
|
|
149
|
+
context.scale(scale, scale);
|
|
150
|
+
const line = '#57524d', pale = '#aaa49a', faint = 'rgba(126,118,107,.13)';
|
|
151
|
+
for (let x = 20; x < 680; x += 30) path(context, [[x, 0], [x, 380]], faint, .8);
|
|
152
|
+
for (let y = 20; y < 380; y += 30) path(context, [[0, y], [680, y]], faint, .8);
|
|
153
|
+
path(context, [[4, 340], [659, 340], [651, 334]], pale);
|
|
154
|
+
path(context, [[42, 373], [42, 5], [36, 13]], pale);
|
|
155
|
+
context.fillStyle = MUTED; font(context, 18, 600);
|
|
156
|
+
context.fillText('X', 659, 347); context.fillText('Y', 17, 5);
|
|
157
|
+
for (let x = 90; x < 650; x += 60) path(context, [[x, 336], [x, 344]], pale);
|
|
158
|
+
// Circular test chamber, service rings and a cutaway hatch, all illustrative.
|
|
159
|
+
const cx = 252, cy = 190;
|
|
160
|
+
context.fillStyle = '#e2dad4'; context.beginPath(); context.arc(cx, cy, 102, 0, Math.PI * 2); context.fill();
|
|
161
|
+
circle(context, cx, cy, 102, line, 2.8); circle(context, cx, cy, 92, line, 1.8);
|
|
162
|
+
circle(context, cx, cy, 63, pale, 1.5); circle(context, cx, cy, 38, line, 2.2);
|
|
163
|
+
circle(context, cx, cy, 17, line, 1.6);
|
|
164
|
+
context.setLineDash([5, 6]); circle(context, cx, cy, 77, pale, 1.3); context.setLineDash([]);
|
|
165
|
+
for (let index = 0; index < 12; index++) {
|
|
166
|
+
const angle = index * Math.PI / 6;
|
|
167
|
+
const polar = (radius: number) => [cx + Math.cos(angle) * radius, cy + Math.sin(angle) * radius];
|
|
168
|
+
path(context, [polar(94), polar(101)], line, 2);
|
|
169
|
+
if (index % 3 === 0) path(context, [polar(40), polar(61)], line, 2);
|
|
170
|
+
}
|
|
171
|
+
path(context, [[cx - 114, cy], [cx + 114, cy]], pale, .9);
|
|
172
|
+
path(context, [[cx, cy - 113], [cx, cy + 113]], pale, .9);
|
|
173
|
+
// Rectangular service modules with double walls and internal cells.
|
|
174
|
+
for (const [x, y, w, h] of [[458, 55, 142, 91], [468, 245, 144, 74], [78, 108, 64, 126]]) {
|
|
175
|
+
context.fillStyle = '#f7f4ef'; context.fillRect(x, y, w, h);
|
|
176
|
+
context.strokeStyle = line; context.lineWidth = 2; context.strokeRect(x, y, w, h);
|
|
177
|
+
context.strokeStyle = pale; context.lineWidth = 1; context.strokeRect(x + 6, y + 6, w - 12, h - 12);
|
|
178
|
+
for (let offset = 22; offset < w - 12; offset += 22) path(context, [[x + offset, y + 12], [x + offset, y + h - 12]], pale);
|
|
179
|
+
}
|
|
180
|
+
path(context, [[342, 150], [402, 150], [402, 101], [458, 101]], line, 2.5);
|
|
181
|
+
path(context, [[347, 157], [410, 157], [410, 109], [458, 109]], pale, 1.4);
|
|
182
|
+
path(context, [[337, 248], [402, 248], [402, 281], [468, 281]], line, 2.5);
|
|
183
|
+
path(context, [[332, 257], [394, 257], [394, 289], [468, 289]], pale, 1.4);
|
|
184
|
+
path(context, [[142, 157], [159, 157]], line, 2); path(context, [[142, 220], [159, 220]], line, 2);
|
|
185
|
+
path(context, [[252, 87], [252, 34], [387, 34], [387, 71]], line, 1.6);
|
|
186
|
+
circle(context, 387, 82, 11, line, 1.8);
|
|
187
|
+
for (let index = 0; index < 6; index++) path(context, [[194 + index * 12, 265], [207 + index * 12, 279]], pale, 1);
|
|
188
|
+
// Dotted travel path and dimension brackets, not fictitious measured values.
|
|
189
|
+
context.setLineDash([7, 5]);
|
|
190
|
+
context.beginPath(); context.moveTo(100, 300); context.bezierCurveTo(140, 345, 380, 368, 548, 184);
|
|
191
|
+
context.strokeStyle = line; context.lineWidth = 1.8; context.stroke(); context.setLineDash([]);
|
|
192
|
+
path(context, [[538, 187], [548, 184], [546, 196]], line, 1.8);
|
|
193
|
+
path(context, [[154, 70], [154, 53], [354, 53], [354, 70]], pale);
|
|
194
|
+
path(context, [[151, 50], [158, 57]], pale); path(context, [[350, 50], [357, 57]], pale);
|
|
195
|
+
context.fillStyle = line; font(context, 22, 600);
|
|
196
|
+
context.fillText(loneTrail ? '万星园' : 'A', loneTrail ? 219 : 244, 156);
|
|
197
|
+
context.fillText(loneTrail ? '星荚' : 'B', loneTrail ? 503 : 514, 20);
|
|
198
|
+
context.fillText(loneTrail ? '能量井' : 'C', loneTrail ? 511 : 538, 326);
|
|
199
|
+
context.fillStyle = MUTED; font(context, 18); context.fillText(loneTrail ? 'RHINE / 计划关联' : 'PLAN / 01', 47, 356);
|
|
200
|
+
// A restrained pencil-red circle and one short handwritten-looking mark.
|
|
201
|
+
context.beginPath(); context.ellipse(527, 102, 87, 57, -.09, -.12, Math.PI * 2 + .12);
|
|
202
|
+
context.strokeStyle = RED; context.lineWidth = 3.3; context.stroke();
|
|
203
|
+
context.beginPath(); context.moveTo(575, 174); context.quadraticCurveTo(591, 180, 611, 174);
|
|
204
|
+
context.strokeStyle = RED; context.lineWidth = 2.5; context.stroke();
|
|
205
|
+
context.restore();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function schematicCard(context: Context, card: ArtCard, height: number) {
|
|
209
|
+
paper(context, height, '#eeeae3', card.id);
|
|
210
|
+
// Fold shadows belong to the paper and remain quiet behind the dark drafting ink.
|
|
211
|
+
for (const x of [WIDTH * .34, WIDTH * .68]) {
|
|
212
|
+
const crease = context.createLinearGradient(x - 8, 0, x + 9, 0);
|
|
213
|
+
crease.addColorStop(0, 'rgba(112,102,91,0)'); crease.addColorStop(.43, 'rgba(112,102,91,.055)');
|
|
214
|
+
crease.addColorStop(.52, 'rgba(255,253,250,.55)'); crease.addColorStop(1, 'rgba(255,253,250,0)');
|
|
215
|
+
context.fillStyle = crease; context.fillRect(x - 8, 0, 17, height);
|
|
216
|
+
}
|
|
217
|
+
path(context, [[0, height * .56], [WIDTH, height * .56]], 'rgba(126,118,107,.08)', 1);
|
|
218
|
+
context.fillStyle = MUTED; font(context, 22, 600); context.fillText('关系示意', 43, 20);
|
|
219
|
+
sample(context, card, WIDTH - 40, 21, 'right');
|
|
220
|
+
context.fillStyle = INK;
|
|
221
|
+
const titleHeight = paragraph(context, card.title || '未命名线索', 40, 58, WIDTH - 80, 48, 59, 2, 600);
|
|
222
|
+
const top = 58 + titleHeight + 18;
|
|
223
|
+
const hasBody = Boolean(card.body.trim());
|
|
224
|
+
diagram(context, top, Math.max(0, height - top - (hasBody ? 120 : 30)), Boolean(card.sourceTitle?.startsWith('《孤星》')));
|
|
225
|
+
if (hasBody) {
|
|
226
|
+
context.fillStyle = INK;
|
|
227
|
+
paragraph(context, card.body, 43, height - 100, WIDTH - 86, 32, 42, 2);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function signalCard(context: Context, card: ArtCard, height: number, index: number) {
|
|
232
|
+
paper(context, height, '#e2dad4', card.id);
|
|
233
|
+
context.fillStyle = '#726f67'; font(context, 22, 600);
|
|
234
|
+
context.fillText(`OBS. ${String(index + 1).padStart(2, '0')} / 示意`, 39, 22);
|
|
235
|
+
sample(context, card, WIDTH - 39, 22, 'right');
|
|
236
|
+
context.fillStyle = '#24221f'; font(context, 48, 600);
|
|
237
|
+
context.fillText(fitted(context, card.title || '未命名线索', WIDTH - 80), 39, 63);
|
|
238
|
+
const top = 128, bottom = Math.max(top + 42, height - 122), center = (top + bottom) / 2;
|
|
239
|
+
for (let x = 42; x <= WIDTH - 36; x += 28) path(context, [[x, top], [x, bottom]], 'rgba(100,93,85,.14)', .9);
|
|
240
|
+
for (let y = top; y <= bottom; y += 25) path(context, [[38, y], [WIDTH - 37, y]], 'rgba(100,93,85,.12)', .9);
|
|
241
|
+
path(context, [[38, center], [WIDTH - 38, center]], '#a2998c', 1.3);
|
|
242
|
+
const amplitude = (bottom - top) * .4;
|
|
243
|
+
const waveform = (offset: number, color: string, weight: number) => {
|
|
244
|
+
const points = [];
|
|
245
|
+
for (let step = 0; step <= 260; step++) {
|
|
246
|
+
const t = step / 260;
|
|
247
|
+
const envelope = .19 + .58 * Math.exp(-(((t - .39) * 8) ** 2)) + .36 * Math.exp(-(((t - .78) * 12) ** 2));
|
|
248
|
+
const value = (Math.sin(t * 88 + offset) + .28 * Math.sin(t * 193 + offset)) * envelope;
|
|
249
|
+
points.push([39 + t * (WIDTH - 78), center - value * amplitude]);
|
|
250
|
+
}
|
|
251
|
+
path(context, points, color, weight);
|
|
252
|
+
};
|
|
253
|
+
waveform(.6, 'rgba(128,114,97,.35)', 1.3); waveform(0, '#504b41', 3);
|
|
254
|
+
const marker = WIDTH * .39;
|
|
255
|
+
path(context, [[marker, top - 8], [marker, bottom + 5]], RED, 2);
|
|
256
|
+
context.fillStyle = RED; context.beginPath(); context.moveTo(marker - 6, top - 12);
|
|
257
|
+
context.lineTo(marker + 6, top - 12); context.lineTo(marker, top - 3); context.closePath(); context.fill();
|
|
258
|
+
context.fillStyle = '#24221f';
|
|
259
|
+
paragraph(context, card.body, 40, height - 100, WIDTH - 80, 32, 42, 2);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function sourceCard(context: Context, card: ArtCard, height: number, index: number) {
|
|
263
|
+
paper(context, height, '#fffdfa', card.id);
|
|
264
|
+
context.fillStyle = '#c4baae'; context.fillRect(0, 0, 65, height);
|
|
265
|
+
context.save(); context.translate(23, height - 33); context.rotate(-Math.PI / 2);
|
|
266
|
+
context.fillStyle = '#504b41'; font(context, 23, 600);
|
|
267
|
+
context.fillText(`SOURCE / ${String(index + 1).padStart(2, '0')}`, 0, 0); context.restore();
|
|
268
|
+
const left = 103, width = WIDTH - left - 38;
|
|
269
|
+
context.fillStyle = MUTED; font(context, 22, 600); context.fillText('ARCHIVE / EXTRACT', left, 31);
|
|
270
|
+
sample(context, card, WIDTH - 37, 31, 'right');
|
|
271
|
+
context.fillStyle = INK;
|
|
272
|
+
const titleHeight = paragraph(context, card.title || '未命名线索', left, 86, width, 48, 60,
|
|
273
|
+
height < 550 ? 2 : 3, 600);
|
|
274
|
+
const top = 86 + titleHeight + 30;
|
|
275
|
+
const leading = 44;
|
|
276
|
+
const bottom = height - (card.sourceTitle ? 95 : 44);
|
|
277
|
+
const rows = Math.max(0, Math.min(12, Math.floor((bottom - top) / leading)));
|
|
278
|
+
const gutter = 29, column = (width - gutter) / 2;
|
|
279
|
+
font(context, 34); context.fillStyle = INK;
|
|
280
|
+
const text = wrap(context, card.body, column, rows * 2);
|
|
281
|
+
let split = Math.min(rows, Math.ceil(text.length / 2));
|
|
282
|
+
// Keep short supplied paragraphs together where both columns can fit them.
|
|
283
|
+
const paragraphs = card.body.replace(/\r/g, '').split('\n');
|
|
284
|
+
if (paragraphs.length > 1 && paragraphs.every(value => value.trim())) {
|
|
285
|
+
let boundary = 0, distance = Infinity;
|
|
286
|
+
for (const value of paragraphs.slice(0, -1)) {
|
|
287
|
+
boundary += wrap(context, value, column, rows * 2).length;
|
|
288
|
+
if (boundary > 0 && boundary <= rows && text.length - boundary > 0 && text.length - boundary <= rows
|
|
289
|
+
&& Math.abs(text.length / 2 - boundary) < distance) {
|
|
290
|
+
split = boundary; distance = Math.abs(text.length / 2 - boundary);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
text.forEach((line, at) => {
|
|
295
|
+
const second = at >= split;
|
|
296
|
+
context.fillText(line, left + (second ? column + gutter : 0), top + (second ? at - split : at) * leading);
|
|
297
|
+
});
|
|
298
|
+
if (text.length > split && split > 0) path(context,
|
|
299
|
+
[[left + column + gutter / 2, top + 4], [left + column + gutter / 2, top + split * leading - 12]], '#c7beb6', 1);
|
|
300
|
+
if (card.sourceTitle) {
|
|
301
|
+
context.fillStyle = MUTED; font(context, 23, 600);
|
|
302
|
+
context.fillText(fitted(context, card.sourceTitle, width), left, height - 58);
|
|
303
|
+
}
|
|
304
|
+
// A small folded corner changes the document silhouette without ornamental facts.
|
|
305
|
+
context.fillStyle = '#e2dad4'; context.beginPath(); context.moveTo(WIDTH - 34, height);
|
|
306
|
+
context.lineTo(WIDTH - 34, height - 32); context.lineTo(WIDTH, height - 32); context.closePath(); context.fill();
|
|
307
|
+
path(context, [[WIDTH - 34, height], [WIDTH - 34, height - 32], [WIDTH, height - 32]], '#c4baae', 1);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function noteCard(context: Context, card: ArtCard, height: number, index: number) {
|
|
311
|
+
paper(context, height, '#f0e7df', card.id);
|
|
312
|
+
context.fillStyle = MUTED; font(context, 22, 600);
|
|
313
|
+
context.fillText(String(index + 1).padStart(2, '0'), 43, 24);
|
|
314
|
+
sample(context, card, WIDTH - 41, 24, 'right');
|
|
315
|
+
font(context, 48, 600);
|
|
316
|
+
const title = wrap(context, card.title || '未命名线索', WIDTH - 89, 2);
|
|
317
|
+
const top = 65;
|
|
318
|
+
const titleLeading = 58;
|
|
319
|
+
const highlightWidth = Math.min(380, context.measureText(title[0] || '').width + 8);
|
|
320
|
+
context.fillStyle = 'rgba(203,179,151,.3)'; context.fillRect(39, top + 34, highlightWidth, 14);
|
|
321
|
+
context.fillStyle = INK; title.forEach((line, at) => context.fillText(line, 43, top + at * titleLeading));
|
|
322
|
+
const bodyTop = top + title.length * titleLeading + 33;
|
|
323
|
+
context.fillStyle = INK;
|
|
324
|
+
paragraph(context, card.body, 44, bodyTop, WIDTH - 88, 34, 45,
|
|
325
|
+
Math.max(0, Math.min(8, Math.floor((height - 50 - bodyTop) / 45))));
|
|
326
|
+
context.beginPath(); context.moveTo(42, top + title.length * titleLeading + 4);
|
|
327
|
+
context.bezierCurveTo(105, top + title.length * titleLeading, 191, top + title.length * titleLeading + 8,
|
|
328
|
+
Math.min(230, 43 + highlightWidth), top + title.length * titleLeading + 2);
|
|
329
|
+
context.strokeStyle = RED; context.lineWidth = 2.4; context.stroke();
|
|
330
|
+
// A faint crease and a short rough edge are paper details, not another footer.
|
|
331
|
+
path(context, [[16, 0], [22, height]], 'rgba(112,102,91,.07)', 1);
|
|
332
|
+
path(context, [[WIDTH - 30, height - 5], [WIDTH - 23, height - 9], [WIDTH - 17, height - 5]], 'rgba(112,102,91,.17)', 1.3);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function questionCard(context: Context, card: ArtCard, height: number) {
|
|
336
|
+
paper(context, height, '#e4d5c1', card.id);
|
|
337
|
+
const glue = context.createLinearGradient(0, 0, 0, 39);
|
|
338
|
+
glue.addColorStop(0, 'rgba(166,125,72,.09)'); glue.addColorStop(1, 'rgba(166,125,72,0)');
|
|
339
|
+
context.fillStyle = glue; context.fillRect(0, 0, WIDTH, 39);
|
|
340
|
+
context.fillStyle = RED; font(context, 34, 600); context.fillText('?', WIDTH - 66, 21);
|
|
341
|
+
context.fillStyle = '#24221f';
|
|
342
|
+
const titleRows = height < 400 ? 1 : height < 560 ? 2 : 3;
|
|
343
|
+
const titleHeight = paragraph(context, card.title || '未命名线索', 45, 66, WIDTH - 103, 48, 60, titleRows, 600);
|
|
344
|
+
const bodyTop = 66 + titleHeight + 26;
|
|
345
|
+
paragraph(context, card.body, 47, bodyTop, WIDTH - 99, 34, 45,
|
|
346
|
+
Math.max(0, Math.min(3, Math.floor((height - 51 - bodyTop) / 45))));
|
|
347
|
+
sample(context, card, 45, height - 34);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function compactCard(context: Context, card: ArtCard, height: number, index: number) {
|
|
351
|
+
const question = card.kind === 'question';
|
|
352
|
+
paper(context, height, question ? '#e4d5c1' : '#f0e7df', card.id);
|
|
353
|
+
context.fillStyle = MUTED; font(context, 20, 600);
|
|
354
|
+
context.fillText(`${question ? 'QUESTION' : 'OBSERVATION'} / ${String(index + 1).padStart(2, '0')}`, 43, 22);
|
|
355
|
+
if (question) { context.fillStyle = RED; font(context, 30, 600); context.fillText('?', WIDTH - 59, 19); }
|
|
356
|
+
context.fillStyle = INK;
|
|
357
|
+
const titleHeight = paragraph(context, card.title || '未命名线索', 43, 67, WIDTH - 87, 48, 59, 2, 600);
|
|
358
|
+
const bodyTop = 67 + titleHeight + 21, footer = card.sourceTitle ? 57 : 29;
|
|
359
|
+
paragraph(context, card.body, 44, bodyTop, WIDTH - 88, 33, 43,
|
|
360
|
+
Math.max(0, Math.min(3, Math.floor((height - footer - bodyTop) / 43))));
|
|
361
|
+
if (card.sourceTitle) {
|
|
362
|
+
context.fillStyle = MUTED; font(context, 19);
|
|
363
|
+
context.fillText(fitted(context, card.sourceTitle, WIDTH - 88), 44, height - 36);
|
|
364
|
+
}
|
|
365
|
+
path(context, [[42, 55], [WIDTH - 42, 55]], 'rgba(126,118,107,.15)', 1);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function tagCard(context: Context, card: ArtCard, height: number) {
|
|
369
|
+
paper(context, height, '#eee8dd', card.id);
|
|
370
|
+
context.fillStyle = '#c4b297'; context.fillRect(0, 0, 9, height);
|
|
371
|
+
context.fillStyle = INK; font(context, 62, 600);
|
|
372
|
+
context.fillText(fitted(context, card.title || '关键词', WIDTH - 118), 78, (height - 66) / 2);
|
|
373
|
+
// Labels deliberately show only their title; full content remains in the editor.
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** A board exposes one takeaway; the drawer retains the original title and complete text. */
|
|
377
|
+
function takeaway(value: string) {
|
|
378
|
+
const text=value.replace(/\s+/g,' ').trim(), sentence=text.match(/^.{12,}?[。!?](?:[”」』])?/u)?.[0];
|
|
379
|
+
return sentence && sentence.length < text.length ? sentence : text;
|
|
380
|
+
}
|
|
381
|
+
function boardHeading(context:Context, title:string, x:number, y:number, width:number, rows:number, size=55) {
|
|
382
|
+
let fittedSize=size;
|
|
383
|
+
while(fittedSize>44){font(context,fittedSize,550);if(wrap(context,title,width,20).length<=rows)break;fittedSize-=2;}
|
|
384
|
+
context.fillStyle=INK;
|
|
385
|
+
return paragraph(context,title,x,y,width,fittedSize,fittedSize*1.25,rows,550);
|
|
386
|
+
}
|
|
387
|
+
function investigationCard(context: Context, card: ArtCard, height: number) {
|
|
388
|
+
const kind=card.clueKind!, report=kind==='report';
|
|
389
|
+
const appearance = evidenceCardAppearance(card);
|
|
390
|
+
const accent=appearance.accent, left=report?66:44, right=report?66:44;
|
|
391
|
+
paper(context,height,appearance.paper,card.id);
|
|
392
|
+
context.fillStyle=accent;
|
|
393
|
+
// Real differences in document format: source slips, observation cards, labels and taped questions.
|
|
394
|
+
if(report){
|
|
395
|
+
context.fillStyle='#d6ddc9';context.fillRect(0,0,15,height);
|
|
396
|
+
path(context,[[27,0],[27,height]],'#d5d8c7',1);
|
|
397
|
+
context.fillStyle=accent;context.fillRect(left,37,86,4);
|
|
398
|
+
font(context,22,500);context.fillText('RESEARCH REPORT',left,60);
|
|
399
|
+
context.textAlign='right';font(context,20);context.fillText(card.evidenceLabel?.replace('REPORT / ','')||'IN PROGRESS',WIDTH-right,62);context.textAlign='left';
|
|
400
|
+
}else if(kind==='excerpt'){
|
|
401
|
+
context.fillStyle='#e0e5d9';context.fillRect(0,0,17,height);
|
|
402
|
+
path(context,[[left,73],[WIDTH-right,73]],'#bcc5b6',1.5);
|
|
403
|
+
}else if(kind==='finding'){
|
|
404
|
+
context.fillRect(0,0,WIDTH,8);context.fillRect(left,78,38,4);
|
|
405
|
+
}else if(kind==='time'){
|
|
406
|
+
context.fillStyle='#ded1b9';context.fillRect(0,0,18,height);
|
|
407
|
+
circle(context,WIDTH-67,43,14,accent,2);path(context,[[WIDTH-67,34],[WIDTH-67,43],[WIDTH-59,48]],accent,2);
|
|
408
|
+
}else if(kind==='relation'){
|
|
409
|
+
path(context,[[WIDTH-163,44],[WIDTH-74,44]],accent,2);
|
|
410
|
+
for(const x of [WIDTH-163,WIDTH-74]){circle(context,x,44,10,accent,2);}
|
|
411
|
+
}else if(kind==='question'){
|
|
412
|
+
const glue=context.createLinearGradient(0,0,0,75);glue.addColorStop(0,'#cdbc9866');glue.addColorStop(1,'#cdbc9800');
|
|
413
|
+
context.fillStyle=glue;context.fillRect(0,0,WIDTH,75);context.fillStyle=accent;context.globalAlpha=.16;
|
|
414
|
+
font(context,142,300);context.fillText('?',WIDTH-143,69);context.globalAlpha=1;
|
|
415
|
+
}else if(kind==='contrast'){
|
|
416
|
+
context.fillStyle='#aa9081';context.fillRect(0,0,WIDTH/2,7);context.fillStyle='#849380';context.fillRect(WIDTH/2,0,WIDTH/2,7);
|
|
417
|
+
path(context,[[left,79],[WIDTH-right,79]],'#c8bbae',1);
|
|
418
|
+
}
|
|
419
|
+
if(!report){
|
|
420
|
+
context.fillStyle=accent;font(context,23,500);context.fillText(appearance.name,left,32);
|
|
421
|
+
if(kind!=='time'&&kind!=='relation'){context.textAlign='right';font(context,22);context.fillText(card.id,WIDTH-right,34);context.textAlign='left';}
|
|
422
|
+
}
|
|
423
|
+
const titleY=report?116:kind==='question'?104:kind==='finding'?107:kind==='time'?84:98;
|
|
424
|
+
const titleWidth=WIDTH-left-right-(kind==='question'?30:0);
|
|
425
|
+
const used=boardHeading(context,card.title,left,titleY,titleWidth,report||kind==='question'||kind==='finding'?3:2,report?55:54);
|
|
426
|
+
const bodyTop=titleY+used+(report?26:22), footerTop=height-(report?101:80);
|
|
427
|
+
const leading=report?42:40;
|
|
428
|
+
const bodyRows=Math.max(0,Math.min(kind==='time'||kind==='relation'?1:2,Math.floor((footerTop-bodyTop)/leading)));
|
|
429
|
+
context.fillStyle=MUTED;
|
|
430
|
+
paragraph(context,takeaway(card.summary||card.body),left,bodyTop,WIDTH-left-right,report?31:32,leading,bodyRows);
|
|
431
|
+
const footerY=height-49;
|
|
432
|
+
path(context,[[left,height-69],[WIDTH-right,height-69]],report?'#b5bea6':'#a7ad993f',1);
|
|
433
|
+
context.fillStyle=accent;font(context,report?21:23,500);
|
|
434
|
+
if(report){
|
|
435
|
+
context.fillText('打开调查报告',left,footerY);context.textAlign='right';font(context,20);
|
|
436
|
+
context.fillText(card.sourceLabel||'',WIDTH-right-40,footerY+1);font(context,36);context.fillText('↗',WIDTH-right,footerY-10);context.textAlign='left';
|
|
437
|
+
}else{
|
|
438
|
+
context.fillText(fitted(context,card.sourceLabel||card.id,350),left,footerY);
|
|
439
|
+
context.textAlign='right';font(context,22);context.fillText(card.evidenceLabel||'待核验',WIDTH-right,footerY);context.textAlign='left';
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Draw one card at its supplied resolution; no scene, network or animation state. */
|
|
444
|
+
export function drawEvidenceCard(canvas: HTMLCanvasElement, card: EvidenceCard, index: number, photo?: CanvasImageSource): void {
|
|
445
|
+
const context = canvas.getContext('2d');
|
|
446
|
+
if (!context || canvas.width < 1 || canvas.height < 1) return;
|
|
447
|
+
const height = canvas.height / canvas.width * WIDTH;
|
|
448
|
+
const art = card as ArtCard;
|
|
449
|
+
context.save();
|
|
450
|
+
try {
|
|
451
|
+
context.setTransform(1, 0, 0, 1, 0, 0); context.clearRect(0, 0, canvas.width, canvas.height);
|
|
452
|
+
context.scale(canvas.width / WIDTH, canvas.width / WIDTH);
|
|
453
|
+
context.globalAlpha = 1; context.globalCompositeOperation = 'source-over';
|
|
454
|
+
context.filter = 'none'; context.shadowColor = 'transparent'; context.shadowBlur = 0;
|
|
455
|
+
context.shadowOffsetX = 0; context.shadowOffsetY = 0;
|
|
456
|
+
context.textAlign = 'left'; context.textBaseline = 'top'; context.lineCap = 'round'; context.lineJoin = 'round'; context.setLineDash([]);
|
|
457
|
+
context.beginPath(); context.rect(0, 0, WIDTH, height); context.clip();
|
|
458
|
+
if (art.clueKind) investigationCard(context, art, height);
|
|
459
|
+
else if (art.presentation === 'tag') tagCard(context, art, height);
|
|
460
|
+
else if (art.presentation === 'compact') compactCard(context, art, height, index);
|
|
461
|
+
else if (art.visual === 'observatory' && photo && photoCard(context, art, height, photo)) return;
|
|
462
|
+
else if (art.visual === 'schematic') schematicCard(context, art, height);
|
|
463
|
+
else if (art.visual === 'signal') signalCard(context, art, height, index);
|
|
464
|
+
else if (art.kind === 'source' || art.visual === 'observatory') sourceCard(context, art, height, index);
|
|
465
|
+
else if (art.kind === 'question') questionCard(context, art, height);
|
|
466
|
+
else noteCard(context, art, height, index);
|
|
467
|
+
} finally {
|
|
468
|
+
context.restore();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EvidenceCard } from './evidence-board-model';
|
|
2
|
+
|
|
3
|
+
const accents: Record<string, string> = { excerpt: '#7d887e', finding: '#647356', time: '#99815f', relation: '#6b8588', question: '#92774e', contrast: '#90756c', report: '#69765b', note: '#96806c' };
|
|
4
|
+
const stocks: Record<string, string[]> = {
|
|
5
|
+
excerpt: ['#f6f3eb', '#f8f5ed', '#f1eee5'], finding: ['#e8eddf', '#e4e9dc', '#edf0e4'],
|
|
6
|
+
time: ['#eee6d7', '#e9e2d4', '#f0eadd'], relation: ['#e6ecea', '#e1e9e7', '#e9eeec'],
|
|
7
|
+
question: ['#e9dfc7', '#eee3c9', '#e4d9bd'], contrast: ['#f4eee5', '#eee9df', '#f6f1e7'],
|
|
8
|
+
report: ['#f2f1e6'], note: ['#f0e7df'],
|
|
9
|
+
};
|
|
10
|
+
const names: Record<string, string> = { excerpt: '原文摘录', finding: '研究发现', time: '时间节点', relation: '关键关联', question: '待解问题', contrast: '交叉对照', report: '调查报告', note: '观察笔记' };
|
|
11
|
+
|
|
12
|
+
/** The printed card and its editor share the same paper stock and category accents. */
|
|
13
|
+
export function evidenceCardAppearance(card: EvidenceCard) {
|
|
14
|
+
const kind = card.clueKind || (card.kind === 'source' ? 'excerpt' : card.kind);
|
|
15
|
+
const variant = Math.abs(Math.trunc(card.variant || 0));
|
|
16
|
+
return { kind, name: names[kind], accent: accents[kind], paper: stocks[kind][variant % stocks[kind].length] };
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { ARCHIVE_LABEL_LAYER } from './original/label-renderer';
|
|
3
|
+
import type { EvidenceInboxView } from './evidence-inbox';
|
|
4
|
+
|
|
5
|
+
/** A compact sorting table beside the board. Reuses the existing scene and label pass. */
|
|
6
|
+
export function createEvidenceInboxScene(print:(texture:THREE.Texture)=>THREE.MeshBasicMaterial){
|
|
7
|
+
const group=new THREE.Group();group.name='Rhine_Priority_Evidence_Box';
|
|
8
|
+
const cube=new THREE.BoxGeometry(1,1,1),labelGeometry=new THREE.PlaneGeometry(2.58,.68),materials:THREE.Material[]=[];
|
|
9
|
+
const material=(color:string,metalness=0)=>{const m=new THREE.MeshStandardMaterial({color,roughness:.72,metalness,fog:false});materials.push(m);return m;};
|
|
10
|
+
const shell=material('#e5dfd3'),rim=material('#b4ad9b',.25),feet=material('#929683',.25),paper=material('#f5efdf'),tab=material('#a7ad8d'),lining=material('#a6a391');
|
|
11
|
+
const box=(parent:THREE.Group,size:number[],position:number[],m:THREE.Material)=>{const mesh=new THREE.Mesh(cube,m);mesh.scale.set(size[0],size[1],size[2]);mesh.position.set(position[0],position[1],position[2]);parent.add(mesh);return mesh;};
|
|
12
|
+
// Slender enamel work table; the foot height matches the archive furniture.
|
|
13
|
+
box(group,[4.3,.16,2.8],[0,4.92,0],shell);box(group,[4.12,.055,2.62],[0,5.025,0],rim);
|
|
14
|
+
for(const x of [-1.8,1.8])for(const z of [-1,1]){box(group,[.10,4.8,.1],[x,2.47,z],rim);box(group,[.28,.1,.34],[x,.1,z],feet);}
|
|
15
|
+
box(group,[3.7,.065,.065],[0,2.5,-1],rim);
|
|
16
|
+
// Open, lined sorting box with an olive metal index rail, not an archive cassette.
|
|
17
|
+
box(group,[3.48,.10,2.02],[0,5.14,0],shell);box(group,[3.25,.03,1.84],[0,5.20,0],lining);
|
|
18
|
+
box(group,[3.48,.87,.085],[0,5.57,1.01],shell);box(group,[3.48,1.35,.085],[0,5.8,-1.01],shell);
|
|
19
|
+
for(const x of [-1.70,1.70]){box(group,[.085,1.10,2.0],[x,5.68,0],shell);box(group,[.10,.05,2.02],[x,6.25,0],rim);}
|
|
20
|
+
box(group,[3.45,.045,.12],[0,6.02,1.01],rim);box(group,[.85,.09,.025],[0,5.34,1.06],rim);
|
|
21
|
+
const folders:THREE.Group[]=[];
|
|
22
|
+
for(let i=0;i<6;i++){
|
|
23
|
+
const folder=new THREE.Group();folder.name=`Priority_Folder_${i+1}`;folder.visible=false;folder.position.set((i%2)*.05,5.22,-.78+i*.28);group.add(folder);
|
|
24
|
+
box(folder,[3.0,1.48,.065],[0,.74,0],i%3===1?shell:paper);
|
|
25
|
+
box(folder,[.7,.2,.065],[-.98+(i%3)*.95,1.55,0],i%3===0?tab:paper);
|
|
26
|
+
box(folder,[2.7,.012,.015],[0,1.19,.043],rim);folders.push(folder);
|
|
27
|
+
}
|
|
28
|
+
const canvas=document.createElement('canvas');canvas.width=896;canvas.height=236;const ctx=canvas.getContext('2d')!;
|
|
29
|
+
const texture=new THREE.CanvasTexture(canvas);texture.colorSpace=THREE.SRGBColorSpace;const ink=print(texture);materials.push(ink);
|
|
30
|
+
const label=new THREE.Mesh(labelGeometry,ink);label.layers.set(ARCHIVE_LABEL_LAYER);label.position.set(0,5.62,1.058);group.add(label);
|
|
31
|
+
const initial=materials.map(m=>({m,opacity:m.opacity,transparent:m.transparent,depthWrite:m.depthWrite}));let reveal=1,count=0,boardId='',title='',opened=false;
|
|
32
|
+
function draw(){ctx.fillStyle='#e5dfd3';ctx.fillRect(0,0,896,236);ctx.fillStyle='#5b634c';ctx.font='500 38px MiSans, sans-serif';ctx.fillText('重点证据 / 待整理',36,73);ctx.textAlign='right';ctx.font='500 72px MiSans, sans-serif';ctx.fillText(String(count).padStart(2,'0'),855,97);ctx.textAlign='left';ctx.fillStyle='#858774';ctx.font='23px MiSans, sans-serif';ctx.fillText('RHINE LAB / EVIDENCE INBOX',38,133);ctx.font='22px MiSans, sans-serif';const text=title.length>30?title.slice(0,29)+'…':title||'选择调查后收集重点材料';ctx.fillText(text,38,195);texture.needsUpdate=true;}
|
|
33
|
+
draw();
|
|
34
|
+
return{group,
|
|
35
|
+
pick(ray:THREE.Raycaster){return group.visible&&ray.intersectObject(group,true).length>0;},
|
|
36
|
+
set(value:EvidenceInboxView,open:boolean){opened=open;if(boardId!==value.boardId||count!==value.count||title!==value.title){boardId=value.boardId;count=value.count;title=value.title;folders.forEach((folder,i)=>folder.visible=i<Math.min(6,count));draw();}},
|
|
37
|
+
update(amount:number){folders.forEach((folder,i)=>{folder.position.y=5.22+amount*(i===Math.min(count,6)-1?.16:.04);});},
|
|
38
|
+
setReveal(value:number){if(value===reveal)return;reveal=value;group.visible=value>0;for(const state of initial){state.m.opacity=state.opacity*value;state.m.depthWrite=value<1?false:state.depthWrite;const transparent=value<1||state.transparent;if(transparent!==state.m.transparent){state.m.transparent=transparent;state.m.needsUpdate=true;}}},
|
|
39
|
+
anchor(camera:THREE.Camera,width:number,height:number){group.updateWorldMatrix(true,false);const p=new THREE.Vector3(0,7.95,1.1).applyMatrix4(group.matrixWorld).project(camera);const x=(p.x+1)*width/2,y=(1-p.y)*height/2;return{x,y,visible:group.visible&&p.z>-1&&p.z<1&&x>100&&x<width-100&&y>height*.2&&y<height*.85};},
|
|
40
|
+
stats(){return{boardId,count,visible:group.visible,open:opened,folderCount:folders.filter(f=>f.visible).length,meshes:group.children.length,reveal};},
|
|
41
|
+
dispose(){group.removeFromParent();cube.dispose();labelGeometry.dispose();texture.dispose();materials.forEach(m=>m.dispose());}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.rhine-workbench .rhine-inbox-entry{position:absolute;left:4%;top:73%;z-index:24;display:grid;grid-template-columns:auto auto;align-items:baseline;gap:4px 16px;padding:10px 0;color:#515844;background:none;border:0;border-bottom:1px solid #96957b8c;cursor:pointer;text-align:left;min-width:160px;pointer-events:auto}
|
|
2
|
+
.rhine-workbench .rhine-inbox-entry b{font-size:24px;font-weight:450;font-variant-numeric:tabular-nums}.rhine-workbench .rhine-inbox-entry small{grid-column:1/-1;font-size:10px;color:#7c806e;letter-spacing:.1em}.rhine-workbench .rhine-inbox-entry.is-anchored{transform:translate(-50%,0)}
|
|
3
|
+
.rhine-workbench .rhine-inbox-panel{position:absolute;inset:0;z-index:44;overflow:hidden;background:linear-gradient(90deg,transparent 38%,#ebe8e073 52%,#ebe8e0ed 77%,#ebe8e0f5);color:#30362b;pointer-events:auto}
|
|
4
|
+
.rhine-workbench .rhine-inbox-panel>header{position:absolute;left:53%;right:5%;top:19%;display:flex;align-items:center;justify-content:space-between;gap:20px;font-size:11px;color:#7d806e;letter-spacing:.06em}
|
|
5
|
+
.rhine-workbench .rhine-inbox-content{position:absolute;left:53%;right:5%;top:25%;bottom:10%;overflow:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#9a9b8170 transparent;padding-right:12px}
|
|
6
|
+
.rhine-workbench .rhine-inbox-content h2{font-size:32px;font-weight:450;margin:0 0 8px}.rhine-workbench .rhine-inbox-subtitle{font-size:12px;color:#7d806e;line-height:1.8;margin:0 0 20px}
|
|
7
|
+
.rhine-workbench .rhine-inbox-tools{display:flex;align-items:center;gap:20px;padding-bottom:12px;border-bottom:1px solid #aca78f88}.rhine-workbench .rhine-inbox-filter{flex:1;min-width:0;width:100%;font-size:13px}.rhine-workbench .rhine-inbox-ask{font-size:11px;white-space:nowrap}
|
|
8
|
+
.rhine-workbench .rhine-inbox-panel :is(button,input,textarea,select){font:inherit;color:inherit;background:transparent;border:0}.rhine-workbench .rhine-inbox-panel button{cursor:pointer}.rhine-workbench .rhine-inbox-panel button:disabled{opacity:.4;cursor:default}
|
|
9
|
+
.rhine-workbench .rhine-inbox-row{border-bottom:1px solid #aca78f55}.rhine-workbench .rhine-inbox-select{display:flex;flex-direction:column;gap:6px;width:100%;padding:14px 10px;text-align:left;border-left:2px solid transparent!important}.rhine-workbench .rhine-inbox-select[aria-pressed=true]{border-left-color:#858e6e!important}.rhine-workbench .rhine-inbox-select strong{font-size:16px;font-weight:500;line-height:1.55}.rhine-workbench .rhine-inbox-select :is(small,span){font-size:11px;line-height:1.6;color:#7d806e}.rhine-workbench .rhine-inbox-select span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
10
|
+
.rhine-workbench .rhine-inbox-pager,.rhine-workbench .rhine-inbox-actions{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:15px 0;font-size:12px}.rhine-workbench .rhine-inbox-pager{color:#7d806e}.rhine-workbench .rhine-inbox-details{padding-top:22px}.rhine-workbench .rhine-inbox-details h3{font-size:17px;line-height:1.6;font-weight:500;margin:0 0 8px}.rhine-workbench .rhine-inbox-meta{font-size:11px;color:#7d806e}.rhine-workbench .rhine-inbox-note{font-size:13px;line-height:1.85;white-space:pre-wrap}.rhine-workbench .rhine-inbox-actions{border-top:1px solid #aca78f88}.rhine-workbench .rhine-inbox-remove{color:#847466}.rhine-workbench .rhine-inbox-empty{font-size:14px;color:#7d806e;line-height:2;white-space:pre-line;padding:26px 0}
|
|
11
|
+
.rhine-workbench .rhine-inbox-form{display:flex;flex-direction:column;gap:16px;padding-top:22px}.rhine-workbench .rhine-inbox-form small{font-size:11px;color:#7d806e}.rhine-workbench .rhine-inbox-form input{font-size:19px;padding:10px 0;border-bottom:1px solid #aca78f88}.rhine-workbench .rhine-inbox-form textarea{width:100%;resize:vertical;line-height:1.8;font-size:14px;border-bottom:1px solid #aca78f88}.rhine-workbench .rhine-inbox-form-options{display:flex;gap:24px;font-size:13px}
|
|
12
|
+
.rhine-workbench .has-evidence-inbox :is(.rhine-investigation-hud,.rhine-board-toolbar,.rhine-board-chalk-hit,.rhine-board-tool-status){visibility:hidden;pointer-events:none}.rhine-workbench.has-reading-object .rhine-inbox-entry{display:none}
|
|
13
|
+
.rhine-workbench :is(.rhine-inbox-entry,.rhine-inbox-panel,.rhine-inbox-form,.rhine-inbox-details,.rhine-inbox-pager,.rhine-inbox-ask)[hidden]{display:none}
|
|
14
|
+
@media(max-width:900px){.rhine-workbench .rhine-inbox-panel>header{left:45%;right:4%;top:21%}.rhine-workbench .rhine-inbox-content{left:45%;right:4%;top:28%;bottom:9%}.rhine-workbench .rhine-inbox-content h2{font-size:27px}.rhine-workbench .rhine-inbox-tools{flex-wrap:wrap;gap:10px}.rhine-workbench .rhine-inbox-entry{min-width:130px;font-size:12px}}
|
|
15
|
+
@media(max-aspect-ratio:4/5){.rhine-workbench .rhine-inbox-panel{background:linear-gradient(transparent 20%,#ebe8e0ba 36%,#ebe8e0f5 70%)}.rhine-workbench .rhine-inbox-panel>header{left:5%;right:5%;top:32%}.rhine-workbench .rhine-inbox-content{left:5%;right:5%;top:39%;bottom:7%}.rhine-workbench .rhine-inbox-entry{left:6%;top:66%}}
|
|
16
|
+
|
|
17
|
+
.rhine-workbench .rhine-priority-actions{display:flex;align-items:center;justify-content:space-between;gap:16px}.rhine-workbench .rhine-priority-actions>button{width:auto}.rhine-workbench :is(.rhine-array-stage,.rhine-shelf-stage){font-size:11px;white-space:nowrap;color:#788063}.rhine-workbench :is(.rhine-array-stage,.rhine-shelf-stage):disabled{opacity:.4}
|
|
18
|
+
|
|
19
|
+
.rhine-workbench .rhine-inbox-content{display:flex;flex-direction:column;gap:0}
|
|
20
|
+
.rhine-workbench .rhine-inbox-content>:not(.rhine-inbox-list){flex-shrink:0}
|
|
21
|
+
.rhine-workbench .rhine-inbox-list{flex:1 1 auto;min-height:100px;overflow:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#9a9b8170 transparent}
|
|
22
|
+
.rhine-workbench .rhine-inbox-select{padding:12px 10px}
|
|
23
|
+
.rhine-workbench .rhine-inbox-details{padding-top:12px}
|
|
24
|
+
.rhine-workbench .rhine-inbox-details h3{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
25
|
+
.rhine-workbench .rhine-inbox-meta{margin:4px 0}
|
|
26
|
+
.rhine-workbench .rhine-inbox-note{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin:8px 0 12px}
|
|
27
|
+
.rhine-workbench .rhine-inbox-pager{padding:10px 0;font-size:11px}
|
|
28
|
+
.rhine-workbench .rhine-inbox-content.is-editing :is(.rhine-inbox-list,.rhine-inbox-pager,.rhine-inbox-tools){display:none}
|
|
29
|
+
.rhine-workbench .has-evidence-inbox :is(.rhine-evidence-panel,.rhine-evidence-launcher,.rhine-board-compact,.rhine-zone-nav,.rhine-scene-edge){visibility:hidden;pointer-events:none}
|