@brett_lamy/docstream 1.6.1 → 1.8.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 (49) hide show
  1. package/README.md +31 -4
  2. package/dist/demo/DemoViewer.d.ts +8 -0
  3. package/dist/demo/DemoViewer.d.ts.map +1 -1
  4. package/dist/demo/DemoViewer.js +74 -1
  5. package/dist/demo/DemoViewer.js.map +1 -1
  6. package/dist/demo/PathBreadcrumb.d.ts +54 -0
  7. package/dist/demo/PathBreadcrumb.d.ts.map +1 -0
  8. package/dist/demo/PathBreadcrumb.js +245 -0
  9. package/dist/demo/PathBreadcrumb.js.map +1 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/replay/ReplayPlayer.d.ts +64 -0
  15. package/dist/replay/ReplayPlayer.d.ts.map +1 -0
  16. package/dist/replay/ReplayPlayer.js +211 -0
  17. package/dist/replay/ReplayPlayer.js.map +1 -0
  18. package/dist/replay/ReplayPreview.d.ts +24 -12
  19. package/dist/replay/ReplayPreview.d.ts.map +1 -1
  20. package/dist/replay/ReplayPreview.js +37 -32
  21. package/dist/replay/ReplayPreview.js.map +1 -1
  22. package/dist/replay/ReplayStageImpl.js +1 -1
  23. package/dist/replay/ReplayStageImpl.js.map +1 -1
  24. package/dist/replay/demo/index.d.ts +3 -0
  25. package/dist/replay/demo/index.d.ts.map +1 -0
  26. package/dist/replay/demo/index.js +66 -0
  27. package/dist/replay/demo/index.js.map +1 -0
  28. package/dist/replay/index.d.ts +2 -2
  29. package/dist/replay/index.d.ts.map +1 -1
  30. package/dist/replay/index.js +1 -1
  31. package/dist/replay/index.js.map +1 -1
  32. package/dist/replay/types.js +1 -1
  33. package/dist/styles.css +791 -79
  34. package/package.json +7 -1
  35. package/src/demo/DemoViewer.tsx +94 -2
  36. package/src/demo/PathBreadcrumb.tsx +367 -0
  37. package/src/index.ts +2 -1
  38. package/src/replay/ReplayPlayer.tsx +430 -0
  39. package/src/replay/ReplayPreview.tsx +111 -85
  40. package/src/replay/ReplayStageImpl.tsx +1 -1
  41. package/src/replay/demo/index.ts +67 -0
  42. package/src/replay/index.ts +6 -1
  43. package/src/replay/types.ts +1 -1
  44. package/src/styles.css +791 -79
  45. package/dist/replay/NativeReplay.d.ts +0 -9
  46. package/dist/replay/NativeReplay.d.ts.map +0 -1
  47. package/dist/replay/NativeReplay.js +0 -175
  48. package/dist/replay/NativeReplay.js.map +0 -1
  49. package/src/replay/NativeReplay.tsx +0 -240
package/src/styles.css CHANGED
@@ -2064,13 +2064,76 @@
2064
2064
  .docs-demo-files {
2065
2065
  flex-shrink: 0;
2066
2066
  box-sizing: border-box;
2067
- width: 188px;
2067
+ /* --docs-demo-files-w is set (px) once the reader drags the handle; 188px until then. */
2068
+ width: var(--docs-demo-files-w, 188px);
2068
2069
  padding: 8px;
2069
2070
  overflow-y: auto;
2070
2071
  border-right: 1px solid var(--gb-border);
2071
2072
  background: var(--gb-muted);
2072
2073
  }
2073
2074
 
2075
+ /* The drag handle between the file list and the source: zero layout width, with a wider hit area
2076
+ and the same grip as the preview handle straddling the divider. */
2077
+ .docs-demo-files-handle {
2078
+ position: relative;
2079
+ z-index: 2;
2080
+ flex: 0 0 0;
2081
+ width: 0;
2082
+ cursor: col-resize;
2083
+ touch-action: none;
2084
+ }
2085
+
2086
+ .docs-demo-files-handle::before {
2087
+ position: absolute;
2088
+ top: 0;
2089
+ bottom: 0;
2090
+ left: -5px;
2091
+ width: 10px;
2092
+ content: "";
2093
+ }
2094
+
2095
+ .docs-demo-files-handle::after {
2096
+ position: absolute;
2097
+ top: 50%;
2098
+ left: -2.5px;
2099
+ width: 5px;
2100
+ height: 40px;
2101
+ margin-top: -20px;
2102
+ border-radius: 3px;
2103
+ background: var(--gb-stage-dot);
2104
+ content: "";
2105
+ opacity: 0;
2106
+ transition: opacity 150ms ease, background 150ms ease, height 200ms ease, margin 200ms ease;
2107
+ }
2108
+
2109
+ .docs-demo-code:hover .docs-demo-files-handle::after {
2110
+ opacity: 1;
2111
+ }
2112
+
2113
+ .docs-demo-files-handle:hover::after,
2114
+ .docs-demo-files-handle:focus-visible::after,
2115
+ .docs-demo-files-handle[data-dragging]::after {
2116
+ height: 56px;
2117
+ margin-top: -28px;
2118
+ background: var(--gb-primary);
2119
+ opacity: 1;
2120
+ }
2121
+
2122
+ .docs-demo-files-handle:focus-visible {
2123
+ outline: none;
2124
+ }
2125
+
2126
+ .docs-demo-files-handle:focus-visible::before {
2127
+ outline: 2px solid var(--gb-primary);
2128
+ outline-offset: -1px;
2129
+ border-radius: 2px;
2130
+ }
2131
+
2132
+ .docs-demo-code[data-dragging] {
2133
+ cursor: col-resize;
2134
+ user-select: none;
2135
+ }
2136
+
2074
2137
  .docs-demo-files-h {
2075
2138
  padding: 6px 8px 8px;
2076
2139
  color: var(--gb-muted-foreground);
@@ -2143,18 +2206,174 @@
2143
2206
  align-items: center;
2144
2207
  justify-content: space-between;
2145
2208
  gap: 8px;
2146
- padding: 5px 6px 5px 14px;
2209
+ padding: 5px 6px 5px 8px;
2147
2210
  border-bottom: 1px solid var(--gb-border);
2148
2211
  color: var(--gb-muted-foreground);
2149
2212
  font: 12px/1 var(--docs-demo-mono);
2150
2213
  }
2151
2214
 
2152
- .docs-demo-source-path {
2215
+ /* Breadcrumb path: one button per segment. The list is clipped to the header; the fit logic in
2216
+ PathBreadcrumb collapses the middle to a "…" before that matters. */
2217
+ .docs-demo-crumbs {
2218
+ position: relative;
2219
+ flex: 1;
2220
+ min-width: 0;
2221
+ overflow: hidden;
2222
+ }
2223
+
2224
+ .docs-demo-crumbs > .docs-demo-crumbs-list {
2225
+ display: flex;
2226
+ align-items: center;
2227
+ margin: 0;
2228
+ padding: 0;
2229
+ list-style: none;
2230
+ white-space: nowrap;
2231
+ }
2232
+
2233
+ .docs-demo-crumbs > .docs-demo-crumbs-measure {
2234
+ position: absolute;
2235
+ top: 0;
2236
+ left: 0;
2237
+ height: 0;
2238
+ visibility: hidden;
2239
+ pointer-events: none;
2240
+ }
2241
+
2242
+ .docs-demo-crumbs-list > .docs-demo-crumb-item {
2243
+ display: flex;
2244
+ flex: 0 0 auto;
2245
+ align-items: center;
2246
+ min-width: 0;
2247
+ margin: 0;
2248
+ padding: 0;
2249
+ }
2250
+
2251
+ .docs-demo-crumbs-list > .docs-demo-crumb-item[data-last] {
2252
+ flex: 0 1 auto;
2253
+ }
2254
+
2255
+ .docs-demo-crumb-sep {
2256
+ flex-shrink: 0;
2257
+ color: var(--gb-muted-foreground);
2258
+ opacity: 0.7;
2259
+ }
2260
+
2261
+ .docs-demo-crumb {
2262
+ display: inline-flex;
2263
+ align-items: center;
2264
+ gap: 5px;
2265
+ min-width: 0;
2266
+ box-sizing: border-box;
2267
+ height: 24px;
2268
+ padding: 0 6px;
2269
+ border: 0;
2270
+ border-radius: 6px;
2271
+ background: none;
2272
+ color: var(--gb-muted-foreground);
2273
+ font: inherit;
2274
+ cursor: pointer;
2275
+ transition: background 150ms ease, color 150ms ease;
2276
+ }
2277
+
2278
+ .docs-demo-crumb svg {
2279
+ flex-shrink: 0;
2280
+ }
2281
+
2282
+ .docs-demo-crumb-text {
2283
+ overflow: hidden;
2284
+ text-overflow: ellipsis;
2285
+ white-space: nowrap;
2286
+ }
2287
+
2288
+ .docs-demo-crumb:hover,
2289
+ .docs-demo-crumb[aria-expanded="true"] {
2290
+ background: var(--gb-hover);
2291
+ color: var(--gb-panel-foreground);
2292
+ }
2293
+
2294
+ .docs-demo-crumb[data-current] {
2295
+ color: var(--gb-panel-foreground);
2296
+ font-weight: 600;
2297
+ }
2298
+
2299
+ .docs-demo-crumb[data-current] svg {
2300
+ color: var(--gb-primary);
2301
+ }
2302
+
2303
+ .docs-demo-crumb:focus-visible {
2304
+ outline: 2px solid var(--gb-primary);
2305
+ outline-offset: -2px;
2306
+ }
2307
+
2308
+ /* The sibling / hidden-segment menu. Portalled next to the docs root (so it isn't clipped by the
2309
+ viewer) and fixed under its trigger; --crumb-x / --crumb-y are set from the trigger's rect. */
2310
+ .docs-demo-crumb-menu {
2311
+ position: fixed;
2312
+ top: var(--crumb-y, 0);
2313
+ left: var(--crumb-x, 0);
2314
+ z-index: 60;
2315
+ box-sizing: border-box;
2316
+ min-width: 160px;
2317
+ max-width: min(320px, calc(100vw - 16px));
2318
+ max-height: min(320px, calc(100vh - 16px));
2319
+ padding: 4px;
2320
+ overflow-y: auto;
2321
+ border: 1px solid var(--gb-border);
2322
+ border-radius: 10px;
2323
+ background: var(--gb-panel);
2324
+ box-shadow: var(--gb-menu-shadow);
2325
+ color: var(--gb-panel-foreground);
2326
+ font: 12.5px/1.3 var(--docs-demo-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
2327
+ }
2328
+
2329
+ .docs-demo-crumb-option {
2330
+ display: flex;
2331
+ align-items: center;
2332
+ gap: 7px;
2333
+ box-sizing: border-box;
2334
+ width: 100%;
2335
+ padding: 6px 8px;
2336
+ border: 0;
2337
+ border-radius: 6px;
2338
+ background: none;
2339
+ color: inherit;
2340
+ font: inherit;
2341
+ text-align: left;
2342
+ cursor: pointer;
2343
+ }
2344
+
2345
+ .docs-demo-crumb-option svg {
2346
+ flex-shrink: 0;
2347
+ color: var(--gb-muted-foreground);
2348
+ }
2349
+
2350
+ .docs-demo-crumb-option:hover,
2351
+ .docs-demo-crumb-option:focus-visible {
2352
+ outline: none;
2353
+ background: var(--gb-hover);
2354
+ }
2355
+
2356
+ .docs-demo-crumb-option[data-current] {
2357
+ color: var(--gb-primary);
2358
+ font-weight: 600;
2359
+ }
2360
+
2361
+ .docs-demo-crumb-option[data-current] svg {
2362
+ color: var(--gb-primary);
2363
+ }
2364
+
2365
+ .docs-demo-crumb-option-text {
2366
+ flex: 1;
2367
+ min-width: 0;
2153
2368
  overflow: hidden;
2154
2369
  text-overflow: ellipsis;
2155
2370
  white-space: nowrap;
2156
2371
  }
2157
2372
 
2373
+ .docs-demo-crumb-option .docs-demo-crumb-check {
2374
+ margin-left: auto;
2375
+ }
2376
+
2158
2377
  .docs-demo-source-body {
2159
2378
  flex: 1;
2160
2379
  min-height: 0;
@@ -2195,7 +2414,8 @@
2195
2414
  }
2196
2415
 
2197
2416
  .docs-demo-files-h,
2198
- .docs-demo-dir {
2417
+ .docs-demo-dir,
2418
+ .docs-demo-files-handle {
2199
2419
  display: none;
2200
2420
  }
2201
2421
 
@@ -2338,100 +2558,592 @@
2338
2558
  }
2339
2559
 
2340
2560
  .docs-demo-handle::after,
2561
+ .docs-demo-files-handle::after,
2562
+ .docs-demo-crumb,
2341
2563
  .docs-page-actions-more svg {
2342
2564
  transition: none;
2343
2565
  }
2344
2566
  }
2345
2567
 
2346
- /* Replay embeds (ReplayPreview / ReplayEmbed). Part of the one package stylesheet so no
2347
- module imports CSS on the eager path: the root entry stays importable from Node
2348
- (SSR, vitest) without a CSS loader. */
2349
- .replay-preview {
2350
- width: 100%;
2351
- min-width: 0;
2352
- color: var(--replay-preview-fg, #e5e7eb);
2353
- background: var(--replay-preview-bg, #0f172a);
2354
- border: 1px solid var(--replay-preview-border, rgba(148, 163, 184, 0.28));
2355
- border-radius: 12px;
2356
- overflow: hidden;
2357
- }
2568
+ /* ReplayPreview / ReplayEmbed: the session-replay player and its iframe embed. Part of the one package
2569
+ stylesheet so no module imports CSS on the eager path: the root entry stays importable from Node
2570
+ (SSR, vitest) without a CSS loader.
2571
+
2572
+ Everything sits in the `docstream.replay` cascade layer and under `:where()`, so a host's own rules win:
2573
+ unlayered CSS always does, and Tailwind-style utilities (`w-full`, `pointer-events-none`, `h-full`) do as
2574
+ long as their layer is declared after this one. If your utilities layer comes earlier in the layer order,
2575
+ put `@layer docstream.replay;` before it. Colors read the shadcn-style tokens below and fall back to
2576
+ neutral values: --card --background --foreground --muted --muted-foreground --border --primary --ring
2577
+ --destructive --success --warning --tertiary-foreground. The root sets `--replay-ar` (recorded aspect
2578
+ ratio) on the stage; the page and overlay run in recorded-viewport pixels with `--replay-scale` set. */
2579
+ @layer docstream.replay {
2580
+ :where([data-slot="replay-preview"]) {
2581
+ position: relative;
2582
+ isolation: isolate;
2583
+ display: flex;
2584
+ flex-direction: column;
2585
+ box-sizing: border-box;
2586
+ min-width: 0;
2587
+ overflow: hidden;
2588
+ outline: none;
2589
+ color: var(--foreground, #18181b);
2590
+ background: var(--card, #ffffff);
2591
+ font-size: 13px;
2592
+ line-height: 1.4;
2593
+ }
2358
2594
 
2359
- .replay-preview__frame {
2360
- display: block;
2361
- width: 100%;
2362
- aspect-ratio: 16 / 9;
2363
- border: 0;
2364
- background: #0f172a;
2365
- }
2595
+ :where([data-slot="replay-preview"] *, [data-slot="replay-preview"] *::before, [data-slot="replay-preview"] *::after) {
2596
+ box-sizing: border-box;
2597
+ }
2366
2598
 
2367
- .replay-preview__invalid,
2368
- .replay-preview__empty,
2369
- .replay-preview__error {
2370
- display: flex;
2371
- align-items: center;
2372
- justify-content: center;
2373
- min-height: 140px;
2374
- padding: 20px;
2375
- color: var(--replay-preview-muted, #94a3b8);
2376
- font: 13px/1.5 ui-sans-serif, system-ui, sans-serif;
2377
- text-align: center;
2378
- }
2599
+ :where([data-slot="replay-preview"][data-chrome="browser"]) {
2600
+ border: 1px solid var(--border, #e4e4e7);
2601
+ border-radius: 14px;
2602
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 18px 40px -18px rgba(0, 0, 0, 0.28);
2603
+ }
2379
2604
 
2380
- .replay-preview__invalid {
2381
- color: var(--replay-preview-link, #7dd3fc);
2382
- }
2605
+ :where([data-slot="replay-preview"][data-chrome="minimal"]) {
2606
+ border: 1px solid var(--border, #e4e4e7);
2607
+ border-radius: 12px;
2608
+ }
2383
2609
 
2384
- .replay-preview__error {
2385
- color: #fda4af;
2386
- }
2610
+ :where([data-slot="replay-preview"][data-fit="contain"]) {
2611
+ height: 100%;
2612
+ }
2387
2613
 
2388
- .replay-preview__native {
2389
- width: 100%;
2390
- background: #0f172a;
2391
- }
2614
+ :where([data-slot="replay-preview"][data-fullscreen]) {
2615
+ border: 0;
2616
+ border-radius: 0;
2617
+ }
2392
2618
 
2393
- .replay-preview__stage {
2394
- position: relative;
2395
- width: 100%;
2396
- min-height: 160px;
2397
- overflow: hidden;
2398
- background: #020617;
2399
- }
2619
+ /* ── browser bar ─────────────────────────────────────────────── */
2620
+ :where([data-slot="replay-preview-bar"]) {
2621
+ display: flex;
2622
+ flex-shrink: 0;
2623
+ align-items: center;
2624
+ gap: 12px;
2625
+ height: 40px;
2626
+ padding: 0 14px;
2627
+ border-bottom: 1px solid var(--border, #e4e4e7);
2628
+ background: color-mix(in srgb, var(--muted, #f4f4f5) 60%, transparent);
2629
+ }
2400
2630
 
2401
- .replay-preview__scaler {
2402
- position: absolute;
2403
- inset: 0 auto auto 0;
2404
- }
2631
+ :where([data-slot="replay-preview-lights"]) {
2632
+ display: flex;
2633
+ flex-shrink: 0;
2634
+ gap: 7px;
2635
+ }
2405
2636
 
2406
- .replay-preview__player {
2407
- position: relative;
2408
- overflow: hidden;
2637
+ :where([data-slot="replay-preview-lights"] > span) {
2638
+ width: 11px;
2639
+ height: 11px;
2640
+ border-radius: 50%;
2641
+ background: var(--light);
2642
+ box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.12);
2643
+ }
2644
+
2645
+ :where([data-slot="replay-preview-lights-spacer"]) {
2646
+ flex-shrink: 0;
2647
+ width: 47px;
2648
+ }
2649
+
2650
+ :where([data-slot="replay-preview-address"]) {
2651
+ display: flex;
2652
+ align-items: center;
2653
+ justify-content: center;
2654
+ gap: 6px;
2655
+ width: 100%;
2656
+ max-width: 460px;
2657
+ min-width: 0;
2658
+ height: 26px;
2659
+ margin-inline: auto;
2660
+ padding: 0 10px;
2661
+ border-radius: 7px;
2662
+ background: var(--background, #ffffff);
2663
+ box-shadow: inset 0 0 0 1px var(--border, #e4e4e7);
2664
+ font-size: 12px;
2665
+ }
2666
+
2667
+ :where([data-slot="replay-preview-lock"]) {
2668
+ display: grid;
2669
+ flex-shrink: 0;
2670
+ place-items: center;
2671
+ width: 12px;
2672
+ height: 12px;
2673
+ color: var(--tertiary-foreground, #a1a1aa);
2674
+ }
2675
+
2676
+ :where([data-slot="replay-preview-url"]) {
2677
+ min-width: 0;
2678
+ overflow: hidden;
2679
+ text-overflow: ellipsis;
2680
+ white-space: nowrap;
2681
+ }
2682
+
2683
+ :where([data-slot="replay-preview-url"] > :first-child) {
2684
+ color: var(--foreground, #18181b);
2685
+ }
2686
+
2687
+ :where([data-slot="replay-preview-url"] > :last-child) {
2688
+ color: var(--muted-foreground, #71717a);
2689
+ }
2690
+
2691
+ :where([data-slot="replay-preview-spinner"]) {
2692
+ width: 11px;
2693
+ height: 11px;
2694
+ border: 1.5px solid currentColor;
2695
+ border-right-color: transparent;
2696
+ border-radius: 50%;
2697
+ animation: docstream-replay-spin 0.7s linear infinite;
2698
+ }
2699
+
2700
+ /* ── stage: the page, its poster, the pointer overlay ────────── */
2701
+ :where([data-slot="replay-preview-stage"]) {
2702
+ position: relative;
2703
+ min-height: 0;
2704
+ overflow: hidden;
2705
+ aspect-ratio: var(--replay-ar, 16 / 9);
2706
+ background: var(--muted, #f4f4f5);
2707
+ }
2708
+
2709
+ :where([data-slot="replay-preview"][data-fit="contain"] > [data-slot="replay-preview-stage"]) {
2710
+ flex: 1 1 0%;
2711
+ aspect-ratio: auto;
2712
+ }
2713
+
2714
+ /* ReplayStage sets its own inline size and position; the stage box overrides them to fill the frame. */
2715
+ [data-slot="replay-preview-stage"] > .replay-preview__page {
2716
+ position: absolute !important;
2717
+ inset: 0 !important;
2718
+ }
2719
+
2720
+ :where([data-slot="replay-preview-pointer"]) {
2721
+ position: absolute;
2722
+ inset: 0;
2723
+ overflow: hidden;
2724
+ }
2725
+
2726
+ :where([data-slot="replay-preview-ripple"]) {
2727
+ position: absolute;
2728
+ width: 44px;
2729
+ height: 44px;
2730
+ transform-origin: center;
2731
+ border: 2px solid var(--primary, #4f46e5);
2732
+ border-radius: 50%;
2733
+ background: color-mix(in srgb, var(--primary, #4f46e5) 20%, transparent);
2734
+ }
2735
+
2736
+ :where([data-slot="replay-preview-cursor"]) {
2737
+ position: absolute;
2738
+ transform-origin: top left;
2739
+ filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.35));
2740
+ }
2741
+
2742
+ :where([data-slot="replay-preview-poster"]) {
2743
+ position: absolute;
2744
+ inset: 0;
2745
+ z-index: 10;
2746
+ transition: opacity 0.22s ease-out;
2747
+ }
2748
+
2749
+ :where([data-slot="replay-preview-poster"][data-leaving]) {
2750
+ opacity: 0;
2751
+ pointer-events: none;
2752
+ }
2753
+
2754
+ :where([data-slot="replay-preview-skeleton"]) {
2755
+ position: relative;
2756
+ display: flex;
2757
+ flex-direction: column;
2758
+ gap: 6%;
2759
+ width: 100%;
2760
+ height: 100%;
2761
+ padding: 4%;
2762
+ background: var(--background, #ffffff);
2763
+ }
2764
+
2765
+ :where([data-slot="replay-preview-skeleton"] i) {
2766
+ display: block;
2767
+ height: 5%;
2768
+ min-height: 8px;
2769
+ border-radius: 4px;
2770
+ background: color-mix(in srgb, var(--muted-foreground, #71717a) 16%, transparent);
2771
+ animation: docstream-replay-pulse 1.6s ease-in-out infinite;
2772
+ }
2773
+
2774
+ :where([data-slot="replay-preview-skeleton"] i[data-tone="block"]) {
2775
+ border-radius: 8px;
2776
+ min-height: 12px;
2777
+ }
2778
+
2779
+ :where([data-slot="replay-preview-skeleton"] [data-part="header"]) {
2780
+ display: flex;
2781
+ align-items: center;
2782
+ gap: 3%;
2783
+ }
2784
+
2785
+ :where([data-slot="replay-preview-skeleton"] [data-part="header"] > span) {
2786
+ flex: 1;
2787
+ }
2788
+
2789
+ :where([data-slot="replay-preview-skeleton"] [data-part="hero"]) {
2790
+ display: flex;
2791
+ flex-direction: column;
2792
+ gap: 8px;
2793
+ }
2794
+
2795
+ :where([data-slot="replay-preview-skeleton"] [data-part="hero"] i) {
2796
+ min-height: 0;
2797
+ }
2798
+
2799
+ :where([data-slot="replay-preview-skeleton"] [data-part="grid"]) {
2800
+ display: grid;
2801
+ flex: 1;
2802
+ grid-template-columns: repeat(4, 1fr);
2803
+ gap: 2.5%;
2804
+ }
2805
+
2806
+ :where([data-slot="replay-preview-skeleton"] [data-part="grid"] i) {
2807
+ height: 70%;
2808
+ }
2809
+
2810
+ :where([data-slot="replay-preview-skeleton"] [data-part="empty"]) {
2811
+ position: absolute;
2812
+ inset: 0;
2813
+ display: grid;
2814
+ place-items: center;
2815
+ }
2816
+
2817
+ :where([data-slot="replay-preview-skeleton"] [data-part="empty"] > span) {
2818
+ padding: 6px 12px;
2819
+ border-radius: 999px;
2820
+ color: var(--muted-foreground, #71717a);
2821
+ background: var(--card, #ffffff);
2822
+ box-shadow: 0 0 0 1px var(--border, #e4e4e7), 0 4px 16px rgba(0, 0, 0, 0.08);
2823
+ font-size: 12.5px;
2824
+ font-weight: 500;
2825
+ }
2826
+
2827
+ /* ── the big play button ─────────────────────────────────────── */
2828
+ :where([data-slot="replay-preview-center"]) {
2829
+ position: absolute;
2830
+ inset: 0;
2831
+ z-index: 10;
2832
+ display: grid;
2833
+ place-items: center;
2834
+ pointer-events: none;
2835
+ visibility: hidden;
2836
+ opacity: 0;
2837
+ scale: 1.15;
2838
+ transition: opacity 0.16s ease-out, scale 0.2s ease-out, visibility 0s linear 0.2s;
2839
+ }
2840
+
2841
+ :where([data-slot="replay-preview-center"][data-show]) {
2842
+ visibility: visible;
2843
+ opacity: 1;
2844
+ scale: 1;
2845
+ transition: opacity 0.2s ease-out, scale 0.32s cubic-bezier(0.34, 1.56, 0.64, 1), visibility 0s;
2846
+ }
2847
+
2848
+ :where([data-slot="replay-preview-center"] > button) {
2849
+ display: grid;
2850
+ place-items: center;
2851
+ width: 56px;
2852
+ height: 56px;
2853
+ margin: 0;
2854
+ padding: 0;
2855
+ border: 0;
2856
+ border-radius: 50%;
2857
+ outline: none;
2858
+ cursor: pointer;
2859
+ pointer-events: auto;
2860
+ appearance: none;
2861
+ color: var(--background, #ffffff);
2862
+ background: color-mix(in srgb, var(--foreground, #18181b) 72%, transparent);
2863
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
2864
+ -webkit-backdrop-filter: blur(12px);
2865
+ backdrop-filter: blur(12px);
2866
+ transition: scale 0.16s ease-out;
2867
+ }
2868
+
2869
+ :where([data-slot="replay-preview-center"] > button:hover) {
2870
+ scale: 1.05;
2871
+ }
2872
+
2873
+ :where([data-slot="replay-preview-center"] > button:active) {
2874
+ scale: 0.95;
2875
+ }
2876
+
2877
+ :where([data-slot="replay-preview-center"] > button > svg) {
2878
+ translate: 1px 0;
2879
+ }
2880
+
2881
+ /* ── controls ────────────────────────────────────────────────── */
2882
+ :where([data-slot="replay-preview-controls"]) {
2883
+ display: flex;
2884
+ flex-shrink: 0;
2885
+ align-items: center;
2886
+ gap: 4px;
2887
+ height: 44px;
2888
+ padding: 0 6px;
2889
+ border-top: 1px solid var(--border, #e4e4e7);
2890
+ background: var(--card, #ffffff);
2891
+ }
2892
+
2893
+ :where([data-slot="replay-preview-controls"] button) {
2894
+ display: inline-grid;
2895
+ flex-shrink: 0;
2896
+ place-items: center;
2897
+ height: 32px;
2898
+ min-width: 32px;
2899
+ margin: 0;
2900
+ padding: 0;
2901
+ border: 0;
2902
+ border-radius: 8px;
2903
+ outline: none;
2904
+ cursor: pointer;
2905
+ appearance: none;
2906
+ color: var(--foreground, #18181b);
2907
+ background: transparent;
2908
+ font: inherit;
2909
+ transition: background-color 0.12s ease-out;
2910
+ }
2911
+
2912
+ :where([data-slot="replay-preview-controls"] button:hover:not(:disabled)) {
2913
+ background: color-mix(in srgb, var(--foreground, #18181b) 8%, transparent);
2914
+ }
2915
+
2916
+ :where([data-slot="replay-preview-controls"] button:disabled) {
2917
+ cursor: default;
2918
+ opacity: 0.5;
2919
+ }
2920
+
2921
+ :where([data-slot="replay-preview-controls"] button:focus-visible) {
2922
+ box-shadow: 0 0 0 2px var(--ring, #6366f1);
2923
+ }
2924
+
2925
+ :where([data-slot="replay-preview-speed"]) {
2926
+ height: 28px;
2927
+ min-width: 42px;
2928
+ padding: 0 6px;
2929
+ border-radius: 6px;
2930
+ font-size: 12px;
2931
+ font-variant-numeric: tabular-nums;
2932
+ }
2933
+
2934
+ :where([data-slot="replay-preview-time"]) {
2935
+ flex-shrink: 0;
2936
+ width: 74px;
2937
+ color: var(--muted-foreground, #71717a);
2938
+ font-size: 11.5px;
2939
+ font-weight: 500;
2940
+ font-variant-numeric: tabular-nums;
2941
+ }
2942
+
2943
+ :where([data-slot="replay-preview-time"] > span) {
2944
+ color: var(--foreground, #18181b);
2945
+ }
2946
+
2947
+ /* ── scrubber: a native range, with the markers' rail above it ─
2948
+ (the thumb and track pseudo-elements can't sit inside :where(); the layer still lets hosts win) */
2949
+ :where([data-slot="replay-preview-scrubber"]) {
2950
+ position: relative;
2951
+ display: flex;
2952
+ flex: 1 1 0%;
2953
+ flex-direction: column;
2954
+ justify-content: center;
2955
+ align-self: stretch;
2956
+ min-width: 0;
2957
+ padding-inline: 4px;
2958
+ }
2959
+
2960
+ :where([data-slot="replay-preview-markers"]) {
2961
+ position: absolute;
2962
+ inset-inline: 4px;
2963
+ top: 13px;
2964
+ height: 10px;
2965
+ margin-inline: 6px;
2966
+ }
2967
+
2968
+ :where([data-slot="replay-preview-scrubber"][data-markers] > input) {
2969
+ transform: translateY(5px);
2970
+ }
2971
+
2972
+ :where([data-slot="replay-preview-markers"] > button) {
2973
+ position: absolute;
2974
+ top: 50%;
2975
+ box-sizing: border-box;
2976
+ width: 8px;
2977
+ height: 8px;
2978
+ min-width: 0;
2979
+ margin: 0;
2980
+ padding: 0;
2981
+ border: 0;
2982
+ border-radius: 50%;
2983
+ outline: none;
2984
+ cursor: pointer;
2985
+ appearance: none;
2986
+ background: var(--success, #22c55e);
2987
+ box-shadow: 0 0 0 2px var(--card, #ffffff);
2988
+ translate: -50% -50%;
2989
+ transition: scale 0.16s ease-out;
2990
+ }
2991
+
2992
+ :where([data-slot="replay-preview-markers"] > button:hover:not(:disabled)) {
2993
+ scale: 1.5;
2994
+ }
2995
+
2996
+ :where([data-slot="replay-preview-markers"] > button[data-kind="click"]) {
2997
+ width: 3px;
2998
+ height: 8px;
2999
+ border-radius: 1.5px;
3000
+ background: var(--tertiary-foreground, #a1a1aa);
3001
+ box-shadow: none;
3002
+ }
3003
+
3004
+ :where([data-slot="replay-preview-markers"] > button[data-kind="error"]) {
3005
+ background: var(--destructive, #ef4444);
3006
+ }
3007
+
3008
+ :where([data-slot="replay-preview-markers"] > button[data-kind="network"]) {
3009
+ background: var(--warning, #f59e0b);
3010
+ }
3011
+
3012
+ :where([data-slot="replay-preview-markers"] > button[data-kind="navigation"]) {
3013
+ background: var(--primary, #4f46e5);
3014
+ }
3015
+
3016
+ :where([data-slot="replay-preview-markers"] > button:focus-visible) {
3017
+ box-shadow: 0 0 0 2px var(--card, #ffffff), 0 0 0 4px var(--ring, #6366f1);
3018
+ }
3019
+
3020
+ :where([data-slot="replay-preview-scrubber"] > input[type="range"]) {
3021
+ --replay-track: 4px;
3022
+ --replay-thumb: 12px;
3023
+ appearance: none;
3024
+ -webkit-appearance: none;
3025
+ display: block;
3026
+ width: 100%;
3027
+ height: 20px;
3028
+ margin: 0;
3029
+ padding: 0;
3030
+ outline: none;
3031
+ cursor: pointer;
3032
+ background: transparent;
3033
+ }
3034
+
3035
+ :where([data-slot="replay-preview-scrubber"] > input[type="range"]:disabled) {
3036
+ cursor: default;
3037
+ opacity: 0.5;
3038
+ }
3039
+
3040
+ [data-slot="replay-preview-scrubber"] > input[type="range"]::-webkit-slider-runnable-track {
3041
+ height: var(--replay-track);
3042
+ border-radius: 999px;
3043
+ background: linear-gradient(
3044
+ to right,
3045
+ var(--primary, #4f46e5) var(--replay-progress, 0%),
3046
+ color-mix(in srgb, var(--muted-foreground, #71717a) 26%, transparent) var(--replay-progress, 0%)
3047
+ );
3048
+ }
3049
+
3050
+ [data-slot="replay-preview-scrubber"] > input[type="range"]::-moz-range-track {
3051
+ height: var(--replay-track);
3052
+ border-radius: 999px;
3053
+ background: color-mix(in srgb, var(--muted-foreground, #71717a) 26%, transparent);
3054
+ }
3055
+
3056
+ [data-slot="replay-preview-scrubber"] > input[type="range"]::-moz-range-progress {
3057
+ height: var(--replay-track);
3058
+ border-radius: 999px;
3059
+ background: var(--primary, #4f46e5);
3060
+ }
3061
+
3062
+ [data-slot="replay-preview-scrubber"] > input[type="range"]::-webkit-slider-thumb {
3063
+ -webkit-appearance: none;
3064
+ width: var(--replay-thumb);
3065
+ height: var(--replay-thumb);
3066
+ margin-top: calc((var(--replay-track) - var(--replay-thumb)) / 2);
3067
+ border: 0;
3068
+ border-radius: 50%;
3069
+ background: var(--primary, #4f46e5);
3070
+ box-shadow: 0 0 0 2px var(--card, #ffffff), 0 1px 3px rgba(0, 0, 0, 0.3);
3071
+ }
3072
+
3073
+ [data-slot="replay-preview-scrubber"] > input[type="range"]::-moz-range-thumb {
3074
+ width: var(--replay-thumb);
3075
+ height: var(--replay-thumb);
3076
+ border: 0;
3077
+ border-radius: 50%;
3078
+ background: var(--primary, #4f46e5);
3079
+ box-shadow: 0 0 0 2px var(--card, #ffffff), 0 1px 3px rgba(0, 0, 0, 0.3);
3080
+ }
3081
+
3082
+ [data-slot="replay-preview-scrubber"] > input[type="range"]:focus-visible::-webkit-slider-thumb {
3083
+ box-shadow: 0 0 0 2px var(--card, #ffffff), 0 0 0 4px var(--ring, #6366f1);
3084
+ }
3085
+
3086
+ [data-slot="replay-preview-scrubber"] > input[type="range"]:focus-visible::-moz-range-thumb {
3087
+ box-shadow: 0 0 0 2px var(--card, #ffffff), 0 0 0 4px var(--ring, #6366f1);
3088
+ }
3089
+
3090
+ /* ── iframe embeds (a public Loop QA page) and their notes ───── */
3091
+ :where(.replay-preview__frame) {
3092
+ position: absolute;
3093
+ inset: 0;
3094
+ display: block;
3095
+ width: 100%;
3096
+ height: 100%;
3097
+ border: 0;
3098
+ background: var(--background, #ffffff);
3099
+ }
3100
+
3101
+ :where(.replay-preview__invalid, .replay-preview__empty, .replay-preview__error) {
3102
+ position: absolute;
3103
+ inset: 0;
3104
+ display: flex;
3105
+ align-items: center;
3106
+ justify-content: center;
3107
+ padding: 20px;
3108
+ color: var(--muted-foreground, #71717a);
3109
+ font-size: 13px;
3110
+ line-height: 1.5;
3111
+ text-align: center;
3112
+ }
3113
+
3114
+ :where(.replay-preview__invalid) {
3115
+ color: var(--primary, #4f46e5);
3116
+ }
3117
+
3118
+ :where(.replay-preview__error) {
3119
+ color: var(--destructive, #ef4444);
3120
+ }
2409
3121
  }
2410
3122
 
2411
- .replay-preview__controls {
2412
- display: flex;
2413
- align-items: center;
2414
- gap: 8px;
2415
- padding: 8px 10px;
2416
- border-top: 1px solid rgba(148, 163, 184, 0.2);
2417
- background: #111827;
2418
- font: 12px/1.2 ui-sans-serif, system-ui, sans-serif;
3123
+ @keyframes docstream-replay-spin {
3124
+ to {
3125
+ transform: rotate(360deg);
3126
+ }
2419
3127
  }
2420
3128
 
2421
- .replay-preview__controls button,
2422
- .replay-preview__controls select {
2423
- border: 1px solid rgba(148, 163, 184, 0.3);
2424
- border-radius: 6px;
2425
- padding: 5px 8px;
2426
- color: inherit;
2427
- background: #1e293b;
2428
- cursor: pointer;
3129
+ @keyframes docstream-replay-pulse {
3130
+ 50% {
3131
+ opacity: 0.55;
3132
+ }
2429
3133
  }
2430
3134
 
2431
- .replay-preview__controls input[type='range'] {
2432
- min-width: 60px;
2433
- flex: 1;
2434
- accent-color: #38bdf8;
3135
+ @media (prefers-reduced-motion: reduce) {
3136
+ @layer docstream.replay {
3137
+ :where([data-slot="replay-preview"] *) {
3138
+ animation: none !important;
3139
+ transition-duration: 0.01ms !important;
3140
+ transition-delay: 0s !important;
3141
+ }
3142
+
3143
+ :where([data-slot="replay-preview-center"]) {
3144
+ scale: 1;
3145
+ }
3146
+ }
2435
3147
  }
2436
3148
 
2437
3149
  /* Keep every rendered table variant flush, after the shared block spacing rules. */