@dtour/viewer 0.1.0 → 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.
Files changed (63) hide show
  1. package/dist/Dtour.d.ts +5 -1
  2. package/dist/Dtour.d.ts.map +1 -1
  3. package/dist/DtourViewer.d.ts +4 -1
  4. package/dist/DtourViewer.d.ts.map +1 -1
  5. package/dist/components/AxisOverlay.d.ts +11 -1
  6. package/dist/components/AxisOverlay.d.ts.map +1 -1
  7. package/dist/components/CircularSlider.d.ts +21 -2
  8. package/dist/components/CircularSlider.d.ts.map +1 -1
  9. package/dist/components/DtourToolbar.d.ts +2 -1
  10. package/dist/components/DtourToolbar.d.ts.map +1 -1
  11. package/dist/components/Gallery.d.ts +3 -3
  12. package/dist/components/Gallery.d.ts.map +1 -1
  13. package/dist/components/RevertCameraButton.d.ts +6 -0
  14. package/dist/components/RevertCameraButton.d.ts.map +1 -0
  15. package/dist/components/ui/checkbox.d.ts +6 -0
  16. package/dist/components/ui/checkbox.d.ts.map +1 -0
  17. package/dist/hooks/usePlayback.d.ts +7 -5
  18. package/dist/hooks/usePlayback.d.ts.map +1 -1
  19. package/dist/hooks/useScatter.d.ts.map +1 -1
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/layout/gallery-positions.d.ts +3 -1
  23. package/dist/layout/gallery-positions.d.ts.map +1 -1
  24. package/dist/layout/selector-size.d.ts +4 -2
  25. package/dist/layout/selector-size.d.ts.map +1 -1
  26. package/dist/lib/arcball.d.ts +21 -0
  27. package/dist/lib/arcball.d.ts.map +1 -0
  28. package/dist/lib/position-remap.d.ts +16 -0
  29. package/dist/lib/position-remap.d.ts.map +1 -0
  30. package/dist/lib/throttle-debounce.d.ts +28 -0
  31. package/dist/lib/throttle-debounce.d.ts.map +1 -0
  32. package/dist/radial-chart/RadialChart.d.ts +5 -1
  33. package/dist/radial-chart/RadialChart.d.ts.map +1 -1
  34. package/dist/spec.d.ts +32 -0
  35. package/dist/spec.d.ts.map +1 -1
  36. package/dist/state/atoms.d.ts +67 -0
  37. package/dist/state/atoms.d.ts.map +1 -1
  38. package/dist/state/spec-sync.d.ts +2 -0
  39. package/dist/state/spec-sync.d.ts.map +1 -1
  40. package/dist/viewer.css +1 -1
  41. package/dist/viewer.js +11620 -10118
  42. package/package.json +6 -1
  43. package/src/Dtour.tsx +82 -9
  44. package/src/DtourViewer.tsx +480 -100
  45. package/src/components/AxisOverlay.tsx +332 -182
  46. package/src/components/CircularSlider.tsx +363 -174
  47. package/src/components/DtourToolbar.tsx +121 -10
  48. package/src/components/Gallery.tsx +197 -39
  49. package/src/components/RevertCameraButton.tsx +39 -0
  50. package/src/components/ui/checkbox.tsx +32 -0
  51. package/src/hooks/usePlayback.ts +18 -44
  52. package/src/hooks/useScatter.ts +21 -5
  53. package/src/index.ts +16 -3
  54. package/src/layout/gallery-positions.ts +15 -4
  55. package/src/layout/selector-size.ts +24 -10
  56. package/src/lib/arcball.ts +119 -0
  57. package/src/lib/position-remap.ts +51 -0
  58. package/src/lib/throttle-debounce.ts +79 -0
  59. package/src/radial-chart/RadialChart.tsx +45 -6
  60. package/src/spec.ts +143 -0
  61. package/src/state/atoms.ts +65 -0
  62. package/src/state/spec-sync.ts +15 -0
  63. package/src/styles.css +16 -16
@@ -1,7 +1,10 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
+ import { tourToVisual } from '../lib/position-remap.ts';
2
3
  import { arcPath, keyframeAngle, rectBarPath } from './arc-path.ts';
3
4
  import type { ParsedTrack } from './types.ts';
4
5
 
6
+ const START_DEG = -135;
7
+
5
8
  export type RadialChartProps = {
6
9
  tracks: ParsedTrack[];
7
10
  keyframeCount: number;
@@ -11,6 +14,10 @@ export type RadialChartProps = {
11
14
  size: number;
12
15
  /** Inner radius = selector ring radius (selectorSize * 0.4). */
13
16
  innerRadius: number;
17
+ /** Cumulative arc-lengths for geodesic tick positioning. */
18
+ arcLengths?: Float32Array | null;
19
+ /** Slider spacing mode. Default 'equal'. */
20
+ spacingMode?: 'equal' | 'geodesic';
14
21
  };
15
22
 
16
23
  const TRACK_GAP = 2;
@@ -25,6 +32,8 @@ export const RadialChart = ({
25
32
  position,
26
33
  size,
27
34
  innerRadius,
35
+ arcLengths,
36
+ spacingMode = 'equal',
28
37
  }: RadialChartProps) => {
29
38
  const center = size / 2;
30
39
  const [hover, setHover] = useState<HoverInfo | null>(null);
@@ -51,8 +60,37 @@ export const RadialChart = ({
51
60
  });
52
61
  }, [tracks, innerRadius, stacked]);
53
62
 
63
+ // Compute angle for keyframe index, respecting spacing mode.
64
+ // In geodesic mode, bars sit at arc-length positions; in equal mode, uniform.
65
+ const getAngle = useCallback(
66
+ (index: number): number => {
67
+ if (spacingMode === 'geodesic' && arcLengths && index < arcLengths.length) {
68
+ return ((arcLengths[index]! * 360 + START_DEG) * Math.PI) / 180;
69
+ }
70
+ return keyframeAngle(index, keyframeCount);
71
+ },
72
+ [spacingMode, arcLengths, keyframeCount],
73
+ );
74
+
75
+ // Angular span for a segment from keyframe i to i+1
76
+ const getSegmentSpan = useCallback(
77
+ (index: number): number => {
78
+ if (spacingMode === 'geodesic' && arcLengths && arcLengths.length > 1) {
79
+ const n = arcLengths.length - 1;
80
+ const start = arcLengths[index]!;
81
+ const end = arcLengths[(index + 1) % (n + 1)]!;
82
+ const span = end > start ? end - start : 1 - start + end;
83
+ return span * 2 * Math.PI;
84
+ }
85
+ return (2 * Math.PI) / keyframeCount;
86
+ },
87
+ [spacingMode, arcLengths, keyframeCount],
88
+ );
89
+
54
90
  // Flanking keyframes based on current position
55
- const fractionalIndex = position * keyframeCount;
91
+ const visualPos =
92
+ spacingMode === 'equal' && arcLengths ? tourToVisual(position, arcLengths) : position;
93
+ const fractionalIndex = visualPos * keyframeCount;
56
94
  const leftKf = Math.floor(fractionalIndex) % keyframeCount;
57
95
  const rightKf = (leftKf + 1) % keyframeCount;
58
96
 
@@ -85,7 +123,7 @@ export const RadialChart = ({
85
123
  const barWidthPx = (tracks[0]?.barWidth as number) ?? 0;
86
124
 
87
125
  for (let kfIdx = 0; kfIdx < keyframeCount; kfIdx++) {
88
- const centerAngle = keyframeAngle(kfIdx, keyframeCount);
126
+ const centerAngle = getAngle(kfIdx);
89
127
 
90
128
  let stackBase = baseR;
91
129
  for (let trackIdx = 0; trackIdx < tracks.length; trackIdx++) {
@@ -115,7 +153,7 @@ export const RadialChart = ({
115
153
  }
116
154
  }
117
155
  return bars;
118
- }, [stacked, tracks, keyframeCount, baseR, center]);
156
+ }, [stacked, tracks, keyframeCount, baseR, center, getAngle]);
119
157
 
120
158
  return (
121
159
  <div className="relative" style={{ width: size, height: size }}>
@@ -140,9 +178,10 @@ export const RadialChart = ({
140
178
  {track.normalizedValues.map((normVal, kfIdx) => {
141
179
  if (kfIdx >= keyframeCount) return null;
142
180
 
143
- const centerAngle = keyframeAngle(kfIdx, keyframeCount);
144
- const angleStart = centerAngle - segmentAngle / 2 + BAR_PAD_RAD;
145
- const angleEnd = centerAngle + segmentAngle / 2 - BAR_PAD_RAD;
181
+ const centerAngle = getAngle(kfIdx);
182
+ const halfSpan = getSegmentSpan(kfIdx) / 2;
183
+ const angleStart = centerAngle - halfSpan + BAR_PAD_RAD;
184
+ const angleEnd = centerAngle + halfSpan - BAR_PAD_RAD;
146
185
 
147
186
  const barOuter = rInner + normVal * track.height;
148
187
  const rawValue = track.rawValues[kfIdx] as number;
package/src/spec.ts CHANGED
@@ -22,11 +22,149 @@ export const dtourSpecSchema = z.object({
22
22
  cameraZoom: z.number().positive().optional(),
23
23
  viewMode: z.enum(['guided', 'manual', 'grand']).optional(),
24
24
  showLegend: z.boolean().optional(),
25
+ showAxes: z.boolean().optional(),
26
+ showFrameNumbers: z.boolean().optional(),
27
+ showFrameLoadings: z.boolean().optional(),
28
+ showTourDescription: z.boolean().optional(),
29
+ sliderSpacing: z.enum(['equal', 'geodesic']).optional(),
25
30
  themeMode: z.enum(['light', 'dark', 'system']).optional(),
26
31
  });
27
32
 
28
33
  export type DtourSpec = z.infer<typeof dtourSpecSchema>;
29
34
 
35
+ /** Per-frame top-2 feature correlations: [featureName, pearsonR] pairs. */
36
+ export type FrameLoading = [string, number];
37
+
38
+ /** Parsed contents of the Parquet "dtour" key_value_metadata entry. */
39
+ export type EmbeddedConfig = {
40
+ spec: DtourSpec;
41
+ colorMap?: Record<string, string>;
42
+ tour?: {
43
+ nDims: number;
44
+ nViews: number;
45
+ views: Float32Array[];
46
+ tourMode?: 'signed' | 'discriminative' | null;
47
+ frameLoadings?: FrameLoading[][];
48
+ /** Human-readable description of the tour (shown in description sub-bar). */
49
+ tourDescription?: string;
50
+ /** Template for per-frame tooltip, with {dim1}, {dim2}, {relation} placeholders. */
51
+ tourFrameDescription?: string;
52
+ };
53
+ };
54
+
55
+ const SPEC_SHAPE_KEYS = Object.keys(dtourSpecSchema.shape) as (keyof DtourSpec)[];
56
+
57
+ /**
58
+ * Parse the raw JSON "dtour" value from Parquet key_value_metadata.
59
+ * Returns null if the string is falsy or unparseable.
60
+ * Invalid spec fields are silently dropped.
61
+ */
62
+ export function parseEmbeddedConfig(raw: string | undefined): EmbeddedConfig | null {
63
+ if (!raw) return null;
64
+
65
+ let obj: Record<string, unknown>;
66
+ try {
67
+ obj = JSON.parse(raw);
68
+ } catch {
69
+ return null;
70
+ }
71
+ if (typeof obj !== 'object' || obj === null) return null;
72
+
73
+ // Validate each spec field individually — invalid fields are dropped
74
+ // without affecting valid ones.
75
+ const spec: Record<string, unknown> = {};
76
+ for (const key of SPEC_SHAPE_KEYS) {
77
+ if (!(key in obj)) continue;
78
+ const fieldSchema = dtourSpecSchema.shape[key];
79
+ const result = fieldSchema.safeParse(obj[key]);
80
+ if (result.success) spec[key] = result.data;
81
+ }
82
+
83
+ // Extract colorMap (label → hex string)
84
+ let colorMap: Record<string, string> | undefined;
85
+ if (obj.colorMap && typeof obj.colorMap === 'object' && !Array.isArray(obj.colorMap)) {
86
+ const cm = obj.colorMap as Record<string, unknown>;
87
+ const valid: Record<string, string> = {};
88
+ let hasEntries = false;
89
+ for (const [k, v] of Object.entries(cm)) {
90
+ if (typeof v === 'string') {
91
+ valid[k] = v;
92
+ hasEntries = true;
93
+ }
94
+ }
95
+ if (hasEntries) colorMap = valid;
96
+ }
97
+
98
+ // Extract tour views (base64 float32 column-major)
99
+ let tour: EmbeddedConfig['tour'] | undefined;
100
+ if (obj.tour && typeof obj.tour === 'object') {
101
+ const t = obj.tour as Record<string, unknown>;
102
+ const nDims = typeof t.nDims === 'number' ? t.nDims : 0;
103
+ const nViews = typeof t.nViews === 'number' ? t.nViews : 0;
104
+ const viewsB64 = typeof t.views === 'string' ? t.views : '';
105
+ if (nDims >= 2 && nViews >= 2 && viewsB64) {
106
+ try {
107
+ const binary = atob(viewsB64);
108
+ const bytes = new Uint8Array(binary.length);
109
+ for (let i = 0; i < binary.length; i++) {
110
+ bytes[i] = binary.charCodeAt(i);
111
+ }
112
+ const floats = new Float32Array(bytes.buffer);
113
+ const stride = nDims * 2;
114
+ if (floats.length === nViews * stride) {
115
+ const views: Float32Array[] = [];
116
+ for (let v = 0; v < nViews; v++) {
117
+ views.push(floats.slice(v * stride, (v + 1) * stride));
118
+ }
119
+ tour = { nDims, nViews, views };
120
+
121
+ // Parse tourMode
122
+ if (t.tourMode === 'signed' || t.tourMode === 'discriminative') {
123
+ tour.tourMode = t.tourMode;
124
+ }
125
+
126
+ // Parse tourDescription and tourFrameDescription
127
+ if (typeof t.tourDescription === 'string') {
128
+ tour.tourDescription = t.tourDescription;
129
+ }
130
+ if (typeof t.tourFrameDescription === 'string') {
131
+ tour.tourFrameDescription = t.tourFrameDescription;
132
+ }
133
+
134
+ // Parse frameLoadings: array of [[name, coeff], [name, coeff]] per view
135
+ if (Array.isArray(t.frameLoadings)) {
136
+ const fl: FrameLoading[][] = [];
137
+ let valid = true;
138
+ for (const frame of t.frameLoadings as unknown[][]) {
139
+ if (!Array.isArray(frame)) {
140
+ valid = false;
141
+ break;
142
+ }
143
+ const pairs: FrameLoading[] = [];
144
+ for (const pair of frame) {
145
+ if (
146
+ Array.isArray(pair) &&
147
+ pair.length === 2 &&
148
+ typeof pair[0] === 'string' &&
149
+ typeof pair[1] === 'number'
150
+ ) {
151
+ pairs.push([pair[0] as string, pair[1] as number]);
152
+ }
153
+ }
154
+ fl.push(pairs);
155
+ }
156
+ if (valid && fl.length > 0) tour.frameLoadings = fl;
157
+ }
158
+ }
159
+ } catch {
160
+ // Invalid base64 — skip tour
161
+ }
162
+ }
163
+ }
164
+
165
+ return { spec: spec as DtourSpec, colorMap, tour };
166
+ }
167
+
30
168
  export const DTOUR_DEFAULTS: Required<DtourSpec> = {
31
169
  tourBy: 'dimensions',
32
170
  tourPosition: 0,
@@ -44,5 +182,10 @@ export const DTOUR_DEFAULTS: Required<DtourSpec> = {
44
182
  cameraZoom: 1 / 1.5,
45
183
  viewMode: 'guided',
46
184
  showLegend: true,
185
+ showAxes: false,
186
+ showFrameNumbers: false,
187
+ showFrameLoadings: true,
188
+ showTourDescription: false,
189
+ sliderSpacing: 'equal',
47
190
  themeMode: 'dark',
48
191
  };
@@ -1,5 +1,6 @@
1
1
  import type { Metadata } from '@dtour/scatter';
2
2
  import { atom } from 'jotai';
3
+ import type { EmbeddedConfig, FrameLoading } from '../spec.ts';
3
4
 
4
5
  // ---------------------------------------------------------------------------
5
6
  // Tour state — controls position and playback along the tour path
@@ -13,6 +14,12 @@ export const tourPlayingAtom = atom(false);
13
14
  export const tourSpeedAtom = atom(1);
14
15
  export const tourDirectionAtom = atom<1 | -1>(1);
15
16
 
17
+ /** Slider spacing mode: 'equal' = uniform tick spacing, 'geodesic' = arc-length proportional. */
18
+ export const sliderSpacingAtom = atom<'equal' | 'geodesic'>('equal');
19
+
20
+ /** Cumulative arc-lengths for the current tour bases. null when no tour is loaded. */
21
+ export const arcLengthsAtom = atom<Float32Array | null>(null);
22
+
16
23
  // ---------------------------------------------------------------------------
17
24
  // View state — controls preview layout and keyframe selection
18
25
  // ---------------------------------------------------------------------------
@@ -22,6 +29,34 @@ export const previewScaleAtom = atom<1 | 0.75 | 0.5>(1);
22
29
  export const previewPaddingAtom = atom(12);
23
30
  export const selectedKeyframeAtom = atom<number | null>(null);
24
31
 
32
+ /** Which gallery preview is currently hovered (index), or null. */
33
+ export const hoveredKeyframeAtom = atom<number | null>(null);
34
+
35
+ /** Preview center positions relative to the container center, plus preview size. */
36
+ export const previewCentersAtom = atom<{ x: number; y: number; size: number }[]>([]);
37
+
38
+ /** Derived: nearest keyframe to the current tour position. */
39
+ export const currentKeyframeAtom = atom((get) => {
40
+ const position = get(tourPositionAtom);
41
+ const arcLengths = get(arcLengthsAtom);
42
+ const previewCount = get(previewCountAtom);
43
+ if (!arcLengths || arcLengths.length < 2) {
44
+ return Math.round(position * previewCount) % previewCount;
45
+ }
46
+ const n = arcLengths.length - 1;
47
+ let best = 0;
48
+ let bestDist = 1;
49
+ for (let i = 0; i < n; i++) {
50
+ let dist = Math.abs(position - arcLengths[i]!);
51
+ dist = Math.min(dist, 1 - dist);
52
+ if (dist < bestDist) {
53
+ bestDist = dist;
54
+ best = i;
55
+ }
56
+ }
57
+ return best;
58
+ });
59
+
25
60
  // ---------------------------------------------------------------------------
26
61
  // Point style — visual appearance of scatter points
27
62
  // ---------------------------------------------------------------------------
@@ -67,6 +102,9 @@ export const guidedSuspendedAtom = atom(false);
67
102
  /** Target mode after grand ease-out completes. null = not exiting. */
68
103
  export const grandExitTargetAtom = atom<'guided' | 'manual' | null>(null);
69
104
 
105
+ /** True when the 3D camera is rotated away from front-on (manual mode only). */
106
+ export const is3dRotatedAtom = atom(false);
107
+
70
108
  /**
71
109
  * Tracks the currently-displayed projection basis (p×2 column-major).
72
110
  * Updated by tour interpolation, manual axis dragging, and zen animation.
@@ -99,6 +137,9 @@ export const canvasSizeAtom = atom({ width: 0, height: 0 });
99
137
 
100
138
  export const metadataAtom = atom<Metadata | null>(null);
101
139
 
140
+ /** Parsed embedded config from Parquet key_value_metadata. Reset on each data load. */
141
+ export const embeddedConfigAtom = atom<EmbeddedConfig | null>(null);
142
+
102
143
  // ---------------------------------------------------------------------------
103
144
  // Column visibility — which numeric dimensions participate in the tour
104
145
  // ---------------------------------------------------------------------------
@@ -129,6 +170,30 @@ export const activeIndicesAtom = atom<number[]>((get) => {
129
170
  /** User preference for showing the legend panel. */
130
171
  export const showLegendAtom = atom(true);
131
172
 
173
+ /** User preference for showing axis biplot in guided mode. */
174
+ export const showAxesAtom = atom(false);
175
+
176
+ /** User preference for showing frame numbers on preview thumbnails. */
177
+ export const showFrameNumbersAtom = atom(false);
178
+
179
+ /** User preference for showing feature loading pills on preview thumbnails. */
180
+ export const showFrameLoadingsAtom = atom(true);
181
+
182
+ /** User preference for showing the tour description sub-bar. */
183
+ export const showTourDescriptionAtom = atom(false);
184
+
185
+ /** Per-frame top-2 feature correlations from embedded tour config. */
186
+ export const frameLoadingsAtom = atom<FrameLoading[][] | null>(null);
187
+
188
+ /** Tour mode from embedded config: null (vanilla), "signed", or "discriminative". */
189
+ export const tourModeAtom = atom<'signed' | 'discriminative' | null>(null);
190
+
191
+ /** Tour description string from embedded config (shown in description sub-bar). */
192
+ export const tourDescriptionAtom = atom<string | null>(null);
193
+
194
+ /** Per-frame tooltip template from embedded config, with {dim1}, {dim2}, {relation} placeholders. */
195
+ export const tourFrameDescriptionAtom = atom<string | null>(null);
196
+
132
197
  /**
133
198
  * Derived: legend is visible only when showLegend is true, metadata is loaded,
134
199
  * AND points are colored by a known data column (numeric or categorical).
@@ -12,7 +12,12 @@ import {
12
12
  previewCountAtom,
13
13
  previewPaddingAtom,
14
14
  previewScaleAtom,
15
+ showAxesAtom,
16
+ showFrameLoadingsAtom,
17
+ showFrameNumbersAtom,
15
18
  showLegendAtom,
19
+ showTourDescriptionAtom,
20
+ sliderSpacingAtom,
16
21
  themeModeAtom,
17
22
  tourByAtom,
18
23
  tourDirectionAtom,
@@ -61,6 +66,11 @@ const SPEC_ATOM_MAP = {
61
66
  cameraZoom: entry(cameraZoomAtom),
62
67
  viewMode: entry(viewModeAtom),
63
68
  showLegend: entry(showLegendAtom),
69
+ showAxes: entry(showAxesAtom),
70
+ showFrameNumbers: entry(showFrameNumbersAtom),
71
+ showFrameLoadings: entry(showFrameLoadingsAtom),
72
+ showTourDescription: entry(showTourDescriptionAtom),
73
+ sliderSpacing: entry(sliderSpacingAtom),
64
74
  themeMode: entry(themeModeAtom),
65
75
  } as const;
66
76
 
@@ -105,6 +115,11 @@ export function initStoreFromSpec(
105
115
  spec: DtourSpec | undefined,
106
116
  ): void {
107
117
  if (!spec) return;
118
+ applySpecToStore(store, spec);
119
+ }
120
+
121
+ /** Write spec values into the jotai store. Skips undefined fields. */
122
+ export function applySpecToStore(store: ReturnType<typeof useStore>, spec: DtourSpec): void {
108
123
  for (const key of SPEC_KEYS) {
109
124
  const value = spec[key];
110
125
  if (value !== undefined) {
package/src/styles.css CHANGED
@@ -3,27 +3,27 @@
3
3
  @import "tw-animate-css";
4
4
 
5
5
  @theme {
6
- --color-dtour-bg: #000000;
7
- --color-dtour-surface: #222222;
8
- --color-dtour-border: #333333;
9
- --color-dtour-text: #bbbbbb;
10
- --color-dtour-text-muted: #888888;
11
- --color-dtour-accent: #4080e8;
12
- --color-dtour-accent-hover: #5090f0;
13
- --color-dtour-highlight: #ffffff;
6
+ --color-dtour-bg: oklch(0% 0 0);
7
+ --color-dtour-surface: oklch(18.66% 0 0);
8
+ --color-dtour-border: oklch(27.06% 0 0);
9
+ --color-dtour-text: oklch(79.91% 0 0);
10
+ --color-dtour-text-muted: oklch(61.66% 0 0);
11
+ --color-dtour-accent: oklch(61.13% 0.1707 259.68);
12
+ --color-dtour-accent-hover: oklch(65.65% 0.1581 258.37);
13
+ --color-dtour-highlight: oklch(100% 0 0);
14
14
  }
15
15
 
16
16
  /* --- Light theme overrides --- */
17
17
 
18
18
  :where(.dtour-light) {
19
- --color-dtour-bg: #ffffff;
20
- --color-dtour-surface: #f0f0f0;
21
- --color-dtour-border: #d0d0d0;
22
- --color-dtour-text: #333333;
23
- --color-dtour-text-muted: #777777;
24
- --color-dtour-accent: #2060c0;
25
- --color-dtour-accent-hover: #1850a8;
26
- --color-dtour-highlight: #000000;
19
+ --color-dtour-bg: oklch(100% 0 0);
20
+ --color-dtour-surface: oklch(95.51% 0 0);
21
+ --color-dtour-border: oklch(85.76% 0 0);
22
+ --color-dtour-text: oklch(32.11% 0 0);
23
+ --color-dtour-text-muted: oklch(56.93% 0 0);
24
+ --color-dtour-accent: oklch(50.48% 0.164 258.95);
25
+ --color-dtour-accent-hover: oklch(44.94% 0.1531 259.62);
26
+ --color-dtour-highlight: oklch(0% 0 0);
27
27
  }
28
28
 
29
29
  /* --- Animation easing utility for tw-animate-css --- */