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 (
+
- -
+