From e950ad5306944fe1897949dee9573526206a6860 Mon Sep 17 00:00:00 2001 From: Jeremy Osih Date: Fri, 27 Jun 2025 00:38:14 +0200 Subject: feat(web): add scroll to last message button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add intelligent floating scroll button for long conversations that: - Only appears when scrolling down (direction-aware) - Auto-hides after 3 seconds of inactivity - Stays visible on hover to prevent accidental disappearance - Uses consistent design patterns with repo styling - Includes proper accessibility features 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: Jeremy Osih Co-Authored-By: opencode --- packages/web/src/components/Share.tsx | 94 ++++++++++++++++++++++++++++ packages/web/src/components/share.module.css | 35 +++++++++++ 2 files changed, 129 insertions(+) (limited to 'packages/web/src') diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index 07b12946b..5510f5a69 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -40,6 +40,7 @@ import { IconMagnifyingGlass, IconWrenchScrewdriver, IconDocumentMagnifyingGlass, + IconArrowDown, } from "./icons" import DiffView from "./DiffView" import CodeBlock from "./CodeBlock" @@ -721,6 +722,83 @@ export default function Share(props: { }) }) + const [showScrollButton, setShowScrollButton] = createSignal(false) + const [isButtonHovered, setIsButtonHovered] = createSignal(false) + let scrollTimeout: number | undefined + let lastScrollY = 0 + + const checkScrollNeed = () => { + const currentScrollY = window.scrollY + const isScrollingDown = currentScrollY > lastScrollY + const scrolled = currentScrollY > 200 // Show after scrolling 200px + const isNearBottom = window.innerHeight + currentScrollY >= document.body.scrollHeight - 100 + + // Only show when scrolling down, scrolled enough, and not near bottom + const shouldShow = isScrollingDown && scrolled && !isNearBottom + + // Update last scroll position + lastScrollY = currentScrollY + + if (shouldShow) { + setShowScrollButton(true) + // Clear existing timeout + if (scrollTimeout) { + clearTimeout(scrollTimeout) + } + // Hide button after 3 seconds of no scrolling (unless hovered) + scrollTimeout = window.setTimeout(() => { + if (!isButtonHovered()) { + setShowScrollButton(false) + } + }, 3000) + } else if (!isButtonHovered()) { + // Only hide if not hovered (to prevent disappearing while user is about to click) + setShowScrollButton(false) + if (scrollTimeout) { + clearTimeout(scrollTimeout) + } + } + } + + const handleButtonMouseEnter = () => { + setIsButtonHovered(true) + // Clear timeout when hovering + if (scrollTimeout) { + clearTimeout(scrollTimeout) + } + } + + const handleButtonMouseLeave = () => { + setIsButtonHovered(false) + // Restart timeout when leaving hover + if (showScrollButton()) { + scrollTimeout = window.setTimeout(() => { + if (!isButtonHovered()) { + setShowScrollButton(false) + } + }, 3000) + } + } + + const scrollToBottom = () => { + document.body.scrollIntoView({ behavior: "smooth", block: "end" }) + } + + onMount(() => { + lastScrollY = window.scrollY // Initialize scroll position + checkScrollNeed() + window.addEventListener("scroll", checkScrollNeed) + window.addEventListener("resize", checkScrollNeed) + }) + + onCleanup(() => { + window.removeEventListener("scroll", checkScrollNeed) + window.removeEventListener("resize", checkScrollNeed) + if (scrollTimeout) { + clearTimeout(scrollTimeout) + } + }) + const data = createMemo(() => { const result = { rootDir: undefined as string | undefined, @@ -875,6 +953,7 @@ export default function Share(props: { )} + @@ -1975,6 +2054,21 @@ export default function Share(props: { + + {/* Floating scroll to bottom button */} + + + ) } diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css index 53f082c9b..a52fd1765 100644 --- a/packages/web/src/components/share.module.css +++ b/packages/web/src/components/share.module.css @@ -760,3 +760,38 @@ } } } + +.scrollButton { + position: fixed; + bottom: 2rem; + right: 2rem; + width: 2.5rem; + height: 2.5rem; + border-radius: 0.25rem; + border: 1px solid var(--sl-color-divider); + background-color: var(--sl-color-bg-surface); + color: var(--sl-color-text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: all 0.15s ease, opacity 0.5s ease; + z-index: 100; + appearance: none; + opacity: 1; + + &:hover { + color: var(--sl-color-text); + border-color: var(--sl-color-hairline); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } + + &:active { + transform: translateY(1px); + } + + svg { + display: block; + } +} -- cgit v1.2.3 From 065f0aaddf6612aa30e6977aeb9afa2e3a774c56 Mon Sep 17 00:00:00 2001 From: Jay V Date: Thu, 26 Jun 2025 19:02:42 -0400 Subject: docs: tweak lander --- packages/web/src/components/Lander.astro | 67 ++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 20 deletions(-) (limited to 'packages/web/src') diff --git a/packages/web/src/components/Lander.astro b/packages/web/src/components/Lander.astro index ae1b2a5e9..8d4d4efed 100644 --- a/packages/web/src/components/Lander.astro +++ b/packages/web/src/components/Lander.astro @@ -19,8 +19,10 @@ const imageAttrs = { const github = config.social.filter(s => s.icon === 'github')[0]; -const command = "npm i -g"; -const pkg = "opencode-ai"; +const command = "curl -fsSL" +const protocol = "https://" +const url = "opencode.ai/install" +const bash = "| bash" let darkImage: ImageMetadata | undefined; let lightImage: ImageMetadata | undefined; @@ -51,11 +53,14 @@ if (image) {
-
@@ -133,39 +138,43 @@ section.top { section.cta { display: flex; flex-direction: row; - justify-content: space-between; + justify-content: flex-start; + align-items: stretch; border-top: 2px solid var(--sl-color-border); - @media (max-width: 40rem) { + @media (max-width: 50rem) { flex-direction: column; + + & > div.col1 { order: 1; } + & > div.col3 { order: 2; } + & > div.col2 { order: 3; } } & > div { - flex: 1; line-height: 1.4; - padding: calc(var(--padding) / 2) 0.5rem; - - @media (max-width: 40rem) { - padding-bottom: calc(var(--padding) / 2 + 4px); - } + padding: calc(var(--padding) / 2) 1rem; a { font-size: 1rem; } } - & > div.col2 { + & > div.col1, & > div.col3 { + flex: 1 1 auto; + text-align: center; + text-transform: uppercase; + @media (max-width: 50rem) { - flex: 0 0 auto; + padding-bottom: calc(var(--padding) / 2 + 4px); } } - & > div:not(.col2) { - text-align: center; - text-transform: uppercase; + & > div.col2 { + flex: 0 0 auto; } & > div + div { border-left: 2px solid var(--sl-color-border); - @media (max-width: 40rem) { + + @media (max-width: 50rem) { border-left: none; border-top: 2px solid var(--sl-color-border); } @@ -183,6 +192,21 @@ section.cta { code { color: var(--sl-color-text-secondary); font-size: 1.125rem; + + @media (max-width: 24rem) { + font-size: 0.875rem; + } + @media (max-width: 30rem) { + span.protocol { + display: none; + } + } + @media (max-width: 43rem) { + text-align: center; + span:first-child { + display: block; + } + } } code .highlight { color: var(--sl-color-text); @@ -192,6 +216,9 @@ section.cta { .copy { line-height: 1; padding: 0; + @media (max-width: 43rem) { + display: none; + } } .copy svg { width: 1rem; -- cgit v1.2.3 From ebcf11e574d0ebb056248e84f495789e1b211437 Mon Sep 17 00:00:00 2001 From: Jay V Date: Thu, 26 Jun 2025 19:47:58 -0400 Subject: docs: lander tweak --- packages/web/src/components/Lander.astro | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/web/src') diff --git a/packages/web/src/components/Lander.astro b/packages/web/src/components/Lander.astro index 8d4d4efed..82c33f3b3 100644 --- a/packages/web/src/components/Lander.astro +++ b/packages/web/src/components/Lander.astro @@ -58,8 +58,7 @@ if (image) {
) }} - {/* System text */} - - {(part) => ( -
-
- - - -
-
-
-
-
- System -
- -
-
-
- )} -
+ {/* Grep tool */} toolData()?.args.path !== data().rootDir ? stripWorkingDirectory( - toolData()?.args.path, - data().rootDir, - ) + toolData()?.args.path, + data().rootDir, + ) : toolData()?.args.path, ) @@ -1737,8 +1670,7 @@ export default function Share(props: { when={ msg.role === "assistant" && part.type === "tool-invocation" && - part.toolInvocation.toolName === - "todowrite" && + part.toolInvocation.toolName === "todowrite" && part } > @@ -1803,8 +1735,7 @@ export default function Share(props: { when={ msg.role === "assistant" && part.type === "tool-invocation" && - part.toolInvocation.toolName === - "webfetch" && + part.toolInvocation.toolName === "webfetch" && part } > @@ -1978,9 +1909,7 @@ export default function Share(props: { > - - - + -- cgit v1.2.3 From 87d62514dbce9fc0222a99d092c86ea7e6314cbb Mon Sep 17 00:00:00 2001 From: Jay V Date: Fri, 27 Jun 2025 13:25:15 -0400 Subject: docs: share page write tool bug --- packages/web/src/components/Share.tsx | 14 +++++++------- packages/web/src/types/lang-map.d.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) (limited to 'packages/web/src') diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index 9b953bd2d..e8879c50a 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -85,7 +85,7 @@ function scrollToAnchor(id: string) { el.scrollIntoView({ behavior: "smooth" }) } -function stripWorkingDirectory(filePath: string, workingDir?: string) { +function stripWorkingDirectory(filePath?: string, workingDir?: string) { if (filePath === undefined || workingDir === undefined) return filePath const prefix = workingDir.endsWith("/") ? workingDir : workingDir + "/" @@ -1307,9 +1307,9 @@ export default function Share(props: { const path = createMemo(() => toolData()?.args.path !== data().rootDir ? stripWorkingDirectory( - toolData()?.args.path, - data().rootDir, - ) + toolData()?.args.path, + data().rootDir, + ) : toolData()?.args.path, ) @@ -1470,7 +1470,7 @@ export default function Share(props: { {(_part) => { const filePath = createMemo(() => stripWorkingDirectory( - toolData()?.args.filePath, + toolData()?.args?.filePath, data().rootDir, ), ) @@ -1509,7 +1509,7 @@ export default function Share(props: {
{formatErrorString( - toolData()?.result, + toolData()?.result )}
@@ -1528,7 +1528,7 @@ export default function Share(props: {
diff --git a/packages/web/src/types/lang-map.d.ts b/packages/web/src/types/lang-map.d.ts index e69de29bb..b21d2a005 100644 --- a/packages/web/src/types/lang-map.d.ts +++ b/packages/web/src/types/lang-map.d.ts @@ -0,0 +1,27 @@ +declare module "lang-map" { + /** Returned by calling `map()` */ + export interface MapReturn { + /** All extensions keyed by language name */ + extensions: Record; + /** All languages keyed by file-extension */ + languages: Record; + } + + /** + * Calling `map()` gives you the raw lookup tables: + * + * ```js + * const { extensions, languages } = map(); + * ``` + */ + function map(): MapReturn; + + /** Static method: get extensions for a given language */ + namespace map { + function extensions(language: string): string[]; + /** Static method: get languages for a given extension */ + function languages(extension: string): string[]; + } + + export = map; +} -- cgit v1.2.3 From fa2723f2d0033c5b566abea035062e799a8634c6 Mon Sep 17 00:00:00 2001 From: Jay V Date: Fri, 27 Jun 2025 14:04:09 -0400 Subject: docs: update logo screenshot --- README.md | 8 ++++---- packages/web/public/social-share.png | Bin 17103 -> 17520 bytes .../web/src/assets/lander/screenshot-splash.png | Bin 0 -> 467281 bytes packages/web/src/assets/lander/screenshot.png | Bin 0 -> 552769 bytes packages/web/src/assets/logo-ornate-dark.svg | 18 ++++++++++++++++++ packages/web/src/assets/logo-ornate-light.svg | 18 ++++++++++++++++++ packages/web/src/assets/themes/ayu.png | Bin 449635 -> 0 bytes packages/web/src/assets/themes/everforest.png | Bin 448402 -> 0 bytes packages/web/src/assets/themes/opencode.png | Bin 442445 -> 0 bytes packages/web/src/assets/themes/tokyonight.png | Bin 452738 -> 0 bytes packages/web/src/components/Lander.astro | 2 +- packages/web/src/content/docs/docs/index.mdx | 2 +- packages/web/src/content/docs/index.mdx | 4 ++-- 13 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 packages/web/src/assets/lander/screenshot-splash.png create mode 100644 packages/web/src/assets/lander/screenshot.png create mode 100644 packages/web/src/assets/logo-ornate-dark.svg create mode 100644 packages/web/src/assets/logo-ornate-light.svg delete mode 100644 packages/web/src/assets/themes/ayu.png delete mode 100644 packages/web/src/assets/themes/everforest.png delete mode 100644 packages/web/src/assets/themes/opencode.png delete mode 100644 packages/web/src/assets/themes/tokyonight.png (limited to 'packages/web/src') diff --git a/README.md b/README.md index 6cbe9619a..9aa47f988 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@

- - - opencode logo + + + opencode logo

@@ -14,7 +14,7 @@ Build status

-[![opencode Terminal UI](packages/web/src/assets/themes/opencode.png)](https://opencode.ai) +[![opencode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai) --- diff --git a/packages/web/public/social-share.png b/packages/web/public/social-share.png index 39b86514c..97f67994d 100644 Binary files a/packages/web/public/social-share.png and b/packages/web/public/social-share.png differ diff --git a/packages/web/src/assets/lander/screenshot-splash.png b/packages/web/src/assets/lander/screenshot-splash.png new file mode 100644 index 000000000..e900673ef Binary files /dev/null and b/packages/web/src/assets/lander/screenshot-splash.png differ diff --git a/packages/web/src/assets/lander/screenshot.png b/packages/web/src/assets/lander/screenshot.png new file mode 100644 index 000000000..d49a62b4d Binary files /dev/null and b/packages/web/src/assets/lander/screenshot.png differ diff --git a/packages/web/src/assets/logo-ornate-dark.svg b/packages/web/src/assets/logo-ornate-dark.svg new file mode 100644 index 000000000..b937be0af --- /dev/null +++ b/packages/web/src/assets/logo-ornate-dark.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/web/src/assets/logo-ornate-light.svg b/packages/web/src/assets/logo-ornate-light.svg new file mode 100644 index 000000000..b519ec41a --- /dev/null +++ b/packages/web/src/assets/logo-ornate-light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/web/src/assets/themes/ayu.png b/packages/web/src/assets/themes/ayu.png deleted file mode 100644 index 0c4af5026..000000000 Binary files a/packages/web/src/assets/themes/ayu.png and /dev/null differ diff --git a/packages/web/src/assets/themes/everforest.png b/packages/web/src/assets/themes/everforest.png deleted file mode 100644 index 611e95530..000000000 Binary files a/packages/web/src/assets/themes/everforest.png and /dev/null differ diff --git a/packages/web/src/assets/themes/opencode.png b/packages/web/src/assets/themes/opencode.png deleted file mode 100644 index cc5c5d007..000000000 Binary files a/packages/web/src/assets/themes/opencode.png and /dev/null differ diff --git a/packages/web/src/assets/themes/tokyonight.png b/packages/web/src/assets/themes/tokyonight.png deleted file mode 100644 index 1d39ba075..000000000 Binary files a/packages/web/src/assets/themes/tokyonight.png and /dev/null differ diff --git a/packages/web/src/components/Lander.astro b/packages/web/src/components/Lander.astro index 82c33f3b3..596bca2d7 100644 --- a/packages/web/src/components/Lander.astro +++ b/packages/web/src/components/Lander.astro @@ -5,7 +5,7 @@ import type { Props } from '@astrojs/starlight/props'; import CopyIcon from "../assets/lander/copy.svg"; import CheckIcon from "../assets/lander/check.svg"; -import Screenshot from "../assets/themes/tokyonight.png"; +import Screenshot from "../assets/lander/screenshot-splash.png"; const { data } = Astro.locals.starlightRoute.entry; const { title = data.title, tagline, image, actions = [] } = data.hero || {}; diff --git a/packages/web/src/content/docs/docs/index.mdx b/packages/web/src/content/docs/docs/index.mdx index 0a8a8fa25..4926450c7 100644 --- a/packages/web/src/content/docs/docs/index.mdx +++ b/packages/web/src/content/docs/docs/index.mdx @@ -13,7 +13,7 @@ import { Tabs, TabItem } from '@astrojs/starlight/components'; - Log in with Anthropic to use your Claude Pro or Claude Max account. - Supports 75+ LLM providers through [Models.dev](https://models.dev), including local models. -![opencode TUI with the opencode theme](../../../assets/themes/opencode.png) +![opencode TUI with the opencode theme](../../../assets/lander/screenshot.png) --- diff --git a/packages/web/src/content/docs/index.mdx b/packages/web/src/content/docs/index.mdx index 1f735db21..ea39be9ee 100644 --- a/packages/web/src/content/docs/index.mdx +++ b/packages/web/src/content/docs/index.mdx @@ -6,7 +6,7 @@ hero: title: The AI coding agent built for the terminal. tagline: The AI coding agent built for the terminal. image: - dark: ../../assets/logo-dark.svg - light: ../../assets/logo-light.svg + dark: ../../assets/logo-ornate-dark.svg + light: ../../assets/logo-ornate-light.svg alt: opencode logo --- -- cgit v1.2.3 From 289797f56dbe7a7b51bc74ef8413da1a41a1b95b Mon Sep 17 00:00:00 2001 From: Jay V Date: Fri, 27 Jun 2025 15:31:10 -0400 Subject: docs: share cleanup title --- packages/web/src/components/Share.tsx | 122 +++++++++++++-------------- packages/web/src/components/share.module.css | 34 ++++++-- 2 files changed, 89 insertions(+), 67 deletions(-) (limited to 'packages/web/src') diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index e8879c50a..b48d04662 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -859,59 +859,15 @@ export default function Share(props: {

{store.info?.title}

-
    -
  • - Cost - {data().cost !== undefined ? ( - ${data().cost.toFixed(2)} - ) : ( - - )} -
  • -
  • - Input Tokens - {data().tokens.input ? ( - {data().tokens.input} - ) : ( - - )} -
  • -
  • - Output Tokens - {data().tokens.output ? ( - {data().tokens.output} - ) : ( - - )} -
  • -
  • - Reasoning Tokens - {data().tokens.reasoning ? ( - {data().tokens.reasoning} - ) : ( - - )} -
  • -
- -
    -
  • -
    - -
    - {data().rootDir} -
  • -
  • -
    - -
    - - v{store.info?.version} - -
  • -
-
    +
  • +
    + +
    + + v{store.info?.version} + +
  • {Object.values(data().models).length > 0 ? ( {([provider, model]) => ( @@ -1305,12 +1261,12 @@ export default function Share(props: { > {(_part) => { const path = createMemo(() => - toolData()?.args.path !== data().rootDir + toolData()?.args?.path !== data().rootDir ? stripWorkingDirectory( - toolData()?.args.path, + toolData()?.args?.path, data().rootDir, ) - : toolData()?.args.path, + : toolData()?.args?.path, ) return ( @@ -1332,7 +1288,9 @@ export default function Share(props: {
    LS - {path()} + + {path()} +
    @@ -1375,7 +1333,7 @@ export default function Share(props: { {(_part) => { const filePath = createMemo(() => stripWorkingDirectory( - toolData()?.args.filePath, + toolData()?.args?.filePath, data().rootDir, ), ) @@ -1398,7 +1356,9 @@ export default function Share(props: {
    Read - {filePath()} + + {filePath()} +
    @@ -1499,7 +1459,9 @@ export default function Share(props: {
    Write - {filePath()} + + {filePath()} +
    0}> {diagnostics()} @@ -1586,7 +1548,9 @@ export default function Share(props: {
    Edit - {filePath()} + + {filePath()} +
    @@ -1935,13 +1899,47 @@ export default function Share(props: { )} -
    +
    - {getStatusText(connectionStatus())} +

    {getStatusText(connectionStatus())}

    +
      +
    • + Cost + {data().cost !== undefined ? ( + ${data().cost.toFixed(2)} + ) : ( + + )} +
    • +
    • + Input Tokens + {data().tokens.input ? ( + {data().tokens.input} + ) : ( + + )} +
    • +
    • + Output Tokens + {data().tokens.output ? ( + {data().tokens.output} + ) : ( + + )} +
    • +
    • + Reasoning Tokens + {data().tokens.reasoning ? ( + {data().tokens.reasoning} + ) : ( + + )} +
    • +
    diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css index a52fd1765..41b257dad 100644 --- a/packages/web/src/components/share.module.css +++ b/packages/web/src/components/share.module.css @@ -89,7 +89,7 @@ padding: 0; margin: 0; display: flex; - gap: 0.5rem 1rem; + gap: 0.5rem 0.875rem; flex-wrap: wrap; li { @@ -104,8 +104,7 @@ } } - [data-section="stats"][data-section-root], - [data-section="stats"][data-section-models] { + [data-section="stats"] { li { gap: 0.3125rem; @@ -375,7 +374,7 @@ } } } - [data-part-type="connection-status"] { + [data-part-type="summary"] { & > [data-section="decoration"] { span:first-child { flex: 0 0 auto; @@ -405,12 +404,37 @@ } & > [data-section="content"] { - span { + display: flex; + flex-direction: column; + gap: 0.5rem; + + p[data-section="copy"] { display: block; line-height: 18px; font-size: 0.875rem; color: var(--sl-color-text-dimmed); } + + [data-section="stats"] { + list-style-type: none; + padding: 0; + margin: 0; + display: flex; + gap: 0.5rem 0.875rem; + flex-wrap: wrap; + + li { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; + color: var(--sl-color-text-secondary); + + span[data-placeholder] { + color: var(--sl-color-text-dimmed); + } + } + } } } } -- cgit v1.2.3 From 145df084440470bb53655f62b5f5588e2615f1ba Mon Sep 17 00:00:00 2001 From: Jay V Date: Fri, 27 Jun 2025 19:16:33 -0400 Subject: docs: share page format --- packages/web/src/components/Share.tsx | 67 ++++++++++++---------------- packages/web/src/components/share.module.css | 9 +--- 2 files changed, 30 insertions(+), 46 deletions(-) (limited to 'packages/web/src') diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index b48d04662..008f863c9 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -595,12 +595,17 @@ export default function Share(props: { info: Session.Info messages: Record }) { + let lastScrollY = 0 let hasScrolled = false + let scrollTimeout: number | undefined const id = props.id const params = new URLSearchParams(window.location.search) const debug = params.get("debug") === "true" + const [showScrollButton, setShowScrollButton] = createSignal(false) + const [isButtonHovered, setIsButtonHovered] = createSignal(false) + const anchorId = createMemo(() => { const raw = window.location.hash.slice(1) const [id] = raw.split("-") @@ -716,23 +721,18 @@ export default function Share(props: { }) }) - const [showScrollButton, setShowScrollButton] = createSignal(false) - const [isButtonHovered, setIsButtonHovered] = createSignal(false) - let scrollTimeout: number | undefined - let lastScrollY = 0 - - const checkScrollNeed = () => { + function checkScrollNeed() { const currentScrollY = window.scrollY const isScrollingDown = currentScrollY > lastScrollY const scrolled = currentScrollY > 200 // Show after scrolling 200px const isNearBottom = window.innerHeight + currentScrollY >= document.body.scrollHeight - 100 - + // Only show when scrolling down, scrolled enough, and not near bottom const shouldShow = isScrollingDown && scrolled && !isNearBottom - + // Update last scroll position lastScrollY = currentScrollY - + if (shouldShow) { setShowScrollButton(true) // Clear existing timeout @@ -754,30 +754,6 @@ export default function Share(props: { } } - const handleButtonMouseEnter = () => { - setIsButtonHovered(true) - // Clear timeout when hovering - if (scrollTimeout) { - clearTimeout(scrollTimeout) - } - } - - const handleButtonMouseLeave = () => { - setIsButtonHovered(false) - // Restart timeout when leaving hover - if (showScrollButton()) { - scrollTimeout = window.setTimeout(() => { - if (!isButtonHovered()) { - setShowScrollButton(false) - } - }, 3000) - } - } - - const scrollToBottom = () => { - document.body.scrollIntoView({ behavior: "smooth", block: "end" }) - } - onMount(() => { lastScrollY = window.scrollY // Initialize scroll position checkScrollNeed() @@ -1982,14 +1958,29 @@ export default function Share(props: {
    - {/* Floating scroll to bottom button */}