@ifc-lite/viewer 1.17.4 → 1.18.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.
Files changed (196) hide show
  1. package/.turbo/turbo-build.log +20 -17
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +630 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -1
  5. package/dist/assets/{basketViewActivator-BmnNtVfZ.js → basketViewActivator-Cm1QEk_R.js} +1 -1
  6. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  7. package/dist/assets/{exporters-ChAtBmlj.js → exporters-B_OBqIyD.js} +3479 -2845
  8. package/dist/assets/{geometry.worker-BQ0rzNo-.js → geometry.worker-xHHy-9DV.js} +1 -1
  9. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  10. package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
  11. package/dist/assets/{index-Co8E2-FE.js → index-BKq-M3Mk.js} +55873 -40593
  12. package/dist/assets/index-COnQRuqY.css +1 -0
  13. package/dist/assets/{native-bridge-BRvbckFQ.js → native-bridge-SHXiQwFW.js} +104 -104
  14. package/dist/assets/sandbox-jez21HtV.js +9627 -0
  15. package/dist/assets/{server-client-BV8zHZ7Y.js → server-client-ncOQVNso.js} +1 -1
  16. package/dist/assets/{wasm-bridge-g01g7T9b.js → wasm-bridge-DyfBSB8z.js} +1 -1
  17. package/dist/index.html +8 -7
  18. package/index.html +1 -0
  19. package/package.json +13 -13
  20. package/src/App.tsx +16 -2
  21. package/src/apache-arrow.d.ts +30 -0
  22. package/src/components/viewer/AddElementPanel.tsx +758 -0
  23. package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
  24. package/src/components/viewer/CesiumOverlay.tsx +62 -19
  25. package/src/components/viewer/ChatPanel.tsx +259 -93
  26. package/src/components/viewer/CommandPalette.tsx +56 -7
  27. package/src/components/viewer/EntityContextMenu.tsx +168 -4
  28. package/src/components/viewer/ExportChangesButton.tsx +25 -5
  29. package/src/components/viewer/ExportDialog.tsx +19 -1
  30. package/src/components/viewer/MainToolbar.tsx +73 -13
  31. package/src/components/viewer/PropertiesPanel.tsx +237 -23
  32. package/src/components/viewer/SearchInline.tsx +669 -0
  33. package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
  34. package/src/components/viewer/SearchModal.filter.tsx +514 -0
  35. package/src/components/viewer/SearchModal.text.tsx +388 -0
  36. package/src/components/viewer/SearchModal.tsx +235 -0
  37. package/src/components/viewer/SettingsPage.tsx +252 -101
  38. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  39. package/src/components/viewer/ToolOverlays.tsx +5 -0
  40. package/src/components/viewer/ViewerLayout.tsx +25 -4
  41. package/src/components/viewer/Viewport.tsx +25 -3
  42. package/src/components/viewer/ViewportContainer.tsx +51 -64
  43. package/src/components/viewer/ViewportOverlays.tsx +5 -2
  44. package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
  45. package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
  46. package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
  47. package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
  48. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  49. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  50. package/src/components/viewer/lists/ListPanel.tsx +14 -21
  51. package/src/components/viewer/properties/GeoreferencingPanel.tsx +113 -51
  52. package/src/components/viewer/properties/LocationMap.tsx +9 -7
  53. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  54. package/src/components/viewer/properties/RawStepCard.tsx +332 -0
  55. package/src/components/viewer/properties/RawStepRow.tsx +261 -0
  56. package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
  57. package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
  58. package/src/components/viewer/properties/raw-step-format.ts +193 -0
  59. package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
  60. package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
  61. package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
  62. package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
  63. package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
  64. package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
  65. package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
  66. package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
  67. package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
  68. package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
  69. package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
  70. package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
  71. package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
  72. package/src/components/viewer/schedule/generate-schedule.ts +648 -0
  73. package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
  74. package/src/components/viewer/schedule/schedule-animator.ts +488 -0
  75. package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
  76. package/src/components/viewer/schedule/schedule-selection.ts +163 -0
  77. package/src/components/viewer/schedule/schedule-utils.ts +223 -0
  78. package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
  79. package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
  80. package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
  81. package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
  82. package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
  83. package/src/components/viewer/selectionHandlers.ts +446 -0
  84. package/src/components/viewer/tools/AddElementOverlay.tsx +540 -0
  85. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  86. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  87. package/src/components/viewer/useAnimationLoop.ts +9 -1
  88. package/src/components/viewer/useDuplicateShortcut.ts +77 -0
  89. package/src/components/viewer/useMouseControls.ts +9 -1
  90. package/src/components/viewer/useRenderUpdates.ts +1 -1
  91. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  92. package/src/hooks/ingest/viewerModelIngest.ts +7 -2
  93. package/src/hooks/useIfcFederation.ts +326 -71
  94. package/src/hooks/useIfcLoader.ts +23 -10
  95. package/src/hooks/useKeyboardShortcuts.ts +25 -0
  96. package/src/hooks/useSandbox.ts +1 -1
  97. package/src/hooks/useSearchIndex.ts +125 -0
  98. package/src/hooks/useViewControls.ts +13 -5
  99. package/src/index.css +550 -10
  100. package/src/lib/desktop-entitlement.ts +2 -4
  101. package/src/lib/geo/cesium-bridge.ts +15 -7
  102. package/src/lib/geo/effective-georef.test.ts +73 -0
  103. package/src/lib/geo/effective-georef.ts +111 -0
  104. package/src/lib/geo/reproject.ts +105 -19
  105. package/src/lib/llm/byok-guard.test.ts +77 -0
  106. package/src/lib/llm/byok-guard.ts +39 -0
  107. package/src/lib/llm/free-models.test.ts +0 -6
  108. package/src/lib/llm/models.ts +104 -42
  109. package/src/lib/llm/stream-client.ts +74 -110
  110. package/src/lib/llm/stream-direct.test.ts +130 -0
  111. package/src/lib/llm/stream-direct.ts +316 -0
  112. package/src/lib/llm/system-prompt.test.ts +14 -0
  113. package/src/lib/llm/system-prompt.ts +102 -1
  114. package/src/lib/llm/types.ts +20 -2
  115. package/src/lib/recent-files.ts +38 -4
  116. package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
  117. package/src/lib/scripts/templates/construction-schedule.ts +223 -0
  118. package/src/lib/scripts/templates.ts +7 -0
  119. package/src/lib/search/common-ifc-types.ts +36 -0
  120. package/src/lib/search/filter-evaluate.test.ts +537 -0
  121. package/src/lib/search/filter-evaluate.ts +610 -0
  122. package/src/lib/search/filter-rules.test.ts +119 -0
  123. package/src/lib/search/filter-rules.ts +198 -0
  124. package/src/lib/search/filter-schema.test.ts +233 -0
  125. package/src/lib/search/filter-schema.ts +146 -0
  126. package/src/lib/search/recent-searches.test.ts +116 -0
  127. package/src/lib/search/recent-searches.ts +93 -0
  128. package/src/lib/search/result-export.test.ts +101 -0
  129. package/src/lib/search/result-export.ts +104 -0
  130. package/src/lib/search/saved-filters.test.ts +118 -0
  131. package/src/lib/search/saved-filters.ts +154 -0
  132. package/src/lib/search/tier0-scan.test.ts +196 -0
  133. package/src/lib/search/tier0-scan.ts +237 -0
  134. package/src/lib/search/tier1-index.test.ts +242 -0
  135. package/src/lib/search/tier1-index.ts +448 -0
  136. package/src/main.tsx +1 -10
  137. package/src/sdk/adapters/export-adapter.test.ts +434 -1
  138. package/src/sdk/adapters/export-adapter.ts +404 -1
  139. package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
  140. package/src/sdk/adapters/export-schedule-splice.ts +87 -0
  141. package/src/sdk/adapters/model-compat.ts +8 -2
  142. package/src/sdk/adapters/schedule-adapter.ts +73 -0
  143. package/src/sdk/adapters/store-adapter.ts +201 -0
  144. package/src/sdk/adapters/visibility-adapter.ts +3 -0
  145. package/src/sdk/local-backend.ts +16 -8
  146. package/src/services/api-keys.ts +73 -0
  147. package/src/services/desktop-export.ts +3 -1
  148. package/src/services/desktop-native-metadata.ts +41 -18
  149. package/src/services/file-dialog.ts +4 -1
  150. package/src/services/tauri-modules.d.ts +25 -0
  151. package/src/store/basketVisibleSet.ts +3 -0
  152. package/src/store/constants.ts +20 -2
  153. package/src/store/globalId.ts +4 -1
  154. package/src/store/index.ts +82 -6
  155. package/src/store/slices/addElementMeshes.ts +365 -0
  156. package/src/store/slices/addElementSlice.ts +275 -0
  157. package/src/store/slices/annotationsSlice.test.ts +133 -0
  158. package/src/store/slices/annotationsSlice.ts +251 -0
  159. package/src/store/slices/cesiumSlice.ts +5 -0
  160. package/src/store/slices/chatSlice.test.ts +6 -76
  161. package/src/store/slices/chatSlice.ts +17 -58
  162. package/src/store/slices/dataSlice.test.ts +23 -4
  163. package/src/store/slices/dataSlice.ts +1 -1
  164. package/src/store/slices/modelSlice.test.ts +67 -9
  165. package/src/store/slices/modelSlice.ts +39 -7
  166. package/src/store/slices/mutationSlice.ts +964 -3
  167. package/src/store/slices/overlayCompositor.test.ts +164 -0
  168. package/src/store/slices/overlaySlice.test.ts +93 -0
  169. package/src/store/slices/overlaySlice.ts +151 -0
  170. package/src/store/slices/pinboardSlice.test.ts +6 -1
  171. package/src/store/slices/playbackSlice.ts +128 -0
  172. package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
  173. package/src/store/slices/schedule-edit-helpers.ts +179 -0
  174. package/src/store/slices/scheduleSlice.test.ts +694 -0
  175. package/src/store/slices/scheduleSlice.ts +1330 -0
  176. package/src/store/slices/searchSlice.test.ts +342 -0
  177. package/src/store/slices/searchSlice.ts +341 -0
  178. package/src/store/slices/sectionSlice.test.ts +87 -7
  179. package/src/store/slices/sectionSlice.ts +151 -5
  180. package/src/store/slices/selectionSlice.test.ts +46 -0
  181. package/src/store/slices/selectionSlice.ts +20 -0
  182. package/src/store/slices/uiSlice.ts +28 -5
  183. package/src/store/types.ts +26 -0
  184. package/src/store.ts +14 -0
  185. package/src/utils/nativeSpatialDataStore.ts +4 -1
  186. package/src/utils/viewportUtils.ts +7 -2
  187. package/src/vite-env.d.ts +0 -4
  188. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  189. package/dist/assets/ids-B4jTqB1O.js +0 -1
  190. package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
  191. package/dist/assets/index-DckuDqlv.css +0 -1
  192. package/dist/assets/sandbox-DZiNLNMk.js +0 -5933
  193. package/src/components/viewer/UpgradePage.tsx +0 -71
  194. package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +0 -175
  195. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  196. package/src/lib/llm/clerk-auth.ts +0 -62
package/src/index.css CHANGED
@@ -379,6 +379,43 @@ body {
379
379
  color: var(--color-primary) !important;
380
380
  }
381
381
 
382
+ /* Raw STEP tab — terminal-flavoured dev affordance.
383
+ Compact (icon-only </>), separates visually from the three "human"
384
+ tabs with a left divider and a green active state that nods to a
385
+ shell cursor. Stays width-stable at narrow panel widths because
386
+ it explicitly opts out of `flex: 1` (uses an `auto`-ish width
387
+ driven by the </> glyph plus padding). */
388
+ .properties-tab-trigger.raw-step-tab-trigger {
389
+ flex: 0 0 auto;
390
+ min-width: 2.25rem;
391
+ padding-left: 0.5rem;
392
+ padding-right: 0.5rem;
393
+ border-left: 1px solid var(--tabs-border);
394
+ letter-spacing: 0;
395
+ color: color-mix(in srgb, var(--tab-text) 70%, transparent) !important;
396
+ }
397
+
398
+ .properties-tab-trigger.raw-step-tab-trigger:hover {
399
+ background-color: color-mix(in srgb, #10b981 16%, var(--tabs-bg)) !important;
400
+ color: #047857 !important;
401
+ }
402
+
403
+ .dark .properties-tab-trigger.raw-step-tab-trigger:hover {
404
+ background-color: color-mix(in srgb, #10b981 22%, var(--tabs-bg)) !important;
405
+ color: #34d399 !important;
406
+ }
407
+
408
+ .properties-tab-trigger.raw-step-tab-trigger[data-state="active"] {
409
+ background-color: color-mix(in srgb, #10b981 14%, var(--tab-active-bg)) !important;
410
+ color: #047857 !important;
411
+ border-top-color: #10b981 !important;
412
+ }
413
+
414
+ .dark .properties-tab-trigger.raw-step-tab-trigger[data-state="active"] {
415
+ color: #34d399 !important;
416
+ border-top-color: #34d399 !important;
417
+ }
418
+
382
419
  /* Quantity cards - cyan accent */
383
420
  .dark .border-blue-200,
384
421
  .dark .border-blue-800,
@@ -412,6 +449,490 @@ body {
412
449
  background-color: rgba(115, 218, 202, 0.05) !important;
413
450
  }
414
451
 
452
+ /* ═══════════════════════════════════════════════════════════════════════════
453
+ COLORFUL MODE — Brutalist Aurora
454
+ Shift+click the toggle. The same sharp, functional, dense UI — but the
455
+ sky opened up. An aurora gradient behind the 3D scene, frosted-glass
456
+ panels, and the full Tokyo Night accent palette in its natural places.
457
+ No decorative borders. No gradient buttons. Just atmosphere.
458
+ ═══════════════════════════════════════════════════════════════════════════ */
459
+
460
+ @custom-variant colorful (&:where(.colorful, .colorful *));
461
+
462
+ .colorful {
463
+ /* Tokyo Night accent palette — used only where colour already exists */
464
+ --cf-blue: #7aa2f7;
465
+ --cf-cyan: #7dcfff;
466
+ --cf-teal: #73daca;
467
+ --cf-magenta: #bb9af7;
468
+ --cf-purple: #9d7cd8;
469
+ --cf-orange: #ff9e64;
470
+ --cf-pink: #f7768e;
471
+ --cf-green: #9ece6a;
472
+ --cf-yellow: #e0af68;
473
+
474
+ /* Glass */
475
+ --cf-glass: rgba(255, 255, 255, 0.48);
476
+ --cf-glass-strong: rgba(255, 255, 255, 0.68);
477
+
478
+ /* ── Semantic tokens ── */
479
+ --color-background: #e0e6f0 !important;
480
+ --color-foreground: #1a1b2e !important;
481
+ --color-card: var(--cf-glass) !important;
482
+ --color-card-foreground: #1a1b2e !important;
483
+ --color-popover: var(--cf-glass-strong) !important;
484
+ --color-popover-foreground: #1a1b2e !important;
485
+ --color-primary: var(--cf-purple) !important;
486
+ --color-primary-foreground: #fff !important;
487
+ --color-secondary: rgba(157, 124, 216, 0.10) !important;
488
+ --color-secondary-foreground: #2e3050 !important;
489
+ --color-muted: rgba(0, 0, 0, 0.04) !important;
490
+ --color-muted-foreground: #555b78 !important;
491
+ --color-accent: rgba(157, 124, 216, 0.10) !important;
492
+ --color-accent-foreground: #2e3050 !important;
493
+ --color-destructive: var(--cf-pink) !important;
494
+ --color-destructive-foreground: #fff !important;
495
+ /* Borders: neutral, semi-transparent — NOT coloured */
496
+ --color-border: rgba(0, 0, 0, 0.10) !important;
497
+ --color-input: rgba(0, 0, 0, 0.06) !important;
498
+ --color-ring: var(--cf-purple) !important;
499
+
500
+ /* Hierarchy — lavender tints */
501
+ --hierarchy-selected-bg: rgba(157, 124, 216, 0.12);
502
+ --hierarchy-selected-text: #1a1b2e;
503
+ --hierarchy-text: #40445e;
504
+ --hierarchy-hover-bg: rgba(200, 195, 230, 0.15);
505
+
506
+ /* Tabs — warm peach undertone */
507
+ --tabs-bg: rgba(235, 215, 195, 0.25);
508
+ --tabs-border: rgba(0, 0, 0, 0.08);
509
+ --tab-text: #555b78;
510
+ --tab-active-bg: rgba(245, 230, 210, 0.55);
511
+ --tab-active-text: #1a1b2e;
512
+ }
513
+
514
+ /* ── Page background ── */
515
+ .colorful body,
516
+ .colorful #root {
517
+ background: #dde3f0 !important;
518
+ }
519
+
520
+ /* ── Toolbar: cool violet-tinted glass ── */
521
+ .colorful .border-b.bg-white,
522
+ .colorful .border-b.dark\:bg-black {
523
+ background: rgba(230, 225, 245, 0.60) !important;
524
+ backdrop-filter: blur(20px) saturate(1.5) !important;
525
+ -webkit-backdrop-filter: blur(20px) saturate(1.5) !important;
526
+ }
527
+
528
+ /* ── Panels & cards: frosted glass (neutral base) ── */
529
+ .colorful .bg-white,
530
+ .colorful .bg-card,
531
+ .colorful .bg-background {
532
+ background: var(--cf-glass) !important;
533
+ backdrop-filter: blur(16px) saturate(1.3) !important;
534
+ -webkit-backdrop-filter: blur(16px) saturate(1.3) !important;
535
+ }
536
+
537
+ .colorful .bg-zinc-50,
538
+ .colorful .bg-zinc-100 {
539
+ background-color: rgba(255, 255, 255, 0.25) !important;
540
+ }
541
+
542
+ .colorful .bg-zinc-900,
543
+ .colorful .bg-zinc-950,
544
+ .colorful .bg-black {
545
+ background: var(--cf-glass) !important;
546
+ backdrop-filter: blur(16px) saturate(1.3) !important;
547
+ -webkit-backdrop-filter: blur(16px) saturate(1.3) !important;
548
+ }
549
+
550
+ /* ── Tinted glass per panel — colour echoes the aurora ── */
551
+
552
+ /* Left panel: cool lavender tint (echoes aurora top) */
553
+ .colorful .panel-container:first-of-type,
554
+ .colorful [id="left-panel"] > .panel-container,
555
+ .colorful [id="left-panel"] > div {
556
+ background: rgba(200, 195, 230, 0.38) !important;
557
+ backdrop-filter: blur(16px) saturate(1.4) !important;
558
+ -webkit-backdrop-filter: blur(16px) saturate(1.4) !important;
559
+ }
560
+
561
+ /* Right panel: warm peach/amber tint (echoes aurora bottom) */
562
+ .colorful [id="right-panel"] > .panel-container,
563
+ .colorful [id="right-panel"] > div {
564
+ background: rgba(235, 215, 195, 0.40) !important;
565
+ backdrop-filter: blur(16px) saturate(1.4) !important;
566
+ -webkit-backdrop-filter: blur(16px) saturate(1.4) !important;
567
+ }
568
+
569
+ /* Resize handles: thin, tinted */
570
+ .colorful [data-panel-group-direction="horizontal"] > [data-resize-handle] {
571
+ background-color: rgba(157, 124, 216, 0.18) !important;
572
+ transition: background-color 0.15s;
573
+ }
574
+
575
+ .colorful [data-panel-group-direction="horizontal"] > [data-resize-handle]:hover {
576
+ background-color: rgba(157, 124, 216, 0.35) !important;
577
+ }
578
+
579
+ .colorful [data-panel-group-direction="horizontal"] > [data-resize-handle][data-resize-handle-active] {
580
+ background-color: rgba(157, 124, 216, 0.50) !important;
581
+ }
582
+
583
+ /* ── Borders: single neutral tone everywhere ── */
584
+ .colorful .border-zinc-100,
585
+ .colorful .border-zinc-200,
586
+ .colorful .border-zinc-300,
587
+ .colorful .border-zinc-400,
588
+ .colorful .border-zinc-500,
589
+ .colorful .border-zinc-600,
590
+ .colorful .border-zinc-700,
591
+ .colorful .border-zinc-800,
592
+ .colorful .border-zinc-900,
593
+ .colorful .border-zinc-950 {
594
+ border-color: rgba(0, 0, 0, 0.10) !important;
595
+ }
596
+
597
+ .colorful .border,
598
+ .colorful .border-b,
599
+ .colorful .border-t,
600
+ .colorful .border-l,
601
+ .colorful .border-r,
602
+ .colorful .border-border {
603
+ border-color: rgba(0, 0, 0, 0.10) !important;
604
+ }
605
+
606
+ /* Purple borders (property editor) stay purple but softened */
607
+ .colorful .border-purple-200,
608
+ .colorful .border-purple-300\/50,
609
+ .colorful .border-purple-500\/30,
610
+ .colorful .border-purple-700,
611
+ .colorful .border-purple-800 {
612
+ border-color: rgba(157, 124, 216, 0.25) !important;
613
+ }
614
+
615
+ /* ── Text ── */
616
+ .colorful .text-zinc-900,
617
+ .colorful .text-zinc-800,
618
+ .colorful .text-zinc-700,
619
+ .colorful .text-zinc-100 {
620
+ color: #1a1b2e !important;
621
+ }
622
+
623
+ .colorful .text-zinc-500,
624
+ .colorful .text-zinc-400,
625
+ .colorful .text-zinc-600 {
626
+ color: #555b78 !important;
627
+ }
628
+
629
+ .colorful .text-zinc-200,
630
+ .colorful .text-zinc-300 {
631
+ color: #40445e !important;
632
+ }
633
+
634
+ /* ── Primary: purple (from Tokyo Night magenta/purple family) ── */
635
+ .colorful .bg-primary {
636
+ background-color: var(--cf-purple) !important;
637
+ }
638
+
639
+ .colorful .text-primary {
640
+ color: var(--cf-purple) !important;
641
+ }
642
+
643
+ .colorful .border-primary {
644
+ border-color: var(--cf-purple) !important;
645
+ }
646
+
647
+ /* ── Shadows ── */
648
+ .colorful .shadow-lg,
649
+ .colorful .shadow-xl {
650
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
651
+ }
652
+
653
+ /* ── Selection ── */
654
+ .colorful ::selection {
655
+ background-color: rgba(157, 124, 216, 0.25);
656
+ color: #1a1b2e;
657
+ }
658
+
659
+ /* ── Scrollbars ── */
660
+ .colorful .scrollbar-thin {
661
+ scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
662
+ }
663
+
664
+ .colorful .scrollbar-thin::-webkit-scrollbar-thumb {
665
+ background-color: rgba(0, 0, 0, 0.15);
666
+ }
667
+
668
+ .colorful .scrollbar-thin::-webkit-scrollbar-thumb:hover {
669
+ background-color: rgba(0, 0, 0, 0.25);
670
+ }
671
+
672
+ /* ── Hovers: tinted to match panel context ── */
673
+ .colorful .hover\:bg-primary:hover {
674
+ background-color: var(--cf-purple) !important;
675
+ }
676
+
677
+ .colorful .hover\:bg-zinc-50:hover,
678
+ .colorful .hover\:bg-zinc-100:hover,
679
+ .colorful .hover\:bg-zinc-200:hover,
680
+ .colorful .hover\:bg-white:hover {
681
+ background-color: rgba(200, 195, 230, 0.18) !important;
682
+ }
683
+
684
+ /* ── Active tabs ── */
685
+ .colorful [data-state="active"] {
686
+ background-color: var(--cf-glass-strong) !important;
687
+ color: #1a1b2e !important;
688
+ }
689
+
690
+ .colorful .data-\[state\=active\]\:bg-white[data-state="active"],
691
+ .colorful .data-\[state\=active\]\:bg-zinc-100[data-state="active"] {
692
+ background-color: var(--cf-glass-strong) !important;
693
+ }
694
+
695
+ /* Properties tabs: warm orange accent on active (echoes aurora horizon) */
696
+ .colorful .properties-tab-trigger[data-state="active"] {
697
+ border-top-color: var(--cf-orange) !important;
698
+ background: rgba(245, 230, 210, 0.50) !important;
699
+ }
700
+
701
+ /* ── Tabs container: warmer tint to match right panel ── */
702
+ .colorful .properties-tabs-list {
703
+ background-color: rgba(235, 215, 195, 0.30) !important;
704
+ border-bottom-color: rgba(0, 0, 0, 0.08) !important;
705
+ }
706
+
707
+ /* ── Tree selection: purple tint with left accent ── */
708
+ .colorful .tree-node.selected {
709
+ background-color: rgba(157, 124, 216, 0.12) !important;
710
+ border-left-color: var(--cf-purple) !important;
711
+ }
712
+
713
+ .colorful .hierarchy-item.selected {
714
+ background-color: rgba(157, 124, 216, 0.12) !important;
715
+ }
716
+
717
+ /* ── Hierarchy hover: subtle lavender ── */
718
+ .colorful .hierarchy-item:not(.selected):hover {
719
+ background-color: rgba(200, 195, 230, 0.20) !important;
720
+ }
721
+
722
+ /* ── Quantity cards ── */
723
+ .colorful .border-blue-200,
724
+ .colorful .border-blue-800,
725
+ .colorful .border-blue-900 {
726
+ border-color: rgba(122, 162, 247, 0.25) !important;
727
+ }
728
+
729
+ .colorful .text-blue-700,
730
+ .colorful .text-blue-400 {
731
+ color: #6a8fd6 !important;
732
+ }
733
+
734
+ .colorful .bg-blue-50\/20,
735
+ .colorful .bg-blue-950\/20 {
736
+ background-color: rgba(122, 162, 247, 0.06) !important;
737
+ }
738
+
739
+ /* ── Success / Location ── */
740
+ .colorful .border-emerald-500\/30 {
741
+ border-color: rgba(115, 218, 202, 0.25) !important;
742
+ }
743
+
744
+ .colorful .text-emerald-800,
745
+ .colorful .text-emerald-400 {
746
+ color: #3d9e88 !important;
747
+ }
748
+
749
+ .colorful .bg-emerald-50\/50,
750
+ .colorful .bg-emerald-900\/10 {
751
+ background-color: rgba(115, 218, 202, 0.06) !important;
752
+ }
753
+
754
+ /* ── Toolbar buttons ── */
755
+ .colorful .toolbar-button:hover {
756
+ background-color: rgba(200, 195, 230, 0.25) !important;
757
+ }
758
+
759
+ .colorful .toolbar-button.active {
760
+ background-color: var(--cf-purple) !important;
761
+ color: #fff !important;
762
+ }
763
+
764
+ /* ── Status bar (bottom): warm tint ── */
765
+ .colorful .border-t.bg-white,
766
+ .colorful .border-t.dark\:bg-black {
767
+ background: rgba(240, 225, 210, 0.50) !important;
768
+ backdrop-filter: blur(16px) saturate(1.3) !important;
769
+ -webkit-backdrop-filter: blur(16px) saturate(1.3) !important;
770
+ }
771
+
772
+ /* ── Drop overlay ── */
773
+ .colorful .bg-\[\#9ece6a\]\/10 {
774
+ background-color: rgba(115, 218, 202, 0.10) !important;
775
+ }
776
+
777
+ .colorful .border-\[\#9ece6a\] {
778
+ border-color: var(--cf-teal) !important;
779
+ }
780
+
781
+ .colorful .text-\[\#9ece6a\] {
782
+ color: var(--cf-teal) !important;
783
+ }
784
+
785
+ /* ── Popover / dropdown menus: glass ── */
786
+ .colorful [data-radix-popper-content-wrapper] [role="menu"],
787
+ .colorful [data-radix-popper-content-wrapper] [role="listbox"],
788
+ .colorful [data-radix-popper-content-wrapper] [data-radix-collection-item] {
789
+ background: var(--cf-glass-strong) !important;
790
+ backdrop-filter: blur(20px) saturate(1.4) !important;
791
+ -webkit-backdrop-filter: blur(20px) saturate(1.4) !important;
792
+ }
793
+
794
+ /* ═══════════════════════════════════════════════════════════════════════════
795
+ COLORFUL — Component-specific colour variations
796
+ Each section gets a subtle tint from the Tokyo palette, mapped to its
797
+ semantic role. Tasteful, not overwhelming.
798
+ ═══════════════════════════════════════════════════════════════════════════ */
799
+
800
+ /* ── Section headers in hierarchy: cool lavender bg ── */
801
+ .colorful .bg-zinc-100.border-b,
802
+ .colorful .bg-zinc-900.border-b {
803
+ background-color: rgba(200, 195, 230, 0.30) !important;
804
+ }
805
+
806
+ /* ── Section headers in properties: warm peach bg ── */
807
+ .colorful .bg-zinc-50.border-b {
808
+ background-color: rgba(240, 220, 195, 0.35) !important;
809
+ }
810
+
811
+ /* Properties section header text: warmer dark tone */
812
+ .colorful .bg-zinc-50 .text-zinc-700,
813
+ .colorful .bg-zinc-50 .text-zinc-600 {
814
+ color: #5a4a3a !important;
815
+ }
816
+
817
+ /* ── FILE INFORMATION / STATISTICS / PROJECT INFO section containers ── */
818
+ /* Alternate warm tints for each section in the properties panel */
819
+ .colorful .bg-zinc-50.dark\:bg-zinc-900\/50 {
820
+ background-color: rgba(240, 220, 195, 0.30) !important;
821
+ }
822
+
823
+ /* ── LOCATION section: teal/green tint (geography = nature) ── */
824
+ .colorful [class*="border-emerald"] {
825
+ border-color: rgba(115, 218, 202, 0.25) !important;
826
+ }
827
+
828
+ /* ── Landing page / empty state — orange-tinted grid world ── */
829
+
830
+ /* Viewport container bg — only the wrapper div, NOT the canvas.
831
+ The canvas has data-viewport="main"; we exclude it so the
832
+ inline aurora gradient isn't overridden by !important. */
833
+ .colorful div[data-viewport=""]:not(:has(canvas)),
834
+ .colorful div[data-viewport]:not([data-viewport="main"]) {
835
+ background-color: rgba(245, 235, 220, 0.60) !important;
836
+ }
837
+
838
+ /* Orange grid only on the empty-state overlay (no model loaded).
839
+ This div only exists when the landing page renders. */
840
+ .colorful [data-viewport] > .absolute.z-10 {
841
+ background-image:
842
+ linear-gradient(rgba(255, 158, 100, 0.08) 1px, transparent 1px),
843
+ linear-gradient(90deg, rgba(255, 158, 100, 0.08) 1px, transparent 1px);
844
+ background-size: 32px 32px;
845
+ }
846
+
847
+ /* Main landing card: warm glass with orange border */
848
+ .colorful .border-zinc-300.dark\:border-\[\#3b4261\] {
849
+ border-color: rgba(255, 158, 100, 0.28) !important;
850
+ background: rgba(255, 248, 240, 0.65) !important;
851
+ backdrop-filter: blur(16px) !important;
852
+ -webkit-backdrop-filter: blur(16px) !important;
853
+ }
854
+
855
+ /* Logo background layer: warm tint */
856
+ .colorful .-rotate-3.border {
857
+ background-color: rgba(255, 225, 195, 0.30) !important;
858
+ border-color: rgba(255, 158, 100, 0.18) !important;
859
+ }
860
+
861
+ /* Feature grid cards (SELECT / FILTER / ANALYZE): warm glass */
862
+ .colorful .bg-zinc-100.dark\:bg-\[\#1f2335\] {
863
+ background-color: rgba(255, 230, 200, 0.35) !important;
864
+ border-color: rgba(255, 158, 100, 0.20) !important;
865
+ }
866
+
867
+ /* Feature card icon boxes */
868
+ .colorful .bg-zinc-100 .bg-white.border,
869
+ .colorful .bg-zinc-100 .dark\:bg-\[\#16161e\].border {
870
+ background-color: rgba(255, 245, 230, 0.55) !important;
871
+ border-color: rgba(255, 158, 100, 0.15) !important;
872
+ }
873
+
874
+ /* Recent files list */
875
+ .colorful .bg-zinc-50.dark\:bg-\[\#1f2335\] {
876
+ background-color: rgba(255, 235, 210, 0.30) !important;
877
+ border-color: rgba(255, 158, 100, 0.15) !important;
878
+ }
879
+
880
+ /* Shortcuts badge */
881
+ .colorful .bg-zinc-100.dark\:bg-\[\#1f2335\].border.border-zinc-300 {
882
+ background-color: rgba(255, 230, 200, 0.35) !important;
883
+ border-color: rgba(255, 158, 100, 0.20) !important;
884
+ }
885
+
886
+ /* "Open .ifc file" hover */
887
+ .colorful .hover\:border-primary:hover {
888
+ border-color: var(--cf-orange) !important;
889
+ }
890
+
891
+ .colorful .hover\:text-primary:hover {
892
+ color: var(--cf-orange) !important;
893
+ }
894
+
895
+ /* Empty state dashed borders: orange */
896
+ .colorful .border-dashed {
897
+ border-color: rgba(255, 158, 100, 0.35) !important;
898
+ }
899
+
900
+ /* Empty state icon boxes (NO MODEL / NO SELECTION) */
901
+ .colorful .border-dashed.border-2 {
902
+ border-color: rgba(255, 158, 100, 0.30) !important;
903
+ background-color: rgba(255, 235, 210, 0.25) !important;
904
+ }
905
+
906
+ /* ── Hierarchy SPATIAL/CLASS/TYPE toggle ── */
907
+ /* Outline buttons: lavender tint */
908
+ .colorful [data-variant="outline"] {
909
+ border-color: rgba(157, 124, 216, 0.22) !important;
910
+ }
911
+
912
+ .colorful [data-variant="outline"]:hover {
913
+ background-color: rgba(200, 195, 230, 0.22) !important;
914
+ }
915
+
916
+ /* ── Properties panel collapsible sections hover (right panel only) ── */
917
+ .colorful [id="right-panel"] .hover\:bg-zinc-50:hover,
918
+ .colorful [id="right-panel"] .hover\:bg-zinc-900:hover {
919
+ background-color: rgba(240, 220, 195, 0.25) !important;
920
+ }
921
+
922
+ /* ── Properties subsection backgrounds ── */
923
+ .colorful .bg-zinc-50\/50,
924
+ .colorful .dark\:bg-zinc-900\/50 {
925
+ background-color: rgba(240, 225, 205, 0.25) !important;
926
+ }
927
+
928
+ /* ── Tooltip: dark, sharp ── */
929
+ .colorful [role="tooltip"] {
930
+ background: rgba(26, 27, 46, 0.90) !important;
931
+ color: #e0e6f0 !important;
932
+ backdrop-filter: blur(8px) !important;
933
+ -webkit-backdrop-filter: blur(8px) !important;
934
+ }
935
+
415
936
  /* Fix Radix ScrollArea viewport inner div: by default it uses display:table
416
937
  which expands to content width, preventing text truncation in flex children.
417
938
  Force block layout so width constraints cascade correctly. */
@@ -513,6 +1034,35 @@ body {
513
1034
  cursor: not-allowed;
514
1035
  }
515
1036
 
1037
+ /* Annotation pin idle introduction — fires once on mount, then settles.
1038
+ Driven by a `prefers-reduced-motion` guard so accessibility users
1039
+ don't get hit by the pulse. */
1040
+ @keyframes annotation-pin-idle {
1041
+ 0% {
1042
+ transform: scale(0.7);
1043
+ box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.55);
1044
+ }
1045
+ 60% {
1046
+ transform: scale(1.08);
1047
+ box-shadow: 0 0 0 8px rgba(245, 158, 11, 0);
1048
+ }
1049
+ 100% {
1050
+ transform: scale(1);
1051
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35),
1052
+ 0 0 0 1px rgba(0, 0, 0, 0.15);
1053
+ }
1054
+ }
1055
+
1056
+ .annotation-pin-idle {
1057
+ animation: annotation-pin-idle 360ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
1058
+ }
1059
+
1060
+ @media (prefers-reduced-motion: reduce) {
1061
+ .annotation-pin-idle {
1062
+ animation: none;
1063
+ }
1064
+ }
1065
+
516
1066
  /* Loading skeleton animation */
517
1067
  @keyframes skeleton-pulse {
518
1068
  0%, 100% {
@@ -528,13 +1078,3 @@ body {
528
1078
  border-radius: 0.25rem;
529
1079
  animation: skeleton-pulse 2s ease-in-out infinite;
530
1080
  }
531
-
532
- /* Clerk overlays (billing/sidebar/modal) should always stack above app overlays. */
533
- .cl-portalRoot,
534
- .cl-modalBackdrop,
535
- .cl-modalContent,
536
- .cl-drawerRoot,
537
- .cl-drawerBackdrop,
538
- .cl-drawerContent {
539
- z-index: 100000 !important;
540
- }
@@ -26,10 +26,8 @@ interface ResolvedDesktopEntitlement {
26
26
 
27
27
  export function resolveDesktopEntitlement(options: ResolveDesktopEntitlementOptions): ResolvedDesktopEntitlement {
28
28
  // NOTE: `token`, `has`, `publicMetadata`, and `now` are accepted but
29
- // intentionally unused in this initial implementation. Full Clerk-based
30
- // entitlement resolution (plan checks via `has`, metadata-driven overrides,
31
- // token validation, and time-based expiry via `now`) will be wired up once
32
- // the Clerk integration is available in the desktop shell.
29
+ // intentionally unused in this implementation. They are kept for future
30
+ // extensibility if a desktop-specific entitlement provider is added.
33
31
  const { userId } = options;
34
32
  const entitlement: DesktopEntitlement = {
35
33
  ...getDefaultDesktopEntitlement(),
@@ -70,6 +70,7 @@ export async function createCesiumBridge(
70
70
  mapConversion: MapConversion,
71
71
  projectedCRS: ProjectedCRS,
72
72
  coordinateInfo?: CoordinateInfo,
73
+ lengthUnitScale = 1,
73
74
  ): Promise<CesiumBridge | null> {
74
75
  const projDef = await resolveProjection(projectedCRS);
75
76
  if (!projDef) return null;
@@ -98,9 +99,13 @@ export async function createCesiumBridge(
98
99
  const oIfcX = owx;
99
100
  const oIfcY = -owz;
100
101
  const oIfcZ = owy;
101
- const oEasting = mapConversion.eastings + hScale * (absc * oIfcX - ordi * oIfcY);
102
- const oNorthing = mapConversion.northings + hScale * (ordi * oIfcX + absc * oIfcY);
103
- const oHeight = mapConversion.orthogonalHeight + oIfcZ;
102
+ // Geometry coordinates (oIfcX/Y/Z) are already in metres (the geometry engine
103
+ // converts from the IFC file's native unit during extraction). MapConversion
104
+ // values use the unit from IfcProjectedCRS.MapUnit; fall back to project unit.
105
+ const mapScale = projectedCRS.mapUnitScale ?? lengthUnitScale;
106
+ const oEasting = mapConversion.eastings * mapScale + hScale * (absc * oIfcX - ordi * oIfcY);
107
+ const oNorthing = mapConversion.northings * mapScale + hScale * (ordi * oIfcX + absc * oIfcY);
108
+ const oHeight = mapConversion.orthogonalHeight * mapScale + oIfcZ;
104
109
 
105
110
  let originLon: number, originLat: number;
106
111
  try {
@@ -131,6 +136,8 @@ export async function createCesiumBridge(
131
136
  // So M = [hScale*absc, 0, hScale*ordi ]
132
137
  // [hScale*ordi, 0, -hScale*absc ]
133
138
  // [0, 1, 0 ]
139
+ // Viewer-space deltas are already in metres (geometry engine converts during
140
+ // extraction), so no lengthUnitScale needed here.
134
141
  const m00 = hScale * absc; // east from vx
135
142
  const m01 = 0; // east from vy
136
143
  const m02 = hScale * ordi; // east from vz
@@ -138,7 +145,7 @@ export async function createCesiumBridge(
138
145
  const m11 = 0; // north from vy
139
146
  const m12 = -hScale * absc; // north from vz
140
147
  const m20 = 0; // up from vx
141
- const m21 = 1; // up from vy
148
+ const m21 = 1; // up from vy (vertical = viewer Y, already metres)
142
149
  const m22 = 0; // up from vz
143
150
 
144
151
  // ── Cache for ECEF objects ──
@@ -288,9 +295,10 @@ export async function createCesiumBridge(
288
295
  const ifcX = wx;
289
296
  const ifcY = -wz;
290
297
  const ifcZ = wy;
291
- const easting = mapConversion.eastings + hScale * (absc * ifcX - ordi * ifcY);
292
- const northing = mapConversion.northings + hScale * (ordi * ifcX + absc * ifcY);
293
- const height = mapConversion.orthogonalHeight + ifcZ;
298
+ // Viewer coords (ifcX/Y/Z) are already in metres; only MapConversion values need scaling
299
+ const easting = mapConversion.eastings * mapScale + hScale * (absc * ifcX - ordi * ifcY);
300
+ const northing = mapConversion.northings * mapScale + hScale * (ordi * ifcX + absc * ifcY);
301
+ const height = mapConversion.orthogonalHeight * mapScale + ifcZ;
294
302
  try {
295
303
  const [lon, lat] = proj4(projDef!, 'WGS84', [easting, northing]);
296
304
  if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;