diff --git a/web/app/components/plugins/marketplace/home/__tests__/home-hero.spec.tsx b/web/app/components/plugins/marketplace/home/__tests__/home-hero.spec.tsx
index ca34330f7c2..4ee316826ab 100644
--- a/web/app/components/plugins/marketplace/home/__tests__/home-hero.spec.tsx
+++ b/web/app/components/plugins/marketplace/home/__tests__/home-hero.spec.tsx
@@ -26,12 +26,21 @@ describe('HomeHero', () => {
expect(screen.queryByText('marketplace.home.heroTitle')).not.toBeInTheDocument()
})
- it('renders the Google mark as an image instead of a blank iconify mask', () => {
+ it('renders the six decorative hero icons as images instead of iconify masks', () => {
const { container } = render()
- const google = container.querySelector('img[src*="google"]')
- expect(google).not.toBeNull()
+ for (const name of [
+ 'sparkling-fill',
+ 'plug-fill',
+ 'puzzle-fill',
+ 'brain-2-fill',
+ 'image-circle-ai-line',
+ 'voice-ai-fill',
+ ])
+ expect(container.querySelector(`img[src*="${name}"]`)).not.toBeNull()
+
+ expect(container.querySelector('img[src*="google"]')).toBeNull()
+ expect(container.querySelector('.i-ri-sparkling-fill')).toBeNull()
expect(container.querySelector('.i-custom-public-common-gmail')).toBeNull()
- expect(container.querySelector('.i-custom-public-common-dropbox')).toBeNull()
})
})
diff --git a/web/app/components/plugins/marketplace/home/assets/brain-2-fill.svg b/web/app/components/plugins/marketplace/home/assets/brain-2-fill.svg
new file mode 100644
index 00000000000..c747d0dbf1d
--- /dev/null
+++ b/web/app/components/plugins/marketplace/home/assets/brain-2-fill.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/plugins/marketplace/home/assets/dropbox-dark.svg b/web/app/components/plugins/marketplace/home/assets/dropbox-dark.svg
deleted file mode 100644
index c11c0b3aaa0..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/dropbox-dark.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/dropbox-light.svg b/web/app/components/plugins/marketplace/home/assets/dropbox-light.svg
deleted file mode 100644
index 53aa7782d95..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/dropbox-light.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/duckduckgo-dark.svg b/web/app/components/plugins/marketplace/home/assets/duckduckgo-dark.svg
deleted file mode 100644
index cd0e1754ae8..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/duckduckgo-dark.svg
+++ /dev/null
@@ -1,50 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/duckduckgo-light.svg b/web/app/components/plugins/marketplace/home/assets/duckduckgo-light.svg
deleted file mode 100644
index 6741fcf3ac3..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/duckduckgo-light.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/github-dark.svg b/web/app/components/plugins/marketplace/home/assets/github-dark.svg
deleted file mode 100644
index 82d17f7245a..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/github-dark.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/github-light.svg b/web/app/components/plugins/marketplace/home/assets/github-light.svg
deleted file mode 100644
index 2aeb967a900..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/github-light.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/google-dark.svg b/web/app/components/plugins/marketplace/home/assets/google-dark.svg
deleted file mode 100644
index 56e5343dfa2..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/google-dark.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/google-light.svg b/web/app/components/plugins/marketplace/home/assets/google-light.svg
deleted file mode 100644
index f760048810a..00000000000
--- a/web/app/components/plugins/marketplace/home/assets/google-light.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/web/app/components/plugins/marketplace/home/assets/image-circle-ai-line.svg b/web/app/components/plugins/marketplace/home/assets/image-circle-ai-line.svg
new file mode 100644
index 00000000000..d2fa982771e
--- /dev/null
+++ b/web/app/components/plugins/marketplace/home/assets/image-circle-ai-line.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/plugins/marketplace/home/assets/plug-fill.svg b/web/app/components/plugins/marketplace/home/assets/plug-fill.svg
new file mode 100644
index 00000000000..d6c546294e6
--- /dev/null
+++ b/web/app/components/plugins/marketplace/home/assets/plug-fill.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/plugins/marketplace/home/assets/puzzle-fill.svg b/web/app/components/plugins/marketplace/home/assets/puzzle-fill.svg
new file mode 100644
index 00000000000..f9e75e09c07
--- /dev/null
+++ b/web/app/components/plugins/marketplace/home/assets/puzzle-fill.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/plugins/marketplace/home/assets/sparkling-fill.svg b/web/app/components/plugins/marketplace/home/assets/sparkling-fill.svg
new file mode 100644
index 00000000000..3fa7e2c52af
--- /dev/null
+++ b/web/app/components/plugins/marketplace/home/assets/sparkling-fill.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/plugins/marketplace/home/assets/voice-ai-fill.svg b/web/app/components/plugins/marketplace/home/assets/voice-ai-fill.svg
new file mode 100644
index 00000000000..2124d153d36
--- /dev/null
+++ b/web/app/components/plugins/marketplace/home/assets/voice-ai-fill.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/app/components/plugins/marketplace/home/home-hero.module.css b/web/app/components/plugins/marketplace/home/home-hero.module.css
index 5a497cb038b..6b9608c782c 100644
--- a/web/app/components/plugins/marketplace/home/home-hero.module.css
+++ b/web/app/components/plugins/marketplace/home/home-hero.module.css
@@ -1,17 +1,43 @@
-.iconLight {
- display: block;
+.decorations {
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
}
-.iconDark {
- display: none;
+/* 40px cells + 1px divider-subtle lines, matching Figma header/Variant2. */
+.grid {
+ position: absolute;
+ inset: 0;
+ background-image:
+ linear-gradient(
+ to right,
+ transparent 20px,
+ var(--color-divider-subtle) 20px,
+ var(--color-divider-subtle) 21px,
+ transparent 21px
+ ),
+ linear-gradient(to bottom, var(--color-divider-subtle) 1px, transparent 1px);
+ background-size:
+ 41px 100%,
+ 100% 41px;
+ background-position:
+ center top,
+ left 40px;
}
-:global(html[data-theme='dark']) .iconLight {
- display: none;
-}
-
-:global(html[data-theme='dark']) .iconDark {
- display: block;
+.glow {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 640px;
+ height: 200px;
+ transform: translateX(-50%);
+ background: radial-gradient(
+ ellipse 50% 45% at 50% 45%,
+ var(--color-background-default) 0%,
+ var(--color-background-default) 38%,
+ transparent 72%
+ );
}
@media (max-width: 879px) {
diff --git a/web/app/components/plugins/marketplace/home/home-hero.tsx b/web/app/components/plugins/marketplace/home/home-hero.tsx
index 27b6e6efcc4..4e4f9c30526 100644
--- a/web/app/components/plugins/marketplace/home/home-hero.tsx
+++ b/web/app/components/plugins/marketplace/home/home-hero.tsx
@@ -1,18 +1,14 @@
'use client'
import type { ReactNode } from 'react'
-import { useEffect, useState } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
-import { motion, useReducedMotion } from 'motion/react'
import { useTranslation } from '#i18n'
-import dropboxDarkIcon from './assets/dropbox-dark.svg'
-import dropboxLightIcon from './assets/dropbox-light.svg'
-import duckDuckGoDarkIcon from './assets/duckduckgo-dark.svg'
-import duckDuckGoLightIcon from './assets/duckduckgo-light.svg'
-import githubDarkIcon from './assets/github-dark.svg'
-import githubLightIcon from './assets/github-light.svg'
-import googleDarkIcon from './assets/google-dark.svg'
-import googleLightIcon from './assets/google-light.svg'
+import brain2FillIcon from './assets/brain-2-fill.svg'
+import imageCircleAiLineIcon from './assets/image-circle-ai-line.svg'
+import plugFillIcon from './assets/plug-fill.svg'
+import puzzleFillIcon from './assets/puzzle-fill.svg'
+import sparklingFillIcon from './assets/sparkling-fill.svg'
+import voiceAiFillIcon from './assets/voice-ai-fill.svg'
import styles from './home-hero.module.css'
type HomeHeroProps = {
@@ -21,170 +17,58 @@ type HomeHeroProps = {
title?: ReactNode
}
-type HeroIconMark = {
- darkSrc: string
- lightSrc: string
+type HeroDecorationIcon = {
+ left: number
+ src: string
+ top: number
}
-const HERO_ICON_HOLD_MS = 4000
-const HERO_ICON_GAP_MS = 100
-const HERO_ICON_EXIT_MS = 400
+const heroIconSrc = (icon: { src: string } | string) => (typeof icon === 'string' ? icon : icon.src)
-const heroDecorationIconFrameClassName =
- 'absolute flex size-10 items-center justify-center overflow-hidden rounded-[10px] bg-components-panel-bg shadow-lg'
-
-// Rest `left`/`top` is the designed outward pose. Hidden offsets pull each
-// mark toward the title so enter/exit travel radially, not in place.
-const heroIconSlots = [
- { left: 99, top: 26, delay: 0, hiddenX: 32, hiddenY: 20 },
- { left: 12, top: 89, delay: 0.075, hiddenX: 36, hiddenY: -16 },
- { left: 589, top: 4, delay: 0, hiddenX: -32, hiddenY: 20 },
- { left: 653, top: 68, delay: 0.05, hiddenX: -36, hiddenY: -14 },
-] as const
-
-const heroIconSpring = {
- type: 'spring' as const,
- bounce: 0.4,
- duration: 0.25,
-}
-
-const heroIconVariantsForSlot = (slot: (typeof heroIconSlots)[number]) => ({
- hidden: {
- opacity: 0,
- scale: 0.5,
- x: slot.hiddenX,
- y: slot.hiddenY,
- },
- visible: {
- opacity: 1,
- scale: 1,
- x: 0,
- y: 0,
- },
-})
-
-// First set is the designed placement. The second set swaps left/right pairs so
-// the group replacement is visible with only these four marks.
-const heroIconSets: HeroIconMark[][] = [
- [
- { darkSrc: dropboxDarkIcon.src, lightSrc: dropboxLightIcon.src },
- { darkSrc: duckDuckGoDarkIcon.src, lightSrc: duckDuckGoLightIcon.src },
- { darkSrc: githubDarkIcon.src, lightSrc: githubLightIcon.src },
- { darkSrc: googleDarkIcon.src, lightSrc: googleLightIcon.src },
- ],
- [
- { darkSrc: githubDarkIcon.src, lightSrc: githubLightIcon.src },
- { darkSrc: googleDarkIcon.src, lightSrc: googleLightIcon.src },
- { darkSrc: dropboxDarkIcon.src, lightSrc: dropboxLightIcon.src },
- { darkSrc: duckDuckGoDarkIcon.src, lightSrc: duckDuckGoLightIcon.src },
- ],
+// Positions are Figma offsets from the 1512px canvas center, with the top
+// icon row shifted to y=0 so the marks sit in HomeHero instead of the header.
+const heroDecorationIcons: HeroDecorationIcon[] = [
+ { src: heroIconSrc(sparklingFillIcon), left: -450, top: 41 },
+ { src: heroIconSrc(plugFillIcon), left: -286, top: 0 },
+ { src: heroIconSrc(puzzleFillIcon), left: -327, top: 123 },
+ { src: heroIconSrc(brain2FillIcon), left: 247, top: 82 },
+ { src: heroIconSrc(imageCircleAiLineIcon), left: 370, top: 123 },
+ { src: heroIconSrc(voiceAiFillIcon), left: 411, top: 0 },
]
-// Brand marks stay file exports so multi-color logos render. Tailwind iconify
-// classes mask to currentColor and would flatten or hide them.
-const ThemeIcon = ({
- darkSrc,
- lightSrc,
-}: {
- darkSrc: string
- lightSrc: string
-}) => (
- <>
-
-
- >
+const HeroDecorations = () => (
+
+
+
+ {heroDecorationIcons.map((icon) => (
+
+
+
+
+
+ ))}
+
)
-const HeroDecorationIcons = () => {
- const reducedMotion = useReducedMotion()
- const [setIndex, setSetIndex] = useState(0)
- const [open, setOpen] = useState(true)
-
- useEffect(() => {
- if (reducedMotion)
- return
-
- if (open) {
- const timeoutId = window.setTimeout(() => {
- setOpen(false)
- }, HERO_ICON_HOLD_MS)
- return () => window.clearTimeout(timeoutId)
- }
-
- const timeoutId = window.setTimeout(() => {
- setSetIndex(current => (current + 1) % heroIconSets.length)
- setOpen(true)
- }, HERO_ICON_EXIT_MS + HERO_ICON_GAP_MS)
-
- return () => window.clearTimeout(timeoutId)
- }, [open, reducedMotion])
-
- const icons = heroIconSets[setIndex] ?? heroIconSets[0]
-
- if (reducedMotion) {
- return (
-
- {heroIconSets[0].map((icon, index) => {
- const slot = heroIconSlots[index]
- return (
-
-
-
- )
- })}
-
- )
- }
-
- return (
-
- {icons.map((icon, index) => {
- const slot = heroIconSlots[index]
- return (
-
-
-
- )
- })}
-
- )
-}
-
const HomeHero = ({ isMarketplacePlatform, subtitle, title }: HomeHeroProps) => {
const { t } = useTranslation('plugin')
return (