@helpway/react 0.13.3 → 0.14.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.
@@ -66,17 +66,17 @@
66
66
  --hw-focus-ring: rgba(0, 0, 0, 0.06);
67
67
  --hw-accent: #6366f1;
68
68
  --hw-bg: #ffffff;
69
- --hw-bg-subtle: #fafafa;
70
- --hw-bg-hover: #f5f5f5;
71
- --hw-border: #f0f0f0;
69
+ --hw-bg-subtle: #fafaf9;
70
+ --hw-bg-hover: #f5f5f4;
71
+ --hw-border: #ebe9e5;
72
72
  --hw-border-md: #e5e5e5;
73
- --hw-text: #111;
73
+ --hw-text: #1c1b19;
74
74
  --hw-text-1: #111;
75
75
  --hw-text-2: #666;
76
- --hw-text-3: #999;
77
- --hw-text-4: #bbb;
78
- --hw-radius: 16px;
79
- --hw-visitor-bubble-bg: #f0f0f0;
76
+ --hw-text-3: #8a847c;
77
+ --hw-text-4: #a8a29a;
78
+ --hw-radius: 20px;
79
+ --hw-visitor-bubble-bg: #f5f5f4;
80
80
  --hw-visitor-bubble-text: #1a1a1a;
81
81
  /* Widget font stack. Consumers can override by setting --hw-font
82
82
  on the `.hw-widget` element. Was `inherit` previously, which
@@ -534,7 +534,7 @@
534
534
  .hw-header {
535
535
  display: flex;
536
536
  align-items: center;
537
- padding: 12px 16px;
537
+ padding: 14px 12px 14px 16px;
538
538
  background: var(--hw-primary);
539
539
  color: white;
540
540
  flex-shrink: 0;
@@ -546,7 +546,8 @@
546
546
  justify-content: space-between;
547
547
  background: transparent;
548
548
  color: var(--hw-text);
549
- border-bottom: 1px solid var(--hw-border);
549
+ /* no rule under the header — the thread just changes ground */
550
+ border-bottom: 0;
550
551
  }
551
552
 
552
553
  .hw-header-back {
@@ -581,14 +582,14 @@
581
582
  }
582
583
 
583
584
  .hw-header-bot-icon {
584
- width: 28px;
585
- height: 28px;
585
+ width: 32px;
586
+ height: 32px;
586
587
  flex-shrink: 0;
587
588
  color: var(--hw-text);
588
589
  display: flex;
589
590
  align-items: center;
590
591
  justify-content: center;
591
- border-radius: 8px;
592
+ border-radius: 9px;
592
593
  /* Two-tier fallback so site overrides still win cleanly:
593
594
  1. Site stylesheet → `--hw-agent-bg` (highest priority)
594
595
  2. Widget smart default → `--hw-agent-bg-default` (set when the
@@ -644,13 +645,13 @@
644
645
  }
645
646
 
646
647
  .hw-header-name {
647
- font-size: 14px;
648
+ font-size: 15px;
648
649
  font-weight: 600;
649
650
  line-height: 1.3;
650
651
  }
651
652
 
652
653
  .hw-header-status {
653
- font-size: 12px;
654
+ font-size: 11px;
654
655
  color: var(--hw-text-3);
655
656
  line-height: 1.3;
656
657
  }
@@ -692,7 +693,8 @@
692
693
  justify-content: space-between;
693
694
  background: transparent;
694
695
  color: var(--hw-text);
695
- border-bottom: 1px solid var(--hw-border);
696
+ /* no rule under the header — the thread just changes ground */
697
+ border-bottom: 0;
696
698
  }
697
699
  .hw-header--centered .hw-header-title {
698
700
  position: absolute;
@@ -714,7 +716,8 @@
714
716
  justify-content: space-between;
715
717
  background: transparent;
716
718
  color: var(--hw-text);
717
- border-bottom: 1px solid var(--hw-border);
719
+ /* no rule under the header — the thread just changes ground */
720
+ border-bottom: 0;
718
721
  }
719
722
  .hw-header-title {
720
723
  font-size: 16px;
@@ -767,7 +770,8 @@
767
770
  padding: 14px 16px;
768
771
  border-radius: 14px;
769
772
  background: var(--hw-bg);
770
- border: 1px solid rgba(0, 0, 0, 0.06);
773
+ border: 0;
774
+ box-shadow: inset 0 0 0 1px var(--hw-border);
771
775
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);
772
776
  cursor: pointer;
773
777
  /* Scoped box-shadow + transform on strong ease-out. Hover raises
@@ -840,7 +844,8 @@
840
844
  .hw-recent-card {
841
845
  border-radius: 14px;
842
846
  background: var(--hw-bg);
843
- border: 1px solid rgba(0, 0, 0, 0.06);
847
+ border: 0;
848
+ box-shadow: inset 0 0 0 1px var(--hw-border);
844
849
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);
845
850
  overflow: hidden;
846
851
  }
@@ -952,7 +957,8 @@
952
957
  padding: 14px 16px;
953
958
  border-radius: 14px;
954
959
  background: var(--hw-bg);
955
- border: 1px solid rgba(0, 0, 0, 0.06);
960
+ border: 0;
961
+ box-shadow: inset 0 0 0 1px var(--hw-border);
956
962
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);
957
963
  cursor: pointer;
958
964
  transition: box-shadow 0.15s, transform 0.1s;
@@ -1211,11 +1217,11 @@
1211
1217
  flex: 1;
1212
1218
  overflow-y: auto;
1213
1219
  overflow-x: hidden; /* Mobile safeguard — a single stray overflow would create horizontal scroll */
1214
- padding: 16px 20px;
1220
+ padding: 16px 14px 8px;
1215
1221
  background: var(--hw-bg-subtle);
1216
1222
  display: flex;
1217
1223
  flex-direction: column;
1218
- gap: 2px;
1224
+ gap: 14px;
1219
1225
  overscroll-behavior: contain;
1220
1226
  /* Flex column with constrained children — min-width:0 allows children to
1221
1227
  honor their own max-width rather than inflating to content width. */
@@ -1357,7 +1363,7 @@
1357
1363
 
1358
1364
  .hw-msg-bubble {
1359
1365
  padding: 10px 14px;
1360
- border-radius: 18px;
1366
+ border-radius: 16px;
1361
1367
  font-size: 14px;
1362
1368
  line-height: 1.45;
1363
1369
  white-space: pre-wrap;
@@ -1413,7 +1419,8 @@
1413
1419
  font-weight: 500;
1414
1420
  padding: 6px 14px;
1415
1421
  border-radius: 18px;
1416
- border: 1px solid rgba(0, 0, 0, 0.12);
1422
+ border: 0;
1423
+ box-shadow: inset 0 0 0 1px var(--hw-border);
1417
1424
  background: var(--hw-bg, #fff);
1418
1425
  color: var(--hw-text-1, #1a1a1a);
1419
1426
  cursor: pointer;
@@ -1504,21 +1511,13 @@
1504
1511
  animation: hw-msg-appear 0.25s ease-out;
1505
1512
  }
1506
1513
 
1507
- .hw-typing-label {
1508
- font-size: 11px;
1509
- font-weight: 500;
1510
- color: var(--hw-text-3);
1511
- padding: 0 4px;
1512
- }
1513
-
1514
+ /* Bare dots, no bubble. The answer they precede is not in a bubble either, so
1515
+ a filled rounded rectangle here read as a message that never arrived. */
1514
1516
  .hw-typing-dots {
1515
1517
  display: flex;
1516
1518
  gap: 3px;
1517
- padding: 10px 14px;
1519
+ padding: 6px 2px;
1518
1520
  width: fit-content;
1519
- background: var(--hw-visitor-bubble-bg);
1520
- border-radius: var(--hw-radius);
1521
- border-bottom-left-radius: 4px;
1522
1521
  }
1523
1522
 
1524
1523
  .hw-typing-dot {
@@ -1833,13 +1832,19 @@
1833
1832
  EMAIL COLLECTION CARD (fixed above composer)
1834
1833
  ════════════════════════════════════════════════ */
1835
1834
 
1835
+ /* The ask reads as the agent's last line, so it is built like one: no card, no
1836
+ border, no ground of its own, and the width a message gets rather than the full
1837
+ width of the panel. It used to be a bordered form pinned under the thread, which
1838
+ put a second input directly above the composer — two places to type, one of them
1839
+ looking like chrome the panel had grown. */
1836
1840
  .hw-email-card {
1837
- margin: 0 12px;
1838
- padding: 14px 16px 10px;
1839
- border: 1px solid var(--hw-border, rgba(0,0,0,0.08));
1840
- border-radius: 14px;
1841
- background: var(--hw-bg, #fff);
1842
- box-shadow: 0 1px 4px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.03);
1841
+ max-width: min(100%, 340px);
1842
+ margin: 0;
1843
+ padding: 0;
1844
+ border: 0;
1845
+ border-radius: 0;
1846
+ background: none;
1847
+ box-shadow: none;
1843
1848
  animation: hw-email-slide-in 0.25s ease-out;
1844
1849
  }
1845
1850
 
@@ -1848,28 +1853,9 @@
1848
1853
  to { opacity: 1; transform: translateY(0); }
1849
1854
  }
1850
1855
 
1851
- .hw-email-card-header {
1852
- display: flex;
1853
- align-items: flex-start;
1854
- gap: 10px;
1855
- margin-bottom: 12px;
1856
- }
1857
-
1858
- .hw-email-card-icon {
1859
- flex-shrink: 0;
1860
- width: 32px;
1861
- height: 32px;
1862
- border-radius: 8px;
1863
- background: var(--hw-bg-hover, #f5f5f5);
1864
- display: flex;
1865
- align-items: center;
1866
- justify-content: center;
1867
- color: var(--hw-text, #1a1a1a);
1868
- }
1869
-
1870
1856
  .hw-email-card-title {
1871
- font-size: 13px;
1872
- font-weight: 600;
1857
+ font-size: 14px;
1858
+ font-weight: 550;
1873
1859
  color: var(--hw-text, #1a1a1a);
1874
1860
  line-height: 1.3;
1875
1861
  margin: 0 0 2px;
@@ -1882,65 +1868,72 @@
1882
1868
  margin: 0;
1883
1869
  }
1884
1870
 
1885
- .hw-email-card-input {
1886
- width: 100%;
1887
- padding: 9px 12px;
1888
- border-radius: 10px;
1871
+ /* Field and submit share one row, and the row is the only control in the card. */
1872
+ .hw-email-card-row {
1873
+ max-width: 300px;
1874
+ display: flex;
1875
+ align-items: center;
1876
+ gap: 6px;
1877
+ margin-top: 9px;
1878
+ padding: 3px 3px 3px 12px;
1879
+ border-radius: 999px;
1889
1880
  border: 1px solid var(--hw-border, rgba(0,0,0,0.1));
1890
1881
  background: var(--hw-bg-subtle, #fafafa);
1882
+ transition: border-color 0.15s, box-shadow 0.15s;
1883
+ }
1884
+ .hw-email-card-row:focus-within {
1885
+ border-color: var(--hw-focus-border, rgba(0,0,0,0.2));
1886
+ box-shadow: 0 0 0 2px var(--hw-focus-ring, rgba(0,0,0,0.06));
1887
+ }
1888
+ .hw-email-card-input {
1889
+ flex: 1;
1890
+ min-width: 0;
1891
+ padding: 6px 0;
1892
+ border: 0;
1893
+ background: transparent;
1891
1894
  color: var(--hw-text, #1a1a1a);
1895
+ font: inherit;
1892
1896
  font-size: 13px;
1893
1897
  outline: none;
1894
- margin-bottom: 8px;
1895
- box-sizing: border-box;
1896
- transition: border-color 0.15s, box-shadow 0.15s;
1897
1898
  }
1898
1899
 
1899
1900
  .hw-email-card-input::placeholder {
1900
1901
  color: var(--hw-text-3, #999);
1901
1902
  }
1902
1903
 
1903
- .hw-email-card-input:focus {
1904
- border-color: var(--hw-focus-border, rgba(0,0,0,0.2));
1905
- box-shadow: 0 0 0 2px var(--hw-focus-ring, rgba(0,0,0,0.06));
1906
- }
1907
-
1908
- .hw-email-card-input--error {
1904
+ /* The error lands on the row, since the row is what looks like the field now. */
1905
+ .hw-email-card-row:has(.hw-email-card-input--error) {
1909
1906
  border-color: #ef4444;
1910
1907
  }
1911
1908
 
1912
1909
  .hw-email-card-submit {
1913
- width: 100%;
1914
- padding: 9px;
1915
- border-radius: 10px;
1910
+ flex: none;
1911
+ display: grid;
1912
+ place-items: center;
1913
+ width: 28px;
1914
+ height: 28px;
1916
1915
  border: none;
1916
+ border-radius: 50%;
1917
1917
  background: var(--hw-brand, #0a0a0a);
1918
1918
  color: var(--hw-on-brand, #fff);
1919
- font-size: 13px;
1920
- font-weight: 500;
1921
1919
  cursor: pointer;
1922
- transition: opacity 0.15s;
1923
- display: flex;
1924
- align-items: center;
1925
- justify-content: center;
1926
- gap: 6px;
1920
+ transition: opacity 0.15s, transform 0.15s;
1927
1921
  }
1928
1922
 
1929
1923
  .hw-email-card-submit:hover:not(:disabled) {
1930
- opacity: 0.88;
1924
+ transform: scale(1.06);
1931
1925
  }
1932
1926
 
1933
1927
  .hw-email-card-submit:disabled {
1934
- opacity: 0.5;
1935
- cursor: not-allowed;
1928
+ opacity: 0.45;
1929
+ cursor: default;
1936
1930
  }
1937
1931
 
1938
1932
  .hw-email-card--success {
1939
1933
  display: flex;
1940
1934
  align-items: center;
1941
- justify-content: center;
1942
1935
  gap: 6px;
1943
- padding: 10px 16px;
1936
+ padding: 2px 0;
1944
1937
  font-size: 13px;
1945
1938
  color: var(--hw-text-2, #888);
1946
1939
  animation: hw-email-slide-in 0.2s ease-out;
@@ -1969,7 +1962,8 @@
1969
1962
  .hw-composer-box {
1970
1963
  display: flex;
1971
1964
  flex-direction: column;
1972
- border: 2px solid var(--hw-border-md);
1965
+ border: 0;
1966
+ box-shadow: inset 0 0 0 1px var(--hw-border);
1973
1967
  border-radius: 16px;
1974
1968
  overflow: hidden;
1975
1969
  transition: border-color 0.2s;
@@ -2342,7 +2336,11 @@
2342
2336
 
2343
2337
  .hw-hover-preview {
2344
2338
  position: fixed;
2345
- z-index: 99998;
2339
+ /* Above the widget, not merely above the page. This portals into
2340
+ document.body as a SIBLING of .hw-widget, and the docked root declares
2341
+ z-index 2147483000 — so 99998 put the preview behind the very panel
2342
+ whose image you were hovering. Sibling comparison, so one more wins. */
2343
+ z-index: 2147483001;
2346
2344
  padding: 6px;
2347
2345
  border-radius: 14px;
2348
2346
  background: white;
@@ -2770,6 +2768,16 @@
2770
2768
  height: 16px;
2771
2769
  }
2772
2770
 
2771
+ /* A real person's photo fills the circle; the mark stays a 16px glyph inside
2772
+ it. Without this the uploaded avatar rendered at its natural size and burst
2773
+ out of the 28px puck. */
2774
+ .hw-preview-avatar img {
2775
+ width: 100%;
2776
+ height: 100%;
2777
+ object-fit: cover;
2778
+ display: block;
2779
+ }
2780
+
2773
2781
  .hw-preview-name {
2774
2782
  font-size: 13px;
2775
2783
  font-weight: 600;
@@ -2937,7 +2945,8 @@
2937
2945
  .hw-whats-new {
2938
2946
  border-radius: 14px;
2939
2947
  background: var(--hw-bg);
2940
- border: 1px solid rgba(0, 0, 0, 0.06);
2948
+ border: 0;
2949
+ box-shadow: inset 0 0 0 1px var(--hw-border);
2941
2950
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.03);
2942
2951
  overflow: hidden;
2943
2952
  }
@@ -4369,3 +4378,1152 @@
4369
4378
  border-color: var(--hw-border);
4370
4379
  }
4371
4380
  }
4381
+
4382
+ /* ── Docked placement ──────────────────────────────────────────────────
4383
+ The bar is pinned to the foot of the page and IS the composer, so the
4384
+ panel above it renders without one. Ported from the design bench at
4385
+ docs/prototypes/widget-bench.html; geometry verified there against a
4386
+ real 1440x900 page. */
4387
+
4388
+ .hw-widget[data-hw-placement="docked"] {
4389
+ --hw-dock-width: 600px;
4390
+ /* Folded away, the bar carries only a placeholder and three small tools, and
4391
+ at the panel's full measure it read as a wide empty trough across the page.
4392
+ Open, it goes back to --hw-dock-width so the pair still reads as one
4393
+ column. */
4394
+ --hw-dock-bar-width: 480px;
4395
+ --hw-dock-inset: 24px;
4396
+ --hw-bar-max-height: 148px;
4397
+ /* The docked shell owns its surface rather than inheriting --hw-bg, so
4398
+ the bar and the pills read as one family even when a host page
4399
+ themes the panel. It still has to follow the widget's appearance —
4400
+ a black bar under a white panel is two products, not one. Light by
4401
+ default; the dark values live with the rest of the dark tokens. */
4402
+ --hw-dock-surface: #ffffff;
4403
+ --hw-dock-on-surface: #111111;
4404
+ /* The pills sit over page content, not over the bar, so they need a scrim of
4405
+ their own — but a near-black scrim made three floating slabs that read as
4406
+ the loudest thing on the page, louder than the composer they belong to.
4407
+ Light grey with dark ink keeps them legible over whatever is behind them
4408
+ while staying subordinate: they are offers, not the answer. */
4409
+ --hw-dock-pill-bg: rgba(44, 44, 48, 0.66);
4410
+ --hw-dock-pill-fg: rgba(255, 255, 255, 0.94);
4411
+ --hw-dock-pill-border: rgba(255, 255, 255, 0.14);
4412
+ }
4413
+
4414
+ /* The root carries z-index 999999 and is therefore a stacking context, so
4415
+ the dock's 2147483000 was capped by it — the declared value was a lie.
4416
+ Lift the root for this placement so the number means what it says. */
4417
+ .hw-widget[data-hw-placement="docked"] {
4418
+ z-index: 2147483000;
4419
+ }
4420
+
4421
+ .hw-widget[data-hw-placement="docked"] .hw-dock {
4422
+ position: fixed;
4423
+ left: 0;
4424
+ right: 0;
4425
+ bottom: var(--hw-dock-inset);
4426
+ z-index: 2147483000;
4427
+ display: flex;
4428
+ flex-direction: column;
4429
+ align-items: center;
4430
+ pointer-events: none;
4431
+ }
4432
+ .hw-widget[data-hw-placement="docked"] .hw-dock > * {
4433
+ pointer-events: auto;
4434
+ }
4435
+
4436
+ /* Nothing is open yet, so there is no panel to put the ways-in inside.
4437
+ Pills over the page read as the agent offering; a panel would read as
4438
+ the agent demanding attention. */
4439
+ .hw-dock-suggestions {
4440
+ display: flex;
4441
+ flex-direction: column;
4442
+ align-items: flex-start;
4443
+ gap: 9px;
4444
+ /* Above the folded bar, so it takes the bar's measure too. */
4445
+ width: var(--hw-dock-bar-width, var(--hw-dock-width));
4446
+ max-width: calc(100vw - 32px);
4447
+ margin-bottom: 14px;
4448
+ }
4449
+ /* The pill and its dismiss are siblings in one row so the × sits outside the
4450
+ button's hit area — nesting a button in a button is invalid, and a click on the
4451
+ inner one would still send the outer one's question. The row carries the entrance
4452
+ animation so both parts arrive together. */
4453
+ .hw-dock-pill-row {
4454
+ align-self: flex-start;
4455
+ display: inline-flex;
4456
+ align-items: center;
4457
+ gap: 4px;
4458
+ animation: hw-dock-pill-in 0.42s cubic-bezier(0.2, 0.9, 0.3, 1) both;
4459
+ }
4460
+ /* One × for the whole offer, sitting beside the first pill. Hidden until the visitor
4461
+ reaches into the group, because it is an escape hatch and not a decision they are
4462
+ being asked to make. Revealed by hovering ANY pill, not just the first — the control
4463
+ closes all of them, so it has to be reachable from all of them. Focus matters as much
4464
+ as hover: opacity 0 alone would leave a keyboard user tabbing to something invisible. */
4465
+ .hw-dock-pill-dismiss {
4466
+ display: inline-flex;
4467
+ align-items: center;
4468
+ justify-content: center;
4469
+ width: 20px;
4470
+ height: 20px;
4471
+ flex-shrink: 0;
4472
+ padding: 0;
4473
+ /* Bare glyph. Given a pill's own fill and border it read as a second, smaller pill
4474
+ sitting next to the first — two controls of equal weight, when one of them is an
4475
+ escape hatch that is meant to be found rather than offered. */
4476
+ border: 0;
4477
+ background: none;
4478
+ color: var(--hw-dock-pill-fg);
4479
+ cursor: pointer;
4480
+ /* Riding slightly above the pill's centre line. Optically centred sits low next to
4481
+ a rounded pill, because the eye reads the mark against the cap height of the text
4482
+ beside it rather than against the pill's full height. */
4483
+ align-self: flex-start;
4484
+ margin-top: 8px;
4485
+ opacity: 0;
4486
+ transform: scale(0.85);
4487
+ transition:
4488
+ opacity 0.12s linear,
4489
+ transform 0.12s cubic-bezier(0.25, 1, 0.5, 1),
4490
+ color 0.12s linear;
4491
+ }
4492
+ .hw-dock-pill-dismiss svg {
4493
+ width: 11px;
4494
+ height: 11px;
4495
+ }
4496
+ .hw-dock-suggestions:hover .hw-dock-pill-dismiss,
4497
+ .hw-dock-suggestions:focus-within .hw-dock-pill-dismiss {
4498
+ opacity: 1;
4499
+ transform: none;
4500
+ }
4501
+ .hw-dock-pill-dismiss:hover {
4502
+ color: var(--hw-dock-on-surface);
4503
+ }
4504
+ @media (prefers-reduced-motion: reduce) {
4505
+ .hw-dock-pill-dismiss {
4506
+ transition: none;
4507
+ }
4508
+ }
4509
+ /* Pointer-only hover cannot reveal anything on a touch screen, so there the × is
4510
+ simply always there. */
4511
+ @media (hover: none) {
4512
+ .hw-dock-pill-dismiss {
4513
+ opacity: 1;
4514
+ transform: none;
4515
+ }
4516
+ }
4517
+
4518
+ .hw-dock-pill {
4519
+ align-self: flex-start;
4520
+ padding: 10px 18px;
4521
+ /* A hairline is what separates a light pill from the light page behind it. */
4522
+ border: 1px solid var(--hw-dock-pill-border, transparent);
4523
+ border-radius: 999px;
4524
+ cursor: pointer;
4525
+ font: inherit;
4526
+ font-size: 14px;
4527
+ font-weight: 500;
4528
+ text-align: left;
4529
+ background: var(--hw-dock-pill-bg);
4530
+ color: var(--hw-dock-pill-fg);
4531
+ backdrop-filter: blur(10px) saturate(1.2);
4532
+ -webkit-backdrop-filter: blur(10px) saturate(1.2);
4533
+ /* Lift, not drama: enough shadow to sit above the page, not enough to compete
4534
+ with the composer. The old -8px/0.45 spread belonged to a black pill. */
4535
+ box-shadow: 0 4px 14px -8px rgba(12, 11, 10, 0.28);
4536
+ /* The entrance belongs to .hw-dock-pill-row now — the row carries the stagger
4537
+ delay, and animating both would slide the pill inside its own row. */
4538
+ transition:
4539
+ background 0.16s,
4540
+ border-color 0.16s,
4541
+ transform 0.16s;
4542
+ }
4543
+ /* Glass, like the panel and the toast: the pills sit over article text, and a
4544
+ flat translucent fill lets that text read straight through the label. */
4545
+ .hw-dock-pill {
4546
+ backdrop-filter: blur(18px);
4547
+ -webkit-backdrop-filter: blur(18px);
4548
+ }
4549
+ .hw-dock-pill:hover {
4550
+ background: color-mix(in srgb, var(--hw-dock-pill-bg) 100%, var(--hw-dock-on-surface) 8%);
4551
+ border-color: color-mix(in srgb, var(--hw-dock-pill-border) 100%, var(--hw-dock-on-surface) 10%);
4552
+ transform: translateX(3px);
4553
+ }
4554
+ @keyframes hw-dock-pill-in {
4555
+ from { opacity: 0; transform: translateY(10px) scale(0.97); }
4556
+ to { opacity: 1; transform: none; }
4557
+ }
4558
+
4559
+ /* Narrower at rest, the panel's measure once open — see --hw-dock-bar-width. */
4560
+ .hw-dock-bar {
4561
+ display: flex;
4562
+ align-items: flex-end;
4563
+ gap: 10px;
4564
+ width: var(--hw-dock-bar-width, var(--hw-dock-width));
4565
+ max-width: calc(100vw - 32px);
4566
+ min-height: 52px;
4567
+ padding: 10px 10px 10px 18px;
4568
+ border: 0;
4569
+ border-radius: 26px;
4570
+ background: var(--hw-dock-surface);
4571
+ color: var(--hw-dock-on-surface);
4572
+ box-shadow:
4573
+ inset 0 1px 0 color-mix(in srgb, var(--hw-dock-on-surface) 9%, transparent),
4574
+ 0 0 0 1px color-mix(in srgb, var(--hw-dock-on-surface) 7%, transparent),
4575
+ 0 2px 6px rgba(12, 11, 10, 0.18),
4576
+ 0 20px 48px -14px rgba(12, 11, 10, 0.45);
4577
+ /* Only the ring warms on focus — the ground never changes, so one
4578
+ property transitions instead of the whole field flashing. Width joins it on
4579
+ the panel's own curve: the bar snapping from 480 to 600 the instant the
4580
+ panel appeared read as two separate things moving, rather than one column
4581
+ widening. */
4582
+ transition:
4583
+ border-radius 0.18s ease,
4584
+ box-shadow 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
4585
+ width 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
4586
+ }
4587
+ .hw-dock-bar:focus-within {
4588
+ box-shadow:
4589
+ inset 0 1px 0 color-mix(in srgb, var(--hw-dock-on-surface) 9%, transparent),
4590
+ 0 0 0 1px color-mix(in srgb, var(--hw-primary, #fb923c) 60%, transparent),
4591
+ 0 2px 6px rgba(12, 11, 10, 0.18),
4592
+ 0 20px 48px -14px rgba(12, 11, 10, 0.45);
4593
+ }
4594
+ /* The shape follows the content instead of snapping. */
4595
+ .hw-dock-bar[data-grown="true"] { border-radius: 20px; }
4596
+
4597
+ .hw-dock-input {
4598
+ flex: 1;
4599
+ min-width: 0;
4600
+ min-height: 20px;
4601
+ max-height: var(--hw-bar-max-height);
4602
+ padding: 6px 0;
4603
+ border: 0;
4604
+ outline: none;
4605
+ resize: none;
4606
+ overflow-y: auto;
4607
+ background: transparent;
4608
+ color: var(--hw-dock-on-surface);
4609
+ font: inherit;
4610
+ font-size: 14px;
4611
+ line-height: 1.45;
4612
+ transition: height 0.12s ease;
4613
+ }
4614
+ .hw-dock-input::placeholder {
4615
+ color: color-mix(in srgb, var(--hw-dock-on-surface) 45%, transparent);
4616
+ }
4617
+
4618
+ .hw-dock-send {
4619
+ flex: none;
4620
+ width: 32px;
4621
+ height: 32px;
4622
+ margin-bottom: 1px;
4623
+ border: 0;
4624
+ border-radius: 50%;
4625
+ display: grid;
4626
+ place-items: center;
4627
+ cursor: pointer;
4628
+ background: var(--hw-primary, #fb923c);
4629
+ color: var(--hw-on-brand, #fff);
4630
+ transition: transform 0.16s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.16s;
4631
+ }
4632
+ .hw-dock-send:hover:not(:disabled) { transform: scale(1.06); }
4633
+ .hw-dock-send:disabled { opacity: 0.45; cursor: default; }
4634
+
4635
+ /* The panel sits above the bar and carries neither composer nor tabs. */
4636
+ /* .hw-widget is a 20x20 fixed box in the page corner and .hw-window is
4637
+ absolute inside it, so centring on `left: 50%` centres on that box —
4638
+ i.e. the corner. The docked panel has to leave that coordinate space
4639
+ and centre on the viewport. margin-left rather than a transform,
4640
+ because .hw-window animates transform on open and the two would fight. */
4641
+ .hw-widget[data-hw-placement="docked"] .hw-window {
4642
+ position: fixed;
4643
+ left: 50%;
4644
+ right: auto;
4645
+ margin-left: calc(var(--hw-dock-width) / -2);
4646
+ bottom: calc(var(--hw-dock-inset) + 66px);
4647
+ width: var(--hw-dock-width);
4648
+ max-width: calc(100vw - 32px);
4649
+ height: 420px;
4650
+ }
4651
+ @media (max-width: 640px) {
4652
+ .hw-widget[data-hw-placement="docked"] .hw-window {
4653
+ left: 16px;
4654
+ right: 16px;
4655
+ margin-left: 0;
4656
+ width: auto;
4657
+ }
4658
+ }
4659
+ .hw-widget[data-hw-placement="docked"] .hw-bubble { display: none; }
4660
+
4661
+ /* ── Open state ────────────────────────────────────────────────────────────
4662
+ Panel and bar stay two objects, deliberately.
4663
+
4664
+ They were merged into one card for a while — the bar squared its top corners
4665
+ and slid up against the panel's foot. It read as one surface until you looked
4666
+ at the seam: two elements with their own 1px rings, meeting on a line neither
4667
+ owned, so the ring of the wider-shadowed one printed a hairline past the
4668
+ other's edge. Fixing the seam meant reconciling two shadow systems for a join
4669
+ that was never load-bearing.
4670
+
4671
+ Separate is also what the reference does, and it is truer: the bar belongs to
4672
+ the page (it is there before any thread exists and stays after this one folds
4673
+ away), the panel belongs to the conversation. Keeping the gap says so, and the
4674
+ composer keeps its pill — the shape that invited the first message.
4675
+
4676
+ Open, the panel only has to clear the bar: the dock's inset, the bar itself
4677
+ (52px min-height), and the air between the two. There used to be a fourth term
4678
+ for the attribution line under the bar — that line is gone, and so is the term. */
4679
+ .hw-widget[data-hw-placement="docked"][data-hw-open="true"] .hw-window {
4680
+ bottom: calc(var(--hw-dock-inset) + 52px + 12px);
4681
+ }
4682
+ /* The suggestion pills belong to the closed state: they are the ways in. Once a
4683
+ thread exists the panel carries the conversation, so a stack of pills between
4684
+ the panel and its own composer would be arguing with it. */
4685
+ .hw-widget[data-hw-placement="docked"][data-hw-open="true"] .hw-dock-suggestions {
4686
+ display: none;
4687
+ }
4688
+
4689
+ @media (prefers-reduced-motion: reduce) {
4690
+ .hw-dock-pill, .hw-dock-bar, .hw-dock-input, .hw-dock-send { animation: none; transition: none; }
4691
+ }
4692
+
4693
+ /* The bar is the composer, so the panel above it carries neither its own
4694
+ composer nor the tab bar. */
4695
+ .hw-widget[data-hw-placement="docked"] .hw-composer,
4696
+ .hw-widget[data-hw-placement="docked"] .hw-tabs,
4697
+ .hw-widget[data-hw-placement="docked"] .hw-footer-tabs {
4698
+ display: none;
4699
+ }
4700
+
4701
+ /* Starters — offered under the greeting until the visitor speaks. A blank
4702
+ thread with a blank field is a dead end; three labelled ways in are not.
4703
+ Ported from the bench (docs/prototypes/widget-bench.html). */
4704
+ /* One card, hairline-separated rows — the Fin shape. Three separately
4705
+ ringed boxes stacked with a gap read as three unrelated controls; a
4706
+ single grouped surface reads as one list you pick from. */
4707
+ .hw-starters {
4708
+ display: flex;
4709
+ flex-direction: column;
4710
+ margin-top: 10px;
4711
+ border-radius: 14px;
4712
+ overflow: hidden;
4713
+ background: var(--hw-bg);
4714
+ box-shadow: inset 0 0 0 1px var(--hw-border);
4715
+ }
4716
+ .hw-starter {
4717
+ display: flex;
4718
+ align-items: center;
4719
+ gap: 10px;
4720
+ width: 100%;
4721
+ padding: 11px 14px;
4722
+ border: 0;
4723
+ border-radius: 0;
4724
+ cursor: pointer;
4725
+ background: transparent;
4726
+ color: var(--hw-text);
4727
+ font: inherit;
4728
+ text-align: left;
4729
+ animation: hw-starter-in 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) both;
4730
+ transition: background 0.12s;
4731
+ }
4732
+ /* Separator on the row, not the container, so it never draws under the last
4733
+ one and never doubles up against the card's own ring. */
4734
+ .hw-starter + .hw-starter {
4735
+ box-shadow: inset 0 1px 0 var(--hw-border);
4736
+ }
4737
+ .hw-starter:hover {
4738
+ background: var(--hw-bg-hover);
4739
+ }
4740
+ .hw-starter-text {
4741
+ flex: 1;
4742
+ min-width: 0;
4743
+ display: flex;
4744
+ flex-direction: column;
4745
+ gap: 1px;
4746
+ }
4747
+ .hw-starter-k {
4748
+ font-size: 13px;
4749
+ font-weight: 550;
4750
+ line-height: 1.35;
4751
+ color: var(--hw-text);
4752
+ }
4753
+ .hw-starter-q {
4754
+ font-size: 12px;
4755
+ line-height: 1.35;
4756
+ color: var(--hw-text-3, var(--hw-text-muted));
4757
+ }
4758
+ .hw-starter svg {
4759
+ flex: none;
4760
+ color: var(--hw-text-3, var(--hw-text-muted));
4761
+ opacity: 0.45;
4762
+ transition: opacity 0.15s, transform 0.15s;
4763
+ }
4764
+ .hw-starter:hover svg { opacity: 0.9; transform: translateX(2px); }
4765
+ @keyframes hw-starter-in {
4766
+ from { opacity: 0; transform: translateY(6px); }
4767
+ to { opacity: 1; transform: none; }
4768
+ }
4769
+
4770
+ /* The docked panel is one surface deep — there is nothing to go back to,
4771
+ the bar is always right there. */
4772
+ .hw-widget[data-hw-placement="docked"] .hw-header-back { display: none; }
4773
+
4774
+ @media (prefers-reduced-motion: reduce) {
4775
+ .hw-starter { animation: none; }
4776
+ }
4777
+
4778
+
4779
+ /* Docked shell in dark. Guarded like every other dark block so an explicit
4780
+ data-theme="light" still wins. */
4781
+ @media (prefers-color-scheme: dark) {
4782
+ .hw-widget:not([data-theme="light"])[data-hw-placement="docked"] {
4783
+ --hw-dock-surface: #1f1f23;
4784
+ --hw-dock-on-surface: #f4f4f6;
4785
+ --hw-dock-pill-bg: rgba(255, 255, 255, 0.1);
4786
+ --hw-dock-pill-fg: #e4e4e7;
4787
+ --hw-dock-pill-border: rgba(255, 255, 255, 0.14);
4788
+ }
4789
+ }
4790
+ .hw-widget[data-theme="dark"][data-hw-placement="docked"] {
4791
+ --hw-dock-surface: #1f1f23;
4792
+ --hw-dock-on-surface: #f4f4f6;
4793
+ }
4794
+
4795
+ /* ── Docked panel: one glass card ──────────────────────────────────────────
4796
+ The docked shell lives on a documentation page, over prose the reader chose
4797
+ to be on. A second opaque white card on a white page is a page fighting
4798
+ itself: two grounds at the same depth, neither obviously on top.
4799
+
4800
+ So the panel is glass. It picks up whatever it is over — white body copy at
4801
+ the top of the viewport, a tinted section lower down — which is what makes it
4802
+ read as floating ABOVE the page rather than punched INTO it, and it keeps the
4803
+ article visible enough that the reader has not lost their place.
4804
+
4805
+ Glass forces the palette: dark ink on 60%-transparent grey is unreadable over
4806
+ dark page content, so the panel flips its own token block to a light-on-dark
4807
+ set. Tokens rather than per-component overrides — every child already reads
4808
+ through them, and the alternative is finding all forty of them by hand. */
4809
+ .hw-widget[data-hw-placement="docked"] .hw-window {
4810
+ --hw-bg: rgba(255, 255, 255, 0.08);
4811
+ /* The scroller and the panel are one surface here — a second ground inside
4812
+ the glass would show as a rectangle floating in it. */
4813
+ --hw-bg-subtle: transparent;
4814
+ --hw-bg-hover: rgba(255, 255, 255, 0.1);
4815
+ --hw-border: rgba(255, 255, 255, 0.14);
4816
+ --hw-border-md: rgba(255, 255, 255, 0.22);
4817
+ --hw-text: rgba(255, 255, 255, 0.94);
4818
+ --hw-text-1: #ffffff;
4819
+ --hw-text-2: rgba(255, 255, 255, 0.72);
4820
+ --hw-text-3: rgba(255, 255, 255, 0.62);
4821
+ --hw-text-4: rgba(255, 255, 255, 0.46);
4822
+ --hw-visitor-bubble-bg: rgba(255, 255, 255, 0.16);
4823
+ --hw-visitor-bubble-text: #ffffff;
4824
+ /* Brand tiles and CTAs sit on the glass, not on the page, so they invert
4825
+ with it — a near-black avatar on dark glass is a hole. */
4826
+ --hw-brand: rgba(255, 255, 255, 0.92);
4827
+ --hw-on-brand: #17161a;
4828
+ --hw-focus-border: rgba(255, 255, 255, 0.34);
4829
+ --hw-focus-ring: rgba(255, 255, 255, 0.1);
4830
+ height: 500px;
4831
+ max-height: calc(100vh - 180px);
4832
+ border-radius: 24px;
4833
+ color: var(--hw-text);
4834
+ /* Three stops rather than a flat fill: the card is tall enough that a single
4835
+ alpha reads as a grey slab, and the gradient keeps the top honest to the
4836
+ page behind it while the foot settles into the composer's shadow. */
4837
+ background: linear-gradient(
4838
+ 180deg,
4839
+ rgba(40, 40, 44, 0.72) 0%,
4840
+ rgba(46, 46, 50, 0.7) 55%,
4841
+ rgba(52, 52, 56, 0.68) 100%
4842
+ );
4843
+ /* Blur, no saturation boost. Boosting it made the glass adopt the hue of
4844
+ whatever it was over — over a warm page section the whole card went olive,
4845
+ which is a different product every time you scroll. Neutral grey holds. */
4846
+ backdrop-filter: blur(30px);
4847
+ -webkit-backdrop-filter: blur(30px);
4848
+ box-shadow:
4849
+ 0 24px 60px -20px rgba(12, 11, 10, 0.5),
4850
+ inset 0 0 0 1px rgba(255, 255, 255, 0.1);
4851
+ }
4852
+ /* Without blur support the same alpha would show the article straight through
4853
+ the text. Opaque is worse-looking and still readable; unreadable is not a
4854
+ trade we get to make. */
4855
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
4856
+ .hw-widget[data-hw-placement="docked"] .hw-window {
4857
+ background: #2c2c2e;
4858
+ }
4859
+ }
4860
+ @media (max-width: 640px) {
4861
+ .hw-widget[data-hw-placement="docked"] .hw-window {
4862
+ height: min(60vh, 460px);
4863
+ }
4864
+ }
4865
+
4866
+ /* Header: avatar, name, dismiss. The subtitle ("usually replies in minutes")
4867
+ is a promise the closed bar has already made — repeating it inside the panel
4868
+ spends the header's second line on nothing and pushes the first message down.
4869
+ One line, so the name and the glass share the weight. */
4870
+ .hw-widget[data-hw-placement="docked"] .hw-header--sub {
4871
+ padding: 14px 12px 8px 16px;
4872
+ }
4873
+ .hw-widget[data-hw-placement="docked"] .hw-header-status {
4874
+ display: none;
4875
+ }
4876
+ /* 640 at 16px on pure white over dark glass reads as a shout: the name is the
4877
+ quietest thing in the header hierarchy, not the loudest. Weight carries the
4878
+ emphasis at this size, so it drops and the tracking relaxes with it. */
4879
+ .hw-widget[data-hw-placement="docked"] .hw-header-name {
4880
+ font-size: 15.5px;
4881
+ font-weight: 560;
4882
+ letter-spacing: -0.006em;
4883
+ color: var(--hw-text-1);
4884
+ }
4885
+
4886
+ /* Same reasoning one level down. These are list rows and inline sentences, and
4887
+ 600 on near-white makes every one of them compete with the header. */
4888
+ .hw-widget[data-hw-placement="docked"] .hw-header-menu-conv-name,
4889
+ .hw-widget[data-hw-placement="docked"] .hw-event-name {
4890
+ font-weight: 545;
4891
+ }
4892
+ /* Round, like every other avatar in a chat surface — the 9px tile reads as a
4893
+ product logo, and this is a person-shaped conversation. */
4894
+ .hw-widget[data-hw-placement="docked"] .hw-header-bot-icon {
4895
+ border-radius: 50%;
4896
+ overflow: hidden;
4897
+ }
4898
+ .hw-widget[data-hw-placement="docked"] .hw-header-close:hover,
4899
+ .hw-widget[data-hw-placement="docked"] .hw-header-menu-btn:hover {
4900
+ background: rgba(255, 255, 255, 0.12);
4901
+ color: #ffffff;
4902
+ }
4903
+
4904
+ /* The thread scrolls under the header rather than stopping at a rule: a border
4905
+ would cut the glass in two. The mask fades the last few pixels instead, so a
4906
+ message sliding up dissolves into the header's ground. */
4907
+ .hw-widget[data-hw-placement="docked"] .hw-messages {
4908
+ /* Wider gutters than the bubble shell: at 600px the panel is a column of
4909
+ prose, and prose set flush to a 600px edge reads as a wall. */
4910
+ padding: 4px 22px 16px;
4911
+ gap: 18px;
4912
+ -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 16px);
4913
+ mask-image: linear-gradient(180deg, transparent 0, #000 16px);
4914
+ }
4915
+
4916
+ /* An answer IS the panel's content, so it carries no bubble — a pill around
4917
+ every reply on a glass card stacks two translucent surfaces and dulls both.
4918
+ This holds whoever is answering: a human's reply is the same kind of content
4919
+ as the AI's, and giving it a pill made a run of team messages read as a
4920
+ column of grey slabs. The visitor's own message keeps its pill — with both
4921
+ agents unboxed, the pill is what distinguishes who said what. */
4922
+ .hw-widget[data-hw-placement="docked"] .hw-msg--ai .hw-msg-bubble,
4923
+ .hw-widget[data-hw-placement="docked"] .hw-msg--team .hw-msg-bubble {
4924
+ padding: 0;
4925
+ background: transparent;
4926
+ border-radius: 0;
4927
+ /* An unboxed answer is body copy, so it carries a little more than the 14px
4928
+ bubble — but only a little. At 16px a four-line answer filled half the card
4929
+ and read as a slide rather than a reply. */
4930
+ font-size: 15px;
4931
+ line-height: 1.55;
4932
+ color: var(--hw-text);
4933
+ }
4934
+ /* The agent signs the panel once, in the header. Repeating "Better AI · 10:56 PM"
4935
+ under every reply restated the same two facts on every turn — and a multi-part
4936
+ answer restated them three times in a row, which is how the meta line stopped
4937
+ being read at all. A human agent keeps their line: their name is news (someone
4938
+ took over) in a way the AI's is not. */
4939
+ .hw-widget[data-hw-placement="docked"] .hw-msg--ai .hw-msg-meta {
4940
+ display: none;
4941
+ }
4942
+ .hw-widget[data-hw-placement="docked"] .hw-msg--visitor .hw-msg-bubble {
4943
+ background: rgba(255, 255, 255, 0.16);
4944
+ color: #ffffff;
4945
+ font-size: 14.5px;
4946
+ line-height: 1.5;
4947
+ }
4948
+ /* Code and quotes inside an answer need their own ground back — on glass the
4949
+ 6%-black wash they use in light mode is invisible. */
4950
+ .hw-widget[data-hw-placement="docked"] .hw-msg-bubble pre,
4951
+ .hw-widget[data-hw-placement="docked"] .hw-msg-bubble code {
4952
+ background: rgba(0, 0, 0, 0.28);
4953
+ color: var(--hw-text-1);
4954
+ }
4955
+
4956
+ /* The gate's field reads --hw-bg-subtle, which the panel deliberately zeroes out
4957
+ (the scroller is the glass). Left transparent it was a placeholder floating in
4958
+ the card. The ground sits on the row now, since the row is the field. */
4959
+ .hw-widget[data-hw-placement="docked"] .hw-email-card-row {
4960
+ background: rgba(0, 0, 0, 0.26);
4961
+ border-color: rgba(255, 255, 255, 0.14);
4962
+ }
4963
+ /* The widget's global focus ring is `2px solid var(--hw-primary)` — the brand
4964
+ colour, near-black by default. On the glass panel that painted a hard black
4965
+ rectangle around whatever was focused, and around the email field it landed
4966
+ INSIDE the pill, cutting a square through a round shape. The panel is a dark
4967
+ surface, so its focus ring is light; the email row keeps showing focus through
4968
+ its own border (`:focus-within`), so the field inside it does not draw a second
4969
+ one on top. */
4970
+ .hw-widget[data-hw-placement="docked"] .hw-window :focus-visible {
4971
+ outline-color: rgba(255, 255, 255, 0.55);
4972
+ }
4973
+ .hw-widget[data-hw-placement="docked"] .hw-email-card-input:focus,
4974
+ .hw-widget[data-hw-placement="docked"] .hw-email-card-input:focus-visible {
4975
+ outline: none;
4976
+ box-shadow: none;
4977
+ }
4978
+ /* Killing the outline can't take the focus state with it. The docked border-color
4979
+ above out-specifies the base `:focus-within` rule, so the row states its own —
4980
+ the pill brightens rather than growing a rectangle. */
4981
+ .hw-widget[data-hw-placement="docked"] .hw-email-card-row:focus-within {
4982
+ border-color: rgba(255, 255, 255, 0.42);
4983
+ box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.08);
4984
+ }
4985
+ .hw-widget[data-hw-placement="docked"] .hw-email-card-submit {
4986
+ background: #ffffff;
4987
+ color: #17161a;
4988
+ }
4989
+
4990
+ /* The options menu opens over article text, so it is the one surface here that
4991
+ cannot be 8% white: at that fill the preview lines read straight through to
4992
+ whatever paragraph is behind them. */
4993
+ /* The thread was painting straight through the menu — message bubbles readable on
4994
+ top of the conversation rows. Not transparency: a stacking bug I introduced with
4995
+ the settle animation. Both the header and the message list animate a transform,
4996
+ and a transformed element becomes its own stacking context painted at z-index 0,
4997
+ so the two siblings tied and DOM order decided it — the list, coming second, won,
4998
+ and the menu's z-index 6 was trapped inside the header's context. The header is
4999
+ explicitly the upper layer now, which is what it always was visually. */
5000
+ .hw-widget[data-hw-placement="docked"] .hw-window .hw-header--sub {
5001
+ position: relative;
5002
+ z-index: 3;
5003
+ }
5004
+ /* And the menu itself is opaque. It was 90% over glass over article text — three
5005
+ translucent layers, so the rows read as a colour rather than as a list. */
5006
+ .hw-widget[data-hw-placement="docked"] .hw-header-menu {
5007
+ /* Lighter than the panel, not darker. The menu floats above the glass, and
5008
+ in a dark UI a raised surface catches more light — at #2a2a2e it sat
5009
+ below its own parent and read as a hole punched in the card. */
5010
+ background: #3a3a3f;
5011
+ box-shadow:
5012
+ 0 0 0 1px rgba(255, 255, 255, 0.16),
5013
+ 0 18px 44px -12px rgba(12, 11, 10, 0.62);
5014
+ }
5015
+ .hw-widget[data-hw-placement="docked"] .hw-header-menu button:hover {
5016
+ background: rgba(255, 255, 255, 0.11);
5017
+ }
5018
+ .hw-widget[data-hw-placement="docked"] .hw-header-menu button.hw-header-menu-conv[data-active="true"] {
5019
+ background: rgba(255, 255, 255, 0.09);
5020
+ }
5021
+ .hw-widget[data-hw-placement="docked"] .hw-header-menu-convs {
5022
+ border-bottom-color: rgba(255, 255, 255, 0.12);
5023
+ }
5024
+
5025
+ /* Team-join / assignment rows read --hw-bg-subtle for their ground, which the
5026
+ panel zeroes out — so "Ali Osman joined the conversation" arrived as floating
5027
+ text with no card around it. Same translucent white the other inserts use. */
5028
+ .hw-widget[data-hw-placement="docked"] .hw-event--card {
5029
+ margin: 0;
5030
+ background: rgba(255, 255, 255, 0.08);
5031
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
5032
+ }
5033
+ .hw-widget[data-hw-placement="docked"] .hw-event-avatar {
5034
+ background: rgba(255, 255, 255, 0.14);
5035
+ color: var(--hw-text-1);
5036
+ }
5037
+
5038
+ /* The email gate is an insert in the thread, so it is built from the same two
5039
+ materials as everything else on the glass: a translucent white card and a
5040
+ hairline. Its own light-mode shadow read as a white halo here. */
5041
+ /* Nothing to restyle on the card itself any more — it is a message, and the panel's
5042
+ token block already covers its type. Only the field keeps a ground, below. */
5043
+
5044
+ /* The launcher-anchored toast has no launcher to anchor to in this shell — it
5045
+ was landing in the page corner, unrelated to the bar it belongs to. Same
5046
+ measure and the same resting place as the panel: whatever the agent says
5047
+ arrives in one spot, whether the panel is open or folded away. */
5048
+ .hw-widget[data-hw-placement="docked"] .hw-preview {
5049
+ position: fixed;
5050
+ left: 50%;
5051
+ right: auto;
5052
+ /* The bar's measure, not the panel's — this card only ever appears while the
5053
+ panel is folded away, so matching 600 left it overhanging the 480 bar it
5054
+ sits on top of. */
5055
+ margin-left: calc(var(--hw-dock-bar-width, var(--hw-dock-width)) / -2);
5056
+ bottom: calc(var(--hw-dock-inset) + 52px + 12px);
5057
+ width: var(--hw-dock-bar-width, var(--hw-dock-width));
5058
+ max-width: calc(100vw - 32px);
5059
+ border: 0;
5060
+ border-radius: 24px;
5061
+ color: rgba(255, 255, 255, 0.94);
5062
+ background: linear-gradient(180deg, rgba(40, 40, 44, 0.74) 0%, rgba(52, 52, 56, 0.7) 100%);
5063
+ backdrop-filter: blur(30px);
5064
+ -webkit-backdrop-filter: blur(30px);
5065
+ box-shadow:
5066
+ 0 24px 60px -20px rgba(12, 11, 10, 0.5),
5067
+ inset 0 0 0 1px rgba(255, 255, 255, 0.1);
5068
+ }
5069
+ .hw-widget[data-hw-placement="docked"] .hw-preview-name,
5070
+ .hw-widget[data-hw-placement="docked"] .hw-preview-body {
5071
+ color: rgba(255, 255, 255, 0.94);
5072
+ }
5073
+ .hw-widget[data-hw-placement="docked"] .hw-preview-close {
5074
+ color: rgba(255, 255, 255, 0.6);
5075
+ }
5076
+ .hw-widget[data-hw-placement="docked"] .hw-preview-close:hover {
5077
+ background: rgba(255, 255, 255, 0.12);
5078
+ color: #ffffff;
5079
+ }
5080
+ .hw-widget[data-hw-placement="docked"] .hw-preview-footer {
5081
+ color: rgba(255, 255, 255, 0.55);
5082
+ border-top-color: rgba(255, 255, 255, 0.1);
5083
+ }
5084
+ @media (max-width: 640px) {
5085
+ .hw-widget[data-hw-placement="docked"] .hw-preview {
5086
+ left: 16px;
5087
+ right: 16px;
5088
+ margin-left: 0;
5089
+ width: auto;
5090
+ }
5091
+ }
5092
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
5093
+ .hw-widget[data-hw-placement="docked"] .hw-preview {
5094
+ background: #2c2c2e;
5095
+ }
5096
+ }
5097
+
5098
+ /* "Talk to a human" is the only control the agent hands over mid-thread, and every
5099
+ translucent-white version of it read as a slightly lighter patch of the card —
5100
+ present, but not obviously pressable. It is opaque now: the same white the
5101
+ composer is made of, dark ink, its own small shadow. On a card where everything
5102
+ else is glass, the one solid object is the one you can press. */
5103
+ .hw-widget[data-hw-placement="docked"] .hw-quick-reply-btn {
5104
+ padding: 7px 14px;
5105
+ font-size: 13px;
5106
+ font-weight: 550;
5107
+ border-radius: 999px;
5108
+ background: rgba(255, 255, 255, 0.94);
5109
+ box-shadow: 0 1px 3px rgba(12, 11, 10, 0.22);
5110
+ color: #17161a;
5111
+ }
5112
+ .hw-widget[data-hw-placement="docked"] .hw-quick-reply-btn:hover:not(:disabled) {
5113
+ background: #ffffff;
5114
+ box-shadow: 0 4px 12px rgba(12, 11, 10, 0.28);
5115
+ transform: translateY(-1px);
5116
+ }
5117
+ .hw-widget[data-hw-placement="docked"] .hw-quick-reply-btn[data-tapped="true"] {
5118
+ background: rgba(255, 255, 255, 0.22);
5119
+ color: #ffffff;
5120
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
5121
+ transform: none;
5122
+ }
5123
+ .hw-widget[data-hw-placement="docked"] .hw-quick-reply-btn svg {
5124
+ opacity: 0.55;
5125
+ }
5126
+ /* The escalation offer ("Talk to a human") is the same kind of object as a quick
5127
+ reply — the agent handing the visitor a way out — and it was reading as the same
5128
+ washed patch, because .hw-offer takes --hw-bg-hover, which is white at 10% on
5129
+ the glass. Same solid treatment, one rule for both so they never drift. */
5130
+ .hw-widget[data-hw-placement="docked"] .hw-offer {
5131
+ padding: 7px 14px;
5132
+ font-size: 13px;
5133
+ background: rgba(255, 255, 255, 0.94);
5134
+ color: #17161a;
5135
+ box-shadow: 0 1px 3px rgba(12, 11, 10, 0.22);
5136
+ transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
5137
+ }
5138
+ .hw-widget[data-hw-placement="docked"] .hw-offer:hover {
5139
+ background: #ffffff;
5140
+ color: #17161a;
5141
+ box-shadow: 0 4px 12px rgba(12, 11, 10, 0.28);
5142
+ transform: translateY(-1px);
5143
+ }
5144
+ .hw-widget[data-hw-placement="docked"] .hw-offer svg {
5145
+ opacity: 0.55;
5146
+ }
5147
+
5148
+ /* ── The bar is made of the same glass ─────────────────────────────────────
5149
+ A white pill under a grey card was two materials stacked, and the giveaway was
5150
+ its ring: --hw-dock-on-surface is near-black in light mode, so the hairline that
5151
+ reads as a soft edge on a white page printed a hard dark line against the panel
5152
+ above it. Same glass, and the ring inverts with it — one object in two parts.
5153
+
5154
+ Flipping the token on the bar rather than restyling six children: the field,
5155
+ the placeholder, the tools and the disabled send all derive from it. */
5156
+ .hw-widget[data-hw-placement="docked"] .hw-dock-bar {
5157
+ --hw-dock-on-surface: rgba(255, 255, 255, 0.94);
5158
+ color: rgba(255, 255, 255, 0.94);
5159
+ background: linear-gradient(180deg, rgba(44, 44, 48, 0.74) 0%, rgba(52, 52, 56, 0.72) 100%);
5160
+ backdrop-filter: blur(30px);
5161
+ -webkit-backdrop-filter: blur(30px);
5162
+ box-shadow:
5163
+ inset 0 0 0 1px rgba(255, 255, 255, 0.12),
5164
+ 0 18px 40px -16px rgba(12, 11, 10, 0.45);
5165
+ }
5166
+ .hw-widget[data-hw-placement="docked"][data-hw-open="true"] .hw-dock-bar {
5167
+ width: var(--hw-dock-width);
5168
+ }
5169
+ .hw-widget[data-hw-placement="docked"] .hw-dock-bar:focus-within {
5170
+ box-shadow:
5171
+ inset 0 0 0 1px rgba(255, 255, 255, 0.26),
5172
+ 0 18px 40px -16px rgba(12, 11, 10, 0.5);
5173
+ }
5174
+ /* Open, the pair reads as one object, so the bar's shadow deepens with the panel's
5175
+ instead of each casting its own. */
5176
+ .hw-widget[data-hw-placement="docked"][data-hw-open="true"] .hw-dock-bar {
5177
+ box-shadow:
5178
+ inset 0 0 0 1px rgba(255, 255, 255, 0.14),
5179
+ 0 22px 52px -18px rgba(12, 11, 10, 0.52);
5180
+ }
5181
+ .hw-widget[data-hw-placement="docked"] .hw-dock-send:not(:disabled) {
5182
+ background: #ffffff;
5183
+ color: #17161a;
5184
+ }
5185
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
5186
+ .hw-widget[data-hw-placement="docked"] .hw-dock-bar {
5187
+ background: #2c2c2e;
5188
+ }
5189
+ }
5190
+
5191
+ /* ── Open / close ──────────────────────────────────────────────────────────
5192
+ The bubble shell opens with an overshoot — a launcher is a toy you poked and
5193
+ it bounces back. The docked panel is not that: it unfolds out of a bar the
5194
+ reader was already typing in, over prose they are still reading, so it rises
5195
+ from the bar's edge and settles. No overshoot, transform-origin at the foot,
5196
+ and short enough (200ms) that it never feels like waiting. */
5197
+ .hw-widget[data-hw-placement="docked"] .hw-window {
5198
+ transform-origin: 50% 100%;
5199
+ animation: hw-dock-panel-in 0.3s cubic-bezier(0.16, 1, 0.3, 1);
5200
+ }
5201
+ .hw-widget[data-hw-placement="docked"] .hw-window--closing {
5202
+ animation: hw-dock-panel-out 0.18s cubic-bezier(0.4, 0, 1, 1) forwards;
5203
+ }
5204
+ @keyframes hw-dock-panel-in {
5205
+ from { opacity: 0; transform: translateY(14px) scale(0.98); }
5206
+ to { opacity: 1; transform: none; }
5207
+ }
5208
+ @keyframes hw-dock-panel-out {
5209
+ from { opacity: 1; transform: none; }
5210
+ to { opacity: 0; transform: translateY(8px) scale(0.99); }
5211
+ }
5212
+ /* The card arrives, then what is in it. Two short delays rather than one motion:
5213
+ a panel whose header and thread land with the edge feels printed, and the eye
5214
+ has nowhere to start. 60ms and 110ms are below the threshold where a delay
5215
+ registers as waiting — it reads as the content settling, not as a sequence. */
5216
+ .hw-widget[data-hw-placement="docked"] .hw-window .hw-header--sub {
5217
+ animation: hw-dock-settle 0.32s cubic-bezier(0.16, 1, 0.3, 1) 0.06s both;
5218
+ }
5219
+ .hw-widget[data-hw-placement="docked"] .hw-window .hw-messages {
5220
+ animation: hw-dock-settle 0.34s cubic-bezier(0.16, 1, 0.3, 1) 0.11s both;
5221
+ }
5222
+ /* Closing is one gesture: the parts do not leave one at a time. */
5223
+ .hw-widget[data-hw-placement="docked"] .hw-window--closing .hw-header--sub,
5224
+ .hw-widget[data-hw-placement="docked"] .hw-window--closing .hw-messages {
5225
+ animation: none;
5226
+ }
5227
+ @keyframes hw-dock-settle {
5228
+ from { opacity: 0; transform: translateY(7px); }
5229
+ to { opacity: 1; transform: none; }
5230
+ }
5231
+ @media (prefers-reduced-motion: reduce) {
5232
+ .hw-widget[data-hw-placement="docked"] .hw-window,
5233
+ .hw-widget[data-hw-placement="docked"] .hw-window--closing,
5234
+ .hw-widget[data-hw-placement="docked"] .hw-window .hw-header--sub,
5235
+ .hw-widget[data-hw-placement="docked"] .hw-window .hw-messages {
5236
+ animation: none;
5237
+ }
5238
+ }
5239
+
5240
+ /* ── Composer: the tool row ────────────────────────────────────────────────
5241
+ The bar is the only composer the docked shell has, so it is also the only
5242
+ place a visitor can hand over a screenshot — which is how a good half of
5243
+ support threads actually start. Clip and mic sit beside the send button in
5244
+ the order the reference uses; both are real (the mic renders only where the
5245
+ browser can transcribe, the clip only when the host wired attachments). */
5246
+ .hw-dock-bar {
5247
+ /* Lets the preview strip claim its own line above the field instead of
5248
+ squeezing the caption into a sliver. */
5249
+ flex-wrap: wrap;
5250
+ padding-left: 18px;
5251
+ padding-right: 8px;
5252
+ }
5253
+ /* Why the send did not happen, on the bar's own measure. Reads as a note from the
5254
+ composer rather than an error dialog: one line, no icon, gone the moment the
5255
+ visitor types the caption it is asking for. */
5256
+ .hw-dock-error {
5257
+ width: var(--hw-dock-width);
5258
+ max-width: calc(100vw - 32px);
5259
+ margin-bottom: 8px;
5260
+ padding: 8px 14px;
5261
+ border-radius: 14px;
5262
+ background: rgba(190, 24, 40, 0.92);
5263
+ color: #ffffff;
5264
+ font-size: 12.5px;
5265
+ line-height: 1.35;
5266
+ box-shadow: 0 10px 26px -12px rgba(12, 11, 10, 0.5);
5267
+ animation: hw-dock-settle 0.22s cubic-bezier(0.16, 1, 0.3, 1) both;
5268
+ }
5269
+ @media (prefers-reduced-motion: reduce) {
5270
+ .hw-dock-error { animation: none; }
5271
+ }
5272
+
5273
+ .hw-dock-previews {
5274
+ flex: 0 0 100%;
5275
+ order: -1;
5276
+ display: flex;
5277
+ flex-wrap: wrap;
5278
+ gap: 8px;
5279
+ padding: 2px 0 8px;
5280
+ }
5281
+ .hw-dock-actions {
5282
+ flex: none;
5283
+ display: flex;
5284
+ align-items: center;
5285
+ gap: 2px;
5286
+ margin-bottom: 1px;
5287
+ }
5288
+ .hw-dock-tool {
5289
+ display: grid;
5290
+ place-items: center;
5291
+ width: 30px;
5292
+ height: 30px;
5293
+ border: 0;
5294
+ border-radius: 50%;
5295
+ background: transparent;
5296
+ color: color-mix(in srgb, var(--hw-dock-on-surface) 52%, transparent);
5297
+ cursor: pointer;
5298
+ transition: background 0.15s, color 0.15s;
5299
+ }
5300
+ .hw-dock-tool:hover:not(:disabled) {
5301
+ background: color-mix(in srgb, var(--hw-dock-on-surface) 8%, transparent);
5302
+ color: var(--hw-dock-on-surface);
5303
+ }
5304
+ .hw-dock-tool:disabled {
5305
+ opacity: 0.4;
5306
+ cursor: default;
5307
+ }
5308
+ /* Recording is a state the visitor must be able to see at a glance — the mic is
5309
+ live and the page is listening. Red, and it breathes. */
5310
+ .hw-dock-tool[data-active="true"] {
5311
+ color: #e11d48;
5312
+ background: rgba(225, 29, 72, 0.12);
5313
+ animation: hw-dock-listening 1.4s ease-in-out infinite;
5314
+ }
5315
+ @keyframes hw-dock-listening {
5316
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(225, 29, 72, 0.35); }
5317
+ 50% { box-shadow: 0 0 0 4px rgba(225, 29, 72, 0); }
5318
+ }
5319
+ /* Nothing to send yet: a neutral disc, not the brand disc at 45% — a faded
5320
+ brand color reads as "broken button" where grey reads as "not yet". */
5321
+ .hw-dock-send:disabled {
5322
+ opacity: 1;
5323
+ background: color-mix(in srgb, var(--hw-dock-on-surface) 9%, transparent);
5324
+ color: color-mix(in srgb, var(--hw-dock-on-surface) 38%, transparent);
5325
+ }
5326
+ @media (prefers-reduced-motion: reduce) {
5327
+ .hw-dock-tool[data-active="true"] { animation: none; }
5328
+ /* The bar still changes measure between folded and open; it just gets there
5329
+ without the tween. */
5330
+ .hw-dock-bar { transition: none; }
5331
+ }
5332
+
5333
+ /* The host page's global :focus-visible was painting a rectangle inside
5334
+ the bar. The bar already shows focus by warming its own ring. */
5335
+ .hw-dock-input:focus,
5336
+ .hw-dock-input:focus-visible {
5337
+ outline: none;
5338
+ box-shadow: none;
5339
+ }
5340
+
5341
+
5342
+ /* The docked shell carries no attribution line. The panel's was already hidden
5343
+ (the bar used to carry it instead); now neither does, so the dock is the bar and
5344
+ nothing else. Removing it moves the bar 24px DOWN — the column is anchored to its
5345
+ own bottom edge — so both things measured against the bar lose that term too:
5346
+ see the panel and the toast below. */
5347
+ .hw-widget[data-hw-placement="docked"] .hw-powered { display: none; }
5348
+
5349
+ /* Reconnecting. A socket blip is not an error — it resolves itself in a
5350
+ second or two — so it sits quietly at the top of the panel instead of
5351
+ shouting in amber across the full width. */
5352
+ .hw-widget[data-hw-placement="docked"] .hw-disconnect {
5353
+ padding: 7px 14px;
5354
+ background: var(--hw-bg-hover);
5355
+ color: var(--hw-text-3, var(--hw-text-muted));
5356
+ font-size: 11.5px;
5357
+ border-radius: 20px 20px 0 0;
5358
+ }
5359
+
5360
+ /* Author + time under a message. */
5361
+ .hw-msg-meta { display: flex; align-items: center; gap: 5px; }
5362
+ .hw-msg-author { font-weight: 550; }
5363
+ .hw-msg-meta-sep { opacity: 0.55; }
5364
+
5365
+ /* Conversation options. The header carried only a close button, so
5366
+ expanding the panel or starting over had nowhere to live. */
5367
+ .hw-header-actions { position: relative; display: flex; align-items: center; gap: 2px; }
5368
+ .hw-header-menu-btn {
5369
+ display: grid;
5370
+ place-items: center;
5371
+ width: 30px;
5372
+ height: 30px;
5373
+ border: 0;
5374
+ border-radius: 8px;
5375
+ cursor: pointer;
5376
+ background: transparent;
5377
+ color: var(--hw-text-2, var(--hw-text-muted));
5378
+ transition: background 0.15s, color 0.15s;
5379
+ }
5380
+ .hw-header-menu-btn:hover { background: var(--hw-bg-hover); color: var(--hw-text); }
5381
+ .hw-header-menu {
5382
+ position: absolute;
5383
+ top: 36px;
5384
+ right: 0;
5385
+ z-index: 6;
5386
+ min-width: 236px;
5387
+ max-width: 288px;
5388
+ padding: 5px;
5389
+ border-radius: 14px;
5390
+ background: var(--hw-bg);
5391
+ color: var(--hw-text);
5392
+ box-shadow:
5393
+ 0 0 0 1px var(--hw-border),
5394
+ 0 12px 32px -10px rgba(0, 0, 0, 0.32);
5395
+ animation: hw-menu-in 0.16s cubic-bezier(0.2, 0.8, 0.2, 1) both;
5396
+ }
5397
+ @keyframes hw-menu-in {
5398
+ from { opacity: 0; transform: translateY(-4px) scale(0.98); }
5399
+ to { opacity: 1; transform: none; }
5400
+ }
5401
+ .hw-header-menu button {
5402
+ display: flex;
5403
+ align-items: center;
5404
+ gap: 10px;
5405
+ width: 100%;
5406
+ padding: 9px 10px;
5407
+ border: 0;
5408
+ border-radius: 9px;
5409
+ cursor: pointer;
5410
+ background: transparent;
5411
+ color: var(--hw-text);
5412
+ font: inherit;
5413
+ font-size: 13px;
5414
+ text-align: left;
5415
+ transition: background 0.14s;
5416
+ }
5417
+ .hw-header-menu button:hover { background: var(--hw-bg-hover); }
5418
+ .hw-header-menu button svg { flex: none; color: var(--hw-text-3, var(--hw-text-muted)); }
5419
+ .hw-header-menu button:hover svg { color: var(--hw-brand, var(--hw-primary)); }
5420
+
5421
+ /* The visitor's own threads, above the button that starts another one. Two lines
5422
+ each — who they were talking to and when, then the last thing said — which is
5423
+ the minimum that tells two threads apart. The docked shell has no tab bar and
5424
+ no list screen, so without this the second conversation is unreachable. */
5425
+ .hw-header-menu-convs {
5426
+ display: flex;
5427
+ flex-direction: column;
5428
+ max-height: 168px;
5429
+ overflow-y: auto;
5430
+ padding-bottom: 5px;
5431
+ margin-bottom: 5px;
5432
+ border-bottom: 1px solid var(--hw-border);
5433
+ }
5434
+ .hw-header-menu button.hw-header-menu-conv {
5435
+ flex-direction: column;
5436
+ align-items: stretch;
5437
+ gap: 2px;
5438
+ padding: 8px 10px;
5439
+ }
5440
+ .hw-header-menu button.hw-header-menu-conv[data-active="true"] {
5441
+ background: var(--hw-bg-hover);
5442
+ }
5443
+ .hw-header-menu-conv-top {
5444
+ display: flex;
5445
+ align-items: baseline;
5446
+ justify-content: space-between;
5447
+ gap: 8px;
5448
+ }
5449
+ .hw-header-menu-conv-name {
5450
+ overflow: hidden;
5451
+ font-size: 12.5px;
5452
+ font-weight: 600;
5453
+ text-overflow: ellipsis;
5454
+ white-space: nowrap;
5455
+ }
5456
+ .hw-header-menu-conv-time {
5457
+ flex: none;
5458
+ font-size: 11px;
5459
+ color: var(--hw-text-3, var(--hw-text-muted));
5460
+ }
5461
+ .hw-header-menu-conv-preview {
5462
+ overflow: hidden;
5463
+ font-size: 12px;
5464
+ color: var(--hw-text-2);
5465
+ text-overflow: ellipsis;
5466
+ white-space: nowrap;
5467
+ }
5468
+
5469
+
5470
+ /* Offers the agent makes. A pill, not a bar of chrome — the visitor
5471
+ should read it as the agent saying "or I can get you a person". */
5472
+ .hw-offers { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
5473
+ .hw-offer {
5474
+ display: inline-flex;
5475
+ align-items: center;
5476
+ gap: 6px;
5477
+ padding: 8px 14px;
5478
+ border: 0;
5479
+ border-radius: 999px;
5480
+ cursor: pointer;
5481
+ background: var(--hw-bg-hover);
5482
+ color: var(--hw-text);
5483
+ font: inherit;
5484
+ font-size: 12.5px;
5485
+ font-weight: 550;
5486
+ transition: background 0.15s, color 0.15s;
5487
+ }
5488
+ .hw-offer:hover { background: var(--hw-brand, var(--hw-primary)); color: var(--hw-on-brand, #fff); }
5489
+ .hw-offer svg { flex: none; }
5490
+
5491
+
5492
+ /* Sources. A numbered row per entry rather than a card — the answer is the
5493
+ content, these are the receipts, and receipts should be quiet. */
5494
+ .hw-sources { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
5495
+ .hw-source {
5496
+ display: flex;
5497
+ align-items: center;
5498
+ gap: 9px;
5499
+ padding: 7px 10px;
5500
+ border-radius: 9px;
5501
+ background: transparent;
5502
+ color: var(--hw-text);
5503
+ text-decoration: none;
5504
+ transition: background 0.15s;
5505
+ }
5506
+ a.hw-source { cursor: pointer; }
5507
+ a.hw-source:hover { background: var(--hw-bg-hover); }
5508
+ .hw-source-n {
5509
+ display: grid;
5510
+ place-items: center;
5511
+ flex: none;
5512
+ width: 16px;
5513
+ height: 16px;
5514
+ border-radius: 4px;
5515
+ background: var(--hw-brand, var(--hw-primary));
5516
+ color: var(--hw-on-brand, #fff);
5517
+ font-size: 10px;
5518
+ font-weight: 700;
5519
+ font-variant-numeric: tabular-nums;
5520
+ }
5521
+ .hw-source-title {
5522
+ flex: 1;
5523
+ min-width: 0;
5524
+ font-size: 12px;
5525
+ font-weight: 550;
5526
+ overflow: hidden;
5527
+ text-overflow: ellipsis;
5528
+ white-space: nowrap;
5529
+ }