diff --git a/src/components/Document/index.scss b/src/components/Document/index.scss index 39de82de8..0c352853d 100644 --- a/src/components/Document/index.scss +++ b/src/components/Document/index.scss @@ -23,30 +23,36 @@ padding: 8px 14px; font-size: 13px; font-weight: 500; - color: #1d232f !important; - background: #fff; + color: var(--primary-color) !important; + background: var(--panel-bg-color); border-radius: 100px; text-decoration: none; white-space: nowrap; pointer-events: all; - box-shadow: - 0 4px 16px 0 rgba(25, 35, 49, 0.14), - 0 1px 4px 0 rgba(25, 35, 49, 0.06); + box-shadow: var(--box-shadow-hover); transition: box-shadow 0.15s ease; font-family: 'Optimo-Plain', sans-serif; border: 1px solid transparent; isolation: isolate; - // Sparkle icon + // Sparkle icon — shape supplied as a mask so its color follows + // --rd-sys-color-content-brand, the same token the FloatingAssistant + // widget's own sparkle icon (IconColor.BLUE) resolves its fill from, + // keeping the two in sync in both themes. &::before { content: ''; display: inline-block; width: 16px; height: 16px; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 27 27' fill='%232770EF' stroke='none'%3E%3Cpath fill='%232770EF' stroke='none' d='M8.25809 7.21109C8.83155 5.59342 11.1191 5.59349 11.6927 7.21109L13.4163 12.0753C13.4919 12.2885 13.6602 12.4568 13.8733 12.5324L18.7376 14.256C20.3555 14.8294 20.3555 17.1172 18.7376 17.6906L13.8733 19.4142C13.6601 19.4898 13.4918 19.658 13.4163 19.8712L11.6927 24.7355C11.1191 26.353 8.83157 26.3531 8.25809 24.7355L6.53445 19.8712C6.4589 19.658 6.29064 19.4898 6.07742 19.4142L1.21316 17.6906C-0.404397 17.1171 -0.404379 14.8295 1.21316 14.256L6.07742 12.5324C6.29058 12.4568 6.45883 12.2885 6.53445 12.0753L8.25809 7.21109ZM20.2805 0.49136C20.6395 -0.163697 21.6064 -0.163877 21.9651 0.49136L22.0315 0.642727L22.888 3.05972L25.3059 3.91616C26.1625 4.21966 26.1622 5.43079 25.3059 5.73452L22.888 6.59097L22.0315 9.00894C21.7279 9.86544 20.5167 9.86552 20.2132 9.00894L19.3567 6.59097L16.9397 5.73452C16.0831 5.43098 16.0831 4.21969 16.9397 3.91616L19.3567 3.05972L20.2132 0.642727L20.2805 0.49136Z'/%3E%3C/svg%3E"); - background-size: contain; - background-repeat: no-repeat; - background-position: center; + background-color: var(--rd-sys-color-content-brand, #2770EF); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 27 27'%3E%3Cpath d='M8.25809 7.21109C8.83155 5.59342 11.1191 5.59349 11.6927 7.21109L13.4163 12.0753C13.4919 12.2885 13.6602 12.4568 13.8733 12.5324L18.7376 14.256C20.3555 14.8294 20.3555 17.1172 18.7376 17.6906L13.8733 19.4142C13.6601 19.4898 13.4918 19.658 13.4163 19.8712L11.6927 24.7355C11.1191 26.353 8.83157 26.3531 8.25809 24.7355L6.53445 19.8712C6.4589 19.658 6.29064 19.4898 6.07742 19.4142L1.21316 17.6906C-0.404397 17.1171 -0.404379 14.8295 1.21316 14.256L6.07742 12.5324C6.29058 12.4568 6.45883 12.2885 6.53445 12.0753L8.25809 7.21109ZM20.2805 0.49136C20.6395 -0.163697 21.6064 -0.163877 21.9651 0.49136L22.0315 0.642727L22.888 3.05972L25.3059 3.91616C26.1625 4.21966 26.1622 5.43079 25.3059 5.73452L22.888 6.59097L22.0315 9.00894C21.7279 9.86544 20.5167 9.86552 20.2132 9.00894L19.3567 6.59097L16.9397 5.73452C16.0831 5.43098 16.0831 4.21969 16.9397 3.91616L19.3567 3.05972L20.2132 0.642727L20.2805 0.49136Z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 27 27'%3E%3Cpath d='M8.25809 7.21109C8.83155 5.59342 11.1191 5.59349 11.6927 7.21109L13.4163 12.0753C13.4919 12.2885 13.6602 12.4568 13.8733 12.5324L18.7376 14.256C20.3555 14.8294 20.3555 17.1172 18.7376 17.6906L13.8733 19.4142C13.6601 19.4898 13.4918 19.658 13.4163 19.8712L11.6927 24.7355C11.1191 26.353 8.83157 26.3531 8.25809 24.7355L6.53445 19.8712C6.4589 19.658 6.29064 19.4898 6.07742 19.4142L1.21316 17.6906C-0.404397 17.1171 -0.404379 14.8295 1.21316 14.256L6.07742 12.5324C6.29058 12.4568 6.45883 12.2885 6.53445 12.0753L8.25809 7.21109ZM20.2805 0.49136C20.6395 -0.163697 21.6064 -0.163877 21.9651 0.49136L22.0315 0.642727L22.888 3.05972L25.3059 3.91616C26.1625 4.21966 26.1622 5.43079 25.3059 5.73452L22.888 6.59097L22.0315 9.00894C21.7279 9.86544 20.5167 9.86552 20.2132 9.00894L19.3567 6.59097L16.9397 5.73452C16.0831 5.43098 16.0831 4.21969 16.9397 3.91616L19.3567 3.05972L20.2132 0.642727L20.2805 0.49136Z'/%3E%3C/svg%3E"); + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; flex-shrink: 0; position: relative; z-index: 1; @@ -81,11 +87,9 @@ } &:hover { - color: #1d232f !important; + color: var(--primary-color) !important; text-decoration: none; - box-shadow: - 0 4px 16px 0 rgba(25, 35, 49, 0.14), - 0 1px 4px 0 rgba(25, 35, 49, 0.06); + box-shadow: var(--box-shadow-hover); &::after { opacity: 1; @@ -94,6 +98,11 @@ } } +#wrapper[data-theme='dark'] .selection-cta-button, +#docsModal[data-theme='dark'] .selection-cta-button { + background: #26282C; +} + .documentWrapper { width: calc(98%); color: var(--primary-color); diff --git a/src/components/Document/index.tsx b/src/components/Document/index.tsx index 5a2305447..21e93ad04 100644 --- a/src/components/Document/index.tsx +++ b/src/components/Document/index.tsx @@ -39,6 +39,13 @@ const Document = (props: { if (target.closest('.selection-cta-button')) return; if (target.closest('.floating-assistant__panel, .floating-assistant__chip-ring')) return; + // Only offer "Ask SpotterCode" for selections inside the main article + // body — not the left nav, header, footer, or right-side doc map/TOC. + if (!target.closest('.documentView')) { + setSelectionPos(null); + return; + } + // If mouse didn't move (plain click, not a drag-select), don't re-show const moved = Math.abs(e.clientX - mouseDownX) > 3 || Math.abs(e.clientY - mouseDownY) > 3; if (!moved) return; diff --git a/src/components/FloatingAssistant/index.scss b/src/components/FloatingAssistant/index.scss index f8276f276..dcff12ae4 100644 --- a/src/components/FloatingAssistant/index.scss +++ b/src/components/FloatingAssistant/index.scss @@ -1,6 +1,98 @@ @import '../../assets/styles/variables.scss'; @import '../../assets/styles/highlight.scss'; +// ── Theme tokens ──────────────────────────────────────────────────────────── +// This widget mounts into its own React root appended directly to +// document.body (see gatsby-browser.js), outside #wrapper/#docsModal, so it +// can't inherit the site's data-theme custom properties from variables.scss. +// index.tsx mirrors the site's current theme onto this root's own +// data-theme attribute; these aliases are redeclared under that selector so +// they resolve against the dark values for every descendant (same reason +// variables.scss's ts-docs-radiant-aliases mixin is included twice). +#floating-assistant-root { + --fa-bg: #fff; + --fa-panel-bg: #fff; + --fa-surface: #F6F8FA; + --fa-surface-hover: #eef1f5; + --fa-border: #EAEDF2; + --fa-border-strong: #e3e6eb; + --fa-text: #1d232f; + --fa-text-secondary: #5f6368; + --fa-text-muted: #a5acb9; + --fa-accent: #2770ef; + --fa-accent-hover: #1a5fd4; + --fa-btn-primary-pressed: #1a5fd4; + --fa-btn-primary-icon: #fff; + --fa-btn-primary-icon-hover: #fff; + --fa-btn-primary-icon-pressed: #fff; + --fa-btn-secondary-bg: #fff; + --fa-btn-secondary-bg-hover: #F6F8FA; + --fa-btn-secondary-bg-pressed: #eef1f5; + --fa-user-bubble-bg: #f1f4f8; + --fa-user-bubble-border: #e8eaed; + --fa-scrollbar: #d0d4db; + --fa-scrollbar-hover: #a5acb9; + --fa-scroll-btn-bg: #fff; + --fa-scroll-btn-border: #e0e4ea; + --fa-shadow: 0 4px 16px 0 rgba(25, 35, 49, 0.10); + --fa-shadow-hover: 0 8px 24px 0 rgba(25, 35, 49, 0.16); + --fa-panel-shadow: -4px 0 24px 0 rgba(25, 35, 49, 0.10); + --fa-input-border-idle: #d0d4db; + --fa-input-row-border: rgba(0, 0, 0, 0.06); + --fa-input-row-bg: rgba(255, 255, 255, 0.6); + --fa-fade-color: #fff; + --fa-code-inline-bg: #f1f4f8; + --fa-code-inline-border: #e3e6eb; + --fa-hover-overlay: rgba(0, 0, 0, 0.06); + --fa-panel-wash-1: rgba(255, 120, 160, 0.3); + --fa-panel-wash-2: rgba(160, 100, 255, 0.2); + --fa-panel-wash-3: rgba(100, 160, 255, 0.3); +} + +#floating-assistant-root[data-theme='dark'] { + --fa-bg: #26282C; + --fa-panel-bg: #1F2024; + --fa-surface: #26282C; + --fa-surface-hover: #2f333b; + --fa-border: #34363C; + --fa-border-strong: #34363C; + --fa-text: #e6edf3; + --fa-text-secondary: #9098a8; + --fa-text-muted: #8b949e; + --fa-accent: #71a1f4; + --fa-accent-hover: #abc7f9; + --fa-btn-primary-pressed: #cedcf5; + --fa-btn-primary-icon: #fff; + --fa-btn-primary-icon-hover: #dee8fa; + --fa-btn-primary-icon-pressed: #cedcf5; + --fa-btn-secondary-bg: #2f3237; + --fa-btn-secondary-bg-hover: #2f3237; + --fa-btn-secondary-bg-pressed: #60656c; + --fa-btn-secondary-icon: #dfe0e2; + --fa-user-bubble-bg: #26282C; + --fa-user-bubble-border: #34363C; + --fa-scrollbar: #3a3f4b; + --fa-scrollbar-hover: #545b6b; + --fa-scroll-btn-bg: #2a3142; + --fa-scroll-btn-border: #3a4255; + --fa-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.4); + --fa-shadow-hover: 0 8px 24px 0 rgba(0, 0, 0, 0.5); + --fa-panel-shadow: -4px 0 24px 0 rgba(0, 0, 0, 0.4); + --fa-input-border-idle: #3a3f4b; + --fa-input-row-border: rgba(255, 255, 255, 0.08); + --fa-input-row-bg: rgba(31, 32, 36, 0.6); + --fa-fade-color: #1F2024; + --fa-code-inline-bg: #2a3142; + --fa-code-inline-border: #3a4255; + --fa-hover-overlay: rgba(255, 255, 255, 0.08); + // Same wash hues at much lower opacity — on the dark panel background the + // light-mode opacities read as a saturated, concentrated band instead of + // the soft diffused glow the reference design uses. + --fa-panel-wash-1: rgba(255, 120, 160, 0.10); + --fa-panel-wash-2: rgba(160, 100, 255, 0.07); + --fa-panel-wash-3: rgba(100, 160, 255, 0.10); +} + // ── Chip trigger ───────────────────────────────────────────────────────────── .floating-assistant__chip { position: fixed; @@ -12,19 +104,19 @@ justify-content: center; width: 56px; height: 56px; - background: #fff; + background: var(--fa-bg); border: 2px solid transparent; border-radius: 50%; cursor: pointer; background-clip: padding-box; - box-shadow: 0 4px 16px 0 rgba(25, 35, 49, 0.10); + box-shadow: var(--fa-shadow); transition: box-shadow 0.2s ease, transform 0.2s ease; outline: 2px solid transparent; // Gradient border using outline + pseudo-element on a wrapper isn't needed — // use a parent wrapper div for the gradient ring &:hover { - box-shadow: 0 8px 24px 0 rgba(25, 35, 49, 0.16); + box-shadow: var(--fa-shadow-hover); } svg { @@ -32,7 +124,7 @@ height: 26px; position: relative; z-index: 1; - color: #2770ef; + color: var(--fa-accent); } } @@ -47,7 +139,7 @@ background: linear-gradient(135deg, #8C62F5, #48D1E0, #2770EF); padding: 1px; z-index: 1001; - box-shadow: 0 4px 16px 0 rgba(25, 35, 49, 0.10); + box-shadow: var(--fa-shadow); cursor: pointer; .floating-assistant__chip { @@ -83,18 +175,18 @@ bottom: 0; width: 360px; background: - linear-gradient(180deg, transparent 0%, #fff 35%), + linear-gradient(180deg, transparent 0%, var(--fa-panel-bg) 35%), linear-gradient(90deg, - rgba(255, 120, 160, 0.3) 0%, - rgba(160, 100, 255, 0.2) 60%, - rgba(100, 160, 255, 0.3) 90% + var(--fa-panel-wash-1) 0%, + var(--fa-panel-wash-2) 60%, + var(--fa-panel-wash-3) 90% ), - #fff; + var(--fa-panel-bg); display: flex; flex-direction: column; overflow: hidden; z-index: 1002; - box-shadow: -4px 0 24px 0 rgba(25, 35, 49, 0.10); + box-shadow: var(--fa-panel-shadow); animation: fa-slide-in 0.25s ease-out forwards; &.closing { @@ -102,7 +194,7 @@ } &--conversation { - background: #fff; + background: var(--fa-panel-bg); } &--embedded { @@ -121,7 +213,7 @@ background: transparent; border: none; outline: none; - border-bottom: 1px solid #EAEDF2; + border-bottom: 1px solid var(--fa-border); } .floating-assistant__close-btn { @@ -133,16 +225,17 @@ justify-content: center; padding: 4px; border-radius: 4px; + color: var(--fa-text); transition: background 0.15s; - &:hover { background: #f1f4f8; } + &:hover { background: var(--fa-surface); } } .floating-assistant__title { font-size: 16px; font-weight: 600; font-family: 'Optimo-Plain', sans-serif; - color: #1d232f; + color: var(--fa-text); } @@ -160,12 +253,12 @@ &::-webkit-scrollbar { width: 4px; } &::-webkit-scrollbar-track { background: transparent; } &::-webkit-scrollbar-thumb { - background: #d0d4db; + background: var(--fa-scrollbar); border-radius: 2px; - &:hover { background: #a5acb9; } + &:hover { background: var(--fa-scrollbar-hover); } } scrollbar-width: thin; - scrollbar-color: #d0d4db transparent; + scrollbar-color: var(--fa-scrollbar) transparent; } // ── Messages fade + scroll-to-bottom ───────────────────────────────────────── @@ -183,7 +276,7 @@ left: 0; right: 0; height: 48px; - background: linear-gradient(to top, #fff 0%, transparent 100%); + background: linear-gradient(to top, var(--fa-fade-color) 0%, transparent 100%); opacity: 0; transition: opacity 0.2s ease; } @@ -203,17 +296,13 @@ display: inline-flex; align-items: center; justify-content: center; - background: #fff; - border: 1px solid #e0e4ea; + background: var(--fa-scroll-btn-bg); + border: 1px solid var(--fa-scroll-btn-border); border-radius: 50%; width: 28px; height: 28px; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.1); - @media (prefers-color-scheme: dark) { - background: #2a3142; - border-color: #3a4255; - } } @keyframes fa-fade-in { @@ -255,12 +344,12 @@ font-size: 22px; font-weight: 700; font-family: 'Optimo-Plain', sans-serif; - color: #1d232f; + color: var(--fa-text); line-height: 1.3; letter-spacing: -0.4px; text-align: center; - span { color: #2770ef; } + span { color: var(--fa-accent); } } .floating-assistant__suggestions { @@ -272,10 +361,10 @@ } .floating-assistant__suggestion { - background: #F6F8FA; - border: 1px solid #EAEDF2; + background: var(--fa-surface); + border: 1px solid var(--fa-border); border-radius: 40px; - color: #1d232f; + color: var(--fa-text); cursor: pointer; font-size: 12px; font-family: 'Optimo-Plain', sans-serif; @@ -287,7 +376,7 @@ animation: fa-suggestion-in 0.3s ease-out both; opacity: 0; - &:hover { background: #eef1f5; } + &:hover { background: var(--fa-surface-hover); } } @keyframes fa-suggestion-in { @@ -320,7 +409,7 @@ .floating-assistant__timestamp { font-size: 11px; - color: #a5acb9; + color: var(--fa-text-muted); font-family: 'Optimo-Plain', sans-serif; } @@ -338,14 +427,14 @@ background: none; &:hover { - background: #d0d4db; + background: var(--fa-scrollbar); } &--send { - background: #2770ef; + background: var(--fa-accent); &:hover { - background: #1a5fd4; + background: var(--fa-accent-hover); } } } @@ -385,13 +474,13 @@ // User bubble — light gray rounded box .floating-assistant__user-bubble { - background: #f1f4f8; - border: 1px solid #e8eaed; + background: var(--fa-user-bubble-bg); + border: 1px solid var(--fa-user-bubble-border); border-radius: 10px; padding: 10px 14px; font-size: 13px; font-weight: 400; - color: #1d232f; + color: var(--fa-text); font-family: 'Optimo-Plain', sans-serif; line-height: 1.5; word-break: break-word; @@ -400,8 +489,8 @@ box-sizing: border-box; &--editing { - border-color: #2770ef; - background: #fff; + border-color: var(--fa-accent); + background: var(--fa-panel-bg); cursor: text; white-space: pre-wrap; } @@ -435,7 +524,7 @@ .floating-assistant__message-text { font-size: 13px; line-height: 1.6; - color: #1d232f; + color: var(--fa-text); word-break: break-word; font-family: 'Optimo-Plain', sans-serif; @@ -448,8 +537,8 @@ li { margin-bottom: 3px; } strong { font-weight: 600; } em { font-style: italic; } - a { color: #2770ef; text-decoration: underline; } - h1, h2, h3 { font-weight: 600; margin: 10px 0 4px; color: #1d232f; } + a { color: var(--fa-accent); text-decoration: underline; } + h1, h2, h3 { font-weight: 600; margin: 10px 0 4px; color: var(--fa-text); } h1 { font-size: 15px; } h2 { font-size: 14px; } h3 { font-size: 13px; } @@ -457,11 +546,11 @@ code { font-family: $font-family-code; font-size: 12px; - background: #f1f4f8; - color: #1d232f; + background: var(--fa-code-inline-bg); + color: var(--fa-text); border-radius: 3px; padding: 1px 5px; - border: 1px solid #e3e6eb; + border: 1px solid var(--fa-code-inline-border); } pre { @@ -486,7 +575,7 @@ gap: 5px; font-size: 13px; font-weight: 400; - color: #6b7280; + color: var(--fa-text-secondary); font-family: 'Optimo-Plain', sans-serif; cursor: default; user-select: none; @@ -513,12 +602,12 @@ border-radius: 4px; font-size: 11px; font-family: 'Optimo-Plain', sans-serif; - color: #a5acb9; + color: var(--fa-text-muted); transition: color 0.15s, background 0.15s; &:hover { - color: #1d232f; - background: #f1f4f8; + color: var(--fa-text); + background: var(--fa-surface); } &--copied { @@ -538,7 +627,7 @@ border-radius: 4px; opacity: 0.7; transition: opacity 0.15s, background 0.15s; - &:hover { opacity: 1; background: rgba(0,0,0,0.06); } + &:hover { opacity: 1; background: var(--fa-hover-overlay); } &.fa-feedback-btn--active { opacity: 1; } } @@ -552,7 +641,7 @@ .floating-assistant__loading-phase { font-size: 13px; - color: #5f6368; + color: var(--fa-text-secondary); font-family: 'Optimo-Plain', sans-serif; animation: fa-phase-in 0.4s ease-out forwards; } @@ -567,19 +656,19 @@ display: flex; align-items: center; gap: 6px; - background: #f1f4f8; + background: var(--fa-surface); border-radius: 8px; padding: 6px 8px 6px 10px; margin-bottom: 4px; - color: #5f6368; + color: var(--fa-text-secondary); - svg { flex-shrink: 0; color: #a5acb9; } + svg { flex-shrink: 0; color: var(--fa-text-muted); } } .floating-assistant__quote-text { flex: 1; font-size: 12px; - color: #5f6368; + color: var(--fa-text-secondary); font-family: 'Optimo-Plain', sans-serif; white-space: nowrap; overflow: hidden; @@ -649,8 +738,8 @@ .floating-assistant__input-row { padding: 8px 12px 8px; flex-shrink: 0; - border-top: 1px solid rgba(0, 0, 0, 0.06); - background: rgba(255,255,255,0.6); + border-top: 1px solid var(--fa-input-row-border); + background: var(--fa-input-row-bg); backdrop-filter: blur(8px); } @@ -658,7 +747,7 @@ display: flex; flex-direction: column; gap: 6px; - background: #fff; + background: var(--fa-bg); border-radius: 12px; padding: 10px 10px 8px; cursor: text; @@ -671,7 +760,7 @@ inset: 0; border-radius: 12px; padding: 1.5px; - background: #d0d4db; + background: var(--fa-input-border-idle); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); @@ -718,15 +807,15 @@ font-size: 14px; font-family: 'Optimo-Plain', sans-serif; background: transparent; - color: #1d232f; + color: var(--fa-text); min-height: 28px; max-height: calc(14px * 1.5 * 4); // 4 lines at font-size 14px, line-height 1.5 overflow-y: auto; line-height: 1.5; - &::placeholder { color: #a5acb9; } + &::placeholder { color: var(--fa-text-muted); } scrollbar-width: thin; - scrollbar-color: #d0d4db transparent; + scrollbar-color: var(--fa-scrollbar) transparent; } .floating-assistant__input-buttons { @@ -740,15 +829,24 @@ width: 30px; height: 30px; border-radius: 50%; - border: 1px solid #e3e6eb; - background: #fff; + border: 1px solid var(--fa-border-strong); + background: var(--fa-btn-secondary-bg); cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: background 0.15s; - &:hover { background: #f1f4f8; } + &:hover { background: var(--fa-btn-secondary-bg-hover); } + &:active { background: var(--fa-btn-secondary-bg-pressed); } +} + +// Dark-mode-only icon tint: fills via +// `var(--rd-sys-color-content-secondary, #777e8b)`, which radiant-react's +// own base stylesheet already redefines globally — redeclare it here, +// scoped to dark mode, rather than fighting that library default. +#floating-assistant-root[data-theme='dark'] .floating-assistant__reset-input { + --rd-sys-color-content-secondary: var(--fa-btn-secondary-icon); } .floating-assistant__send { @@ -756,7 +854,7 @@ height: 30px; border-radius: 50%; border: none; - background: #2770ef; + background: var(--fa-accent); cursor: pointer; display: flex; align-items: center; @@ -764,7 +862,24 @@ flex-shrink: 0; transition: background 0.15s; &:disabled { opacity: 0.4; cursor: not-allowed; } - &:not(:disabled):hover { background: #1a5ccc; } + + // The send icon renders via radiant-react's , + // which sets fill via `var(--rd-sys-color-content-inverse, #fff)` on the + // itself. radiant-react's own base stylesheet already defines that + // property globally (to a near-black value meant for other contexts), so + // the "#fff" fallback never actually applies here — redeclare it + // ourselves rather than fighting the library's own (content-hashed, + // version-fragile) CSS class. + --rd-sys-color-content-inverse: var(--fa-btn-primary-icon); + + &:not(:disabled):hover { + background: var(--fa-accent-hover); + --rd-sys-color-content-inverse: var(--fa-btn-primary-icon-hover); + } + &:not(:disabled):active { + background: var(--fa-btn-primary-pressed); + --rd-sys-color-content-inverse: var(--fa-btn-primary-icon-pressed); + } } .floating-assistant__stop { @@ -776,7 +891,7 @@ align-items: center; justify-content: center; flex-shrink: 0; - background: #2770ef; + background: var(--fa-accent); border:0px; color: #fff; transition: background 0.15s, border-color 0.15s; @@ -787,12 +902,12 @@ .floating-assistant__footer { padding: 6px 16px 12px; font-size: 11.5px; - color: #a5acb9; + color: var(--fa-text-muted); text-align: center; font-family: 'Optimo-Plain', sans-serif; a { - color: #2770ef; + color: var(--fa-accent); text-decoration: none; &:hover { text-decoration: underline; } } diff --git a/src/components/FloatingAssistant/index.tsx b/src/components/FloatingAssistant/index.tsx index 22edad90d..d117a5ad4 100644 --- a/src/components/FloatingAssistant/index.tsx +++ b/src/components/FloatingAssistant/index.tsx @@ -269,6 +269,52 @@ const FloatingAssistant: React.FC = () => { return () => window.removeEventListener('gatsby-route-update', handler as EventListener); }, []); + // This widget mounts into its own React root appended directly to + // document.body (see gatsby-browser.js), outside #wrapper/#docsModal, so + // it can't inherit the site's data-theme custom properties. Mirror the + // site's current theme onto our own root's data-theme attribute instead, + // so CSS here can key off #floating-assistant-root[data-theme='dark']. + useEffect(() => { + const themeRoot = document.getElementById('floating-assistant-root'); + const setTheme = (theme: 'dark' | 'light') => { + // Guard against redundant writes — themeRoot lives inside document.body, + // so an unconditional setAttribute here would itself be a mutation that + // a body-wide observer could pick back up, looping forever. + if (themeRoot && themeRoot.getAttribute('data-theme') !== theme) { + themeRoot.setAttribute('data-theme', theme); + } + }; + + let attrObserver: MutationObserver | null = null; + + // Watches the specific #wrapper/#docsModal element for data-theme flips + // (e.g. the user toggling dark mode) — scoped to that one element only, + // so it never observes themeRoot's own attribute writes above. + const watchThemedEl = () => { + const themedEl = document.getElementById('wrapper') || document.getElementById('docsModal'); + if (!themedEl) return; + setTheme(themedEl.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'); + attrObserver?.disconnect(); + attrObserver = new MutationObserver(() => { + setTheme(themedEl.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'); + }); + attrObserver.observe(themedEl, { attributes: true, attributeFilter: ['data-theme'] }); + }; + + watchThemedEl(); + + // #wrapper/#docsModal can be unmounted/remounted on client-side route + // changes — re-attach the attribute observer whenever that happens. + // childList-only, so this never fires from themeRoot's attribute writes. + const bodyObserver = new MutationObserver(watchThemedEl); + bodyObserver.observe(document.body, { childList: true, subtree: true }); + + return () => { + attrObserver?.disconnect(); + bodyObserver.disconnect(); + }; + }, []); + useEffect(() => { const handler = (e: CustomEvent<{ location: Location }>) => { setIsTutorialsPage(isTutorialsPath(e.detail.location.pathname)); @@ -455,7 +501,7 @@ const FloatingAssistant: React.FC = () => { SpotterCode @@ -685,7 +731,7 @@ const FloatingAssistant: React.FC = () => { aria-label="Reset conversation" disabled={messages.length === 0 && !input.trim() && !quotedText} > - + {isLoading ? (