diff --git a/gatsby-browser.js b/gatsby-browser.js index e6d4c93ee..aa4d482bb 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,15 @@ +const originalFetch = window.fetch.bind(window); +window.fetch = (input, init) => { + const url = typeof input === 'string' ? input : input?.url ?? ''; + if (url.includes('radiant-styles/public/img/rd-icons/rd-icons-sprite/rd-icons.svg')) { + return Promise.resolve(new Response('', { + status: 200, + headers: { 'Content-Type': 'image/svg+xml' }, + })); + } + return originalFetch(input, init); +}; + exports.onClientEntry = () => { // Dynamic imports so @thoughtspot/radiant-react (which reads `window` at // module-load time) is never evaluated during Gatsby's SSR build. diff --git a/src/assets/styles/admonition.scss b/src/assets/styles/admonition.scss index 7ef3bc95a..2d06fa93d 100644 --- a/src/assets/styles/admonition.scss +++ b/src/assets/styles/admonition.scss @@ -91,5 +91,24 @@ table-layout: fixed; position: relative; width: 100%; + + // Asciidoctor renders this as a plain, class-less (its own + // icon+content layout template, not a real [cols=...] table block), + // but Document/index.scss's generic `table` rule still matches it + // and adds a flat grey border-top with no radius — it extends past + // td.content's rounded top-right corner below it. Reset it here + // rather than touching the shared rule, since real content tables + // (with the .tableblock class) should keep that border. + border-top: none; } +} + +// Inline `code` normally sits on the near-white --code-text-bg, which in +// light mode reads as a stark, disconnected chip against a tinted +// admonition background (e.g. the note block's light blue). Blend it into +// the ambient tint with a translucent overlay instead of a fixed color. +#wrapper[data-theme='light'] .documentWrapper .admonitionblock code, +#docsModal[data-theme='light'] .documentWrapper .admonitionblock code { + background: rgba(0, 0, 0, 0.06); + border-color: rgba(0, 0, 0, 0.12); } \ No newline at end of file diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 6c5e6a4cd..949e14305 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -655,9 +655,10 @@ button { .admonitionblock.note { border-left: 4px solid var(--var-note-color); td.content { - border: 1px solid var(--var-note-color); + border: 0.5px solid var(--note-border-color); + border-left: none; box-shadow: var(--box-shadow); - border-radius: 10px !important;; + border-radius: 0 5px 5px 0 !important; background: var(--note-block-color); } @@ -680,8 +681,9 @@ button { border-left: 4px solid var(--var-important-color); td.content { border: 1px solid var(--important-border-color); + border-left: none; box-shadow: var(--box-shadow); - border-radius: 5px !important; + border-radius: 0 5px 5px 0 !important; background: var(--important-block-color); } td.icon { @@ -694,8 +696,9 @@ button { border-left: 5px solid $warning-color; td.content { border: 1px solid $warning-color; + border-left: none; box-shadow: var(--box-shadow); - border-radius: 5px !important; + border-radius: 0 5px 5px 0 !important; } td.icon { left: 20px !important; @@ -707,8 +710,9 @@ button { border-left: 5px solid $caution-color; td.content { border: 1px solid $caution-color; + border-left: none; box-shadow: var(--box-shadow); - border-radius: 5px !important; + border-radius: 0 5px 5px 0 !important; } td.icon { left: 20px !important; @@ -1800,7 +1804,7 @@ a.anchor { /* optional .cl-label role on a paragraph in the left cell */ .cl-label p { display: inline-block; - border: 1px solid var(--rel-notes-pill-border); + border: 0.5px solid var(--rel-notes-pill-border); background-color: var(--rd-comp-color-button-primary-on-color-hover); color: var(--rel-notes-tag-text); font-size: 14px; diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss index 7cab3f66f..b6c551c5a 100644 --- a/src/assets/styles/variables.scss +++ b/src/assets/styles/variables.scss @@ -183,11 +183,7 @@ $tag-color-light: $lightgrey; // --------------------------------------------------------------------------- // radiant remap a custom property whose value contains var() is resolved on -// the element where it is DECLARED, not where it is used. If we alias -// --body-background-color: var(--rd-sys-color-background-base); -// only on :root, dark mode would resolve against :root's (light) token and -// silently stop working. So the SAME alias block must be emitted on every -// element that carries data-theme. Hence one mixin, included twice. +// the element where it is DECLARED, not where it is used. // --------------------------------------------------------------------------- @mixin ts-docs-radiant-aliases { @@ -334,7 +330,7 @@ $tag-color-light: $lightgrey; --body-background-color: #1F2024; --table-header-bg: #26282C; --copy-page-menu-bg: #26282C; - --rel-notes-tag-bg-color: #A0C4FF; + --rel-notes-tag-bg-color: var(--note-block-color); --rel-notes-tag-text: #71a1f4; --rel-notes-pill-border: #278CFF; /* dark-mode-only admonition background — light mode keeps its existing diff --git a/src/components/Document/index.scss b/src/components/Document/index.scss index 39de82de8..bdc41340a 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); @@ -246,9 +255,9 @@ display: inline-flex; align-items: center; padding: 4px 10px; - font-size: 13px; + font-size: 14px; font-weight: 500; - color: var(--nav-accent-color); + color: #71a1f4; background: transparent; border: none; border-radius: 5px; @@ -669,11 +678,7 @@ pre { } } -// Walkthroughs landing page only — zeroed out entirely (rather than mirrored -// like other pages) so .radiance-bg's own negative inset has a clean 0 -// baseline to bleed from on all three edges. Compound selector (higher -// specificity than .doc-wrapper-detail alone) so it doesn't need source -// order and doesn't affect any other page using .doc-wrapper-detail. +// Walkthroughs landing page only .doc-wrapper-detail.doc-walkthroughs { padding: 0; @media (max-width: $mobile-resolution-max) { 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..29ef7857b 100644 --- a/src/components/FloatingAssistant/index.scss +++ b/src/components/FloatingAssistant/index.scss @@ -1,6 +1,88 @@ @import '../../assets/styles/variables.scss'; @import '../../assets/styles/highlight.scss'; +// ── Theme tokens ──────────────────────────────────────────────────────────── +#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-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-hover: #dee8fa; + --fa-btn-primary-icon-pressed: #cedcf5; + --fa-btn-secondary-bg: #2f3237; + --fa-btn-secondary-bg-hover: var(--rd-sys-color-background-secondary-hover); + --fa-btn-secondary-bg-pressed: #60656c; + --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 +94,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 +114,7 @@ height: 26px; position: relative; z-index: 1; - color: #2770ef; + color: var(--fa-accent); } } @@ -47,7 +129,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 +165,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 +184,7 @@ } &--conversation { - background: #fff; + background: var(--fa-panel-bg); } &--embedded { @@ -121,7 +203,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 +215,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 +243,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 +266,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 +286,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 +334,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 +351,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 +366,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 +399,7 @@ .floating-assistant__timestamp { font-size: 11px; - color: #a5acb9; + color: var(--fa-text-muted); font-family: 'Optimo-Plain', sans-serif; } @@ -338,14 +417,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 +464,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 +479,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 +514,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 +527,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 +536,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 +565,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 +592,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 +617,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 +631,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 +646,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; @@ -621,6 +700,15 @@ border-radius: 10px 10px 0 0; } +// Code chrome for dark theme +#floating-assistant-root[data-theme='dark'] { + .fa-code-block { + background: var(--fa-bg); + pre { background: var(--fa-bg); } + } + .fa-code-header { background: var(--fa-surface-hover); } +} + .fa-code-lang { font-size: 12px; font-family: 'Optimo-Plain', sans-serif; @@ -632,7 +720,7 @@ .fa-code-copy { background: none; border: none; - color: #2770ef; + color: #71a1f4; cursor: pointer; font-size: 12px; font-family: 'Optimo-Plain', sans-serif; @@ -649,8 +737,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 +746,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 +759,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 +806,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 +828,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 +#floating-assistant-root[data-theme='dark'] { + .floating-assistant__reset-input, + .floating-assistant__edit-action-btn--edit { + --rd-sys-color-content-secondary: var(--rd-sys-color-background-inverse); + } } .floating-assistant__send { @@ -756,7 +853,7 @@ height: 30px; border-radius: 50%; border: none; - background: #2770ef; + background: var(--fa-accent); cursor: pointer; display: flex; align-items: center; @@ -764,7 +861,20 @@ flex-shrink: 0; transition: background 0.15s; &:disabled { opacity: 0.4; cursor: not-allowed; } - &:not(:disabled):hover { background: #1a5ccc; } + + // Default state uses the ambient --rd-sys-color-content-inverse as-is + // (white in light mode, near-black in dark mode against the lighter + // dark-mode button blue) — only hover/pressed need a local override for + // shades that token doesn't provide. + + &: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 +886,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 +897,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..658f36daa 100644 --- a/src/components/FloatingAssistant/index.tsx +++ b/src/components/FloatingAssistant/index.tsx @@ -12,6 +12,9 @@ import { Message } from './types'; import { renderMarkdown, formatTimestamp, formatDuration, getPageId, stripMarkdown } from './helpers'; import { fetchSuggestedQuestions, streamAgentResponse, sendFeedback } from './api'; +// Matches 404_PAGE_TITLE in src/intl/en.json. +const NOT_FOUND_TITLE = 'Not found'; + const SparkleIcon = () => ( ); @@ -47,6 +50,18 @@ const FloatingAssistant: React.FC = () => { const [isTutorialsPage, setIsTutorialsPage] = useState( () => typeof window !== 'undefined' && isTutorialsPath(window.location.pathname), ); + // The 404 page has no fixed pathname to match against (it renders for + // whatever bogus URL the user hit), so detect it via document.title + // instead. NOTE: a DOM marker (id) doesn't work here — in production, + // Gatsby prefetches/pre-mounts the 404 page's component into the live + // DOM in the background (for instant fallback on broken links) even + // while a completely different, valid page is showing, so + // getElementById('page-404') can be true on any page. document.title + // isn't affected by that background mount, only by the page actually + // being rendered. + const [isNotFoundPage, setIsNotFoundPage] = useState( + () => typeof document !== 'undefined' && document.title === NOT_FOUND_TITLE, + ); const { isOpen, setIsOpen, @@ -269,6 +284,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)); @@ -277,6 +338,12 @@ const FloatingAssistant: React.FC = () => { return () => window.removeEventListener('gatsby-route-update', handler as EventListener); }, []); + useEffect(() => { + const handler = () => setIsNotFoundPage(document.title === NOT_FOUND_TITLE); + window.addEventListener('gatsby-route-update', handler as EventListener); + return () => window.removeEventListener('gatsby-route-update', handler as EventListener); + }, []); + useEffect(() => { const handler = (e: CustomEvent<{ quotedText: string }>) => { setQuotedText(e.detail.quotedText); @@ -427,6 +494,7 @@ const FloatingAssistant: React.FC = () => { if (pageId === CUSTOM_PAGE_ID.API_PLAYGROUND) return null; if (isTutorialsPage) return null; + if (isNotFoundPage) return null; return ( <> @@ -455,7 +523,7 @@ const FloatingAssistant: React.FC = () => { SpotterCode @@ -579,7 +647,7 @@ const FloatingAssistant: React.FC = () => { ) : ( i === messages.map((m, idx) => m.role === 'user' ? idx : -1).filter(x => x >= 0).pop() && !isLoading && ( - ) @@ -685,7 +753,7 @@ const FloatingAssistant: React.FC = () => { aria-label="Reset conversation" disabled={messages.length === 0 && !input.trim() && !quotedText} > - + {isLoading ? (