@helpway/react 0.13.2 → 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.
- package/dist/index.d.ts +53 -9
- package/dist/index.js +2174 -1170
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/themed/styles.css +1251 -93
package/src/themed/styles.css
CHANGED
|
@@ -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: #
|
|
70
|
-
--hw-bg-hover: #
|
|
71
|
-
--hw-border: #
|
|
69
|
+
--hw-bg-subtle: #fafaf9;
|
|
70
|
+
--hw-bg-hover: #f5f5f4;
|
|
71
|
+
--hw-border: #ebe9e5;
|
|
72
72
|
--hw-border-md: #e5e5e5;
|
|
73
|
-
--hw-text: #
|
|
73
|
+
--hw-text: #1c1b19;
|
|
74
74
|
--hw-text-1: #111;
|
|
75
75
|
--hw-text-2: #666;
|
|
76
|
-
--hw-text-3: #
|
|
77
|
-
--hw-text-4: #
|
|
78
|
-
--hw-radius:
|
|
79
|
-
--hw-visitor-bubble-bg: #
|
|
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
|
-
|
|
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:
|
|
585
|
-
height:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1220
|
+
padding: 16px 14px 8px;
|
|
1215
1221
|
background: var(--hw-bg-subtle);
|
|
1216
1222
|
display: flex;
|
|
1217
1223
|
flex-direction: column;
|
|
1218
|
-
gap:
|
|
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:
|
|
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:
|
|
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
|
-
.
|
|
1508
|
-
|
|
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:
|
|
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
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
border
|
|
1841
|
-
|
|
1842
|
-
|
|
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:
|
|
1872
|
-
font-weight:
|
|
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
|
-
.
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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
|
-
.
|
|
1904
|
-
|
|
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
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
-
|
|
1924
|
+
transform: scale(1.06);
|
|
1931
1925
|
}
|
|
1932
1926
|
|
|
1933
1927
|
.hw-email-card-submit:disabled {
|
|
1934
|
-
opacity: 0.
|
|
1935
|
-
cursor:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
+
}
|