This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git
The following commit(s) were added to refs/heads/master by this push:
new 95e247b3a87 [refactor](next) add doris mascot (#3656)
95e247b3a87 is described below
commit 95e247b3a870edaa996b78f6447bce8512b4e0f6
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sat May 16 00:02:47 2026 -0700
[refactor](next) add doris mascot (#3656)
---
src/components/home-next/HomeNext.tsx | 2 +-
.../home-next/sections/FeaturesSection.tsx | 5 +-
src/components/home-next/sections/HeroSection.scss | 70 ++++++++++++++
src/components/home-next/sections/HeroSection.tsx | 101 ++++++++++++++++++++-
.../home-next/sections/UseCasesSection.scss | 33 +++----
.../home-next/sections/UseCasesSection.tsx | 3 +-
static/images/next/home-page/doris-mascot.png | Bin 0 -> 316741 bytes
7 files changed, 189 insertions(+), 25 deletions(-)
diff --git a/src/components/home-next/HomeNext.tsx
b/src/components/home-next/HomeNext.tsx
index 50ce993f63a..4270a82bba9 100644
--- a/src/components/home-next/HomeNext.tsx
+++ b/src/components/home-next/HomeNext.tsx
@@ -20,8 +20,8 @@ export default function HomeNext({ onSwitchBack }:
HomeNextProps): JSX.Element {
onSwitchBack={onSwitchBack}
>
<HeroSection />
- <FeaturesSection />
<UseCasesSection />
+ <FeaturesSection />
<EcosystemSection />
<DeploymentSection />
<CommunitySection />
diff --git a/src/components/home-next/sections/FeaturesSection.tsx
b/src/components/home-next/sections/FeaturesSection.tsx
index 124e99ff257..22ebc0d80e8 100644
--- a/src/components/home-next/sections/FeaturesSection.tsx
+++ b/src/components/home-next/sections/FeaturesSection.tsx
@@ -330,10 +330,7 @@ export function FeaturesSection(): JSX.Element {
<div className="features-next__header">
<div className="features-next__eyebrow">Core
Capabilities</div>
<h2 className="features-next__headline">
- <span className="features-next__headline-line">Build
For</span>
- <span className="features-next__headline-line
features-next__headline-line--accent">
- Every Kind of Modern Analytics Workload
- </span>
+ <span className="features-next__headline-line">Three
Pillars. One Engine</span>
</h2>
</div>
<div
diff --git a/src/components/home-next/sections/HeroSection.scss
b/src/components/home-next/sections/HeroSection.scss
index 50d67d855d5..0a779962f2a 100644
--- a/src/components/home-next/sections/HeroSection.scss
+++ b/src/components/home-next/sections/HeroSection.scss
@@ -123,10 +123,12 @@
display: flex;
flex-direction: column;
gap: 2px;
+ position: relative;
}
&__title-line {
display: block;
+ position: relative;
}
&__title-fast {
@@ -140,6 +142,67 @@
color: var(--hn-yellow);
}
+ // Mascot is anchored inside the "Analytics and" line span:
+ // `bottom: 100%` places the IMG's bottom edge at that line's top, then
+ // `translateY` pushes the IMG back down so the *hand blob* lands on the
+ // letter caps. Arms (thin) start higher and aren't the visual anchor —
+ // the wide round hands are.
+ //
+ // PNG geometry measured from doris-mascot.png (1344x768):
+ // • thin arms y=441–510 (above hands, not aligned)
+ // • hand blob top y=510 → 33.6% from image bottom
+ // • hand blob center y≈520 → 32.3% from image bottom
+ // • hand blob bottom y=548 → 28.6% from image bottom
+ // • bottom 28.5% (y=548–767) transparent
+ //
+ // translateY(33%) ≈ "hand top aligned with text line top, hand draping
+ // ~5% of mascot height into the letter caps".
+ &__title-line--mascot-host {
+ position: relative;
+ }
+
+ // Wrapper carries the absolute positioning that used to live on the IMG;
+ // it also acts as the positioning context for the pupil overlays so we
+ // can place them at percentage-of-image coordinates.
+ &__mascot-wrap {
+ display: block;
+ position: absolute;
+ bottom: 100%;
+ // Shift the mascot row left from the line's right edge so all three
+ // characters land over the text. The leftmost (purple) mascot should
+ // line up roughly with the "C" in "ANALYTICS".
+ right: 20%;
+ transform: translateY(34%);
+ width: 45%;
+ max-width: 320px;
+ min-width: 220px;
+ z-index: 2;
+ pointer-events: none;
+ user-select: none;
+ filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.28));
+ }
+
+ &__mascot {
+ display: block;
+ width: 100%;
+ height: auto;
+ }
+
+ // Pupils are positioned at eye-white centers via inline `left`/`top` and
+ // `translate(-50%, -50%)`. JS adds an extra `translate(...)` to point
+ // each pupil at the cursor, constrained inside its eye white. Pupil
+ // diameter is 50% of eye-white diameter, leaving 50% of room to move.
+ &__mascot-pupil {
+ position: absolute;
+ width: 1.7%;
+ aspect-ratio: 1;
+ background: var(--hn-ink);
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+ transition: transform 0.08s ease-out;
+ will-change: transform;
+ }
+
// ── Subtitle ───────────────────────────────────────────────────────────
&__sub {
@@ -1215,6 +1278,13 @@
&__title {
font-size: clamp(38px, 4.8vw, 54px);
}
+
+ // Mascot only shows on a full-width desktop viewport. Once the
+ // hero starts compressing, the precise right-percentage anchoring
+ // becomes unreliable, so we hide it.
+ &__mascot-wrap {
+ display: none;
+ }
}
.hn-search-results {
diff --git a/src/components/home-next/sections/HeroSection.tsx
b/src/components/home-next/sections/HeroSection.tsx
index 0bf955e2433..fe35c3bdb80 100644
--- a/src/components/home-next/sections/HeroSection.tsx
+++ b/src/components/home-next/sections/HeroSection.tsx
@@ -1,4 +1,4 @@
-import React, { JSX, useState, useEffect, useMemo } from 'react';
+import React, { JSX, useState, useEffect, useMemo, useRef } from 'react';
import './HeroSection.scss';
// ─── SVG atoms
───────────────────────────────────────────────────────────────
@@ -755,6 +755,102 @@ function ReportCarousel(): JSX.Element {
);
}
+// ─── Mascot + mouse-tracking eyes
────────────────────────────────────────────
+
+// Measured from doris-mascot.png (1344x768) — six eye-white centers, sorted
+// left-to-right. Each radius is ~1.7% of image width.
+const MASCOT_EYES: Array<{ cx: number; cy: number }> = [
+ { cx: 22.96, cy: 54.01 },
+ { cx: 28.59, cy: 54.01 },
+ { cx: 51.01, cy: 51.27 },
+ { cx: 56.39, cy: 51.95 },
+ { cx: 74.99, cy: 58.71 },
+ { cx: 80.30, cy: 59.91 },
+];
+const MASCOT_EYE_RADIUS_PCT = 1.7;
+const MASCOT_PUPIL_RADIUS_RATIO = 0.5;
+
+function MascotWithEyes(): JSX.Element {
+ const wrapRef = useRef<HTMLSpanElement>(null);
+ const pupilRefs = useRef<Array<HTMLSpanElement | null>>([]);
+ const lastMouseRef = useRef<{ x: number; y: number } | null>(null);
+
+ useEffect(() => {
+ if (typeof window === 'undefined') return undefined;
+
+ const update = (mouseX: number, mouseY: number) => {
+ const wrap = wrapRef.current;
+ if (!wrap) return;
+ const rect = wrap.getBoundingClientRect();
+ if (rect.width === 0 || rect.height === 0) return;
+ const eyeR = (MASCOT_EYE_RADIUS_PCT / 100) * rect.width;
+ const pupilR = eyeR * MASCOT_PUPIL_RADIUS_RATIO;
+ const maxOffset = eyeR - pupilR;
+
+ MASCOT_EYES.forEach((eye, i) => {
+ const pupil = pupilRefs.current[i];
+ if (!pupil) return;
+ const ex = rect.left + (eye.cx / 100) * rect.width;
+ const ey = rect.top + (eye.cy / 100) * rect.height;
+ const dx = mouseX - ex;
+ const dy = mouseY - ey;
+ const dist = Math.hypot(dx, dy);
+ // Soft follow: pupil ramps to its max offset as the cursor
+ // gets ~80 px away — close cursors don't pin the pupil to
+ // the edge of the eye white.
+ const scale = Math.min(dist / 80, 1);
+ const ox = dist > 0 ? (dx / dist) * maxOffset * scale : 0;
+ const oy = dist > 0 ? (dy / dist) * maxOffset * scale : 0;
+ pupil.style.transform = `translate(calc(-50% +
${ox.toFixed(2)}px), calc(-50% + ${oy.toFixed(2)}px))`;
+ });
+ };
+
+ const onMouseMove = (e: MouseEvent) => {
+ lastMouseRef.current = { x: e.clientX, y: e.clientY };
+ update(e.clientX, e.clientY);
+ };
+ const onTouchMove = (e: TouchEvent) => {
+ const t = e.touches[0];
+ if (!t) return;
+ lastMouseRef.current = { x: t.clientX, y: t.clientY };
+ update(t.clientX, t.clientY);
+ };
+ const onScroll = () => {
+ const m = lastMouseRef.current;
+ if (m) update(m.x, m.y);
+ };
+
+ window.addEventListener('mousemove', onMouseMove, { passive: true });
+ window.addEventListener('touchmove', onTouchMove, { passive: true });
+ window.addEventListener('scroll', onScroll, { passive: true });
+ return () => {
+ window.removeEventListener('mousemove', onMouseMove);
+ window.removeEventListener('touchmove', onTouchMove);
+ window.removeEventListener('scroll', onScroll);
+ };
+ }, []);
+
+ return (
+ <span className="hero-next__mascot-wrap" ref={wrapRef}
aria-hidden="true">
+ <img
+ className="hero-next__mascot"
+ src="/images/next/home-page/doris-mascot.png"
+ width={1344}
+ height={768}
+ alt=""
+ />
+ {MASCOT_EYES.map((eye, i) => (
+ <span
+ key={i}
+ ref={(el) => { pupilRefs.current[i] = el; }}
+ className="hero-next__mascot-pupil"
+ style={{ left: `${eye.cx}%`, top: `${eye.cy}%` }}
+ />
+ ))}
+ </span>
+ );
+}
+
// ─── HeroSection
─────────────────────────────────────────────────────────────
export function HeroSection(): JSX.Element {
@@ -785,8 +881,9 @@ export function HeroSection(): JSX.Element {
<span
className="hero-next__title-accent">Fast</span>
<LightningSvg size={56} />
</span>
- <span className="hero-next__title-line">
+ <span className="hero-next__title-line
hero-next__title-line--mascot-host">
<span
className="hero-next__title-accent">Analytics</span> and
+ <MascotWithEyes />
</span>
<span className="hero-next__title-line">
<span
className="hero-next__title-accent">Search</span> Database
diff --git a/src/components/home-next/sections/UseCasesSection.scss
b/src/components/home-next/sections/UseCasesSection.scss
index 780ee084f8a..a50c66d90e1 100644
--- a/src/components/home-next/sections/UseCasesSection.scss
+++ b/src/components/home-next/sections/UseCasesSection.scss
@@ -12,8 +12,8 @@
--uc-ink: #0F1A14;
position: relative;
- background: var(--uc-green-dark);
- color: var(--uc-cream-light);
+ background: var(--uc-cream);
+ color: var(--uc-ink);
padding: 48px 0 56px;
overflow: hidden;
isolation: isolate;
@@ -23,7 +23,7 @@
position: absolute;
inset: 0;
z-index: 0;
- background-image: radial-gradient(rgba(245, 239, 228, 0.06) 1px,
transparent 1px);
+ background-image: radial-gradient(rgba(6, 70, 50, 0.07) 1px,
transparent 1px);
background-size: 32px 32px;
mask-image: linear-gradient(180deg, transparent, black 20%, black 80%,
transparent);
-webkit-mask-image: linear-gradient(180deg, transparent, black 20%,
black 80%, transparent);
@@ -42,13 +42,13 @@
&__eyebrow {
@include type.eyebrow;
- color: var(--uc-green-glow);
+ color: var(--uc-green-deep);
margin-bottom: 16px;
}
&__headline {
@include type.section-title;
- color: var(--uc-yellow);
+ color: var(--uc-ink);
margin: 0;
span {
@@ -57,7 +57,7 @@
}
&__headline-accent {
- color: var(--uc-cream-light);
+ color: var(--uc-green-deep);
}
&__grid {
@@ -72,12 +72,12 @@
flex-direction: column;
padding: 20px 22px;
border-radius: 12px;
- background: rgba(245, 239, 228, 0.04);
- border: 1px solid rgba(245, 239, 228, 0.1);
+ background: var(--uc-cream-light);
+ border: 1px solid rgba(6, 70, 50, 0.08);
color: inherit;
text-decoration: none;
overflow: hidden;
- transition: transform 0.25s ease, background 0.25s ease, border-color
0.25s ease;
+ transition: transform 0.25s ease, background 0.25s ease, border-color
0.25s ease, box-shadow 0.25s ease;
&::before {
content: '';
@@ -86,7 +86,7 @@
left: 0;
right: 0;
height: 2px;
- background: var(--uc-yellow);
+ background: var(--uc-green-deep);
transform: translateX(-100%);
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
@@ -94,8 +94,9 @@
&:hover,
&:focus-visible {
transform: translateY(-4px);
- background: rgba(245, 239, 228, 0.07);
- border-color: rgba(245, 239, 228, 0.22);
+ background: #FFFCF5;
+ border-color: rgba(6, 70, 50, 0.18);
+ box-shadow: 0 12px 24px -16px rgba(6, 70, 50, 0.25);
color: inherit;
text-decoration: none;
@@ -105,7 +106,7 @@
.use-cases-next__card-link {
gap: 10px;
- color: var(--uc-yellow);
+ color: var(--uc-green-deep);
}
}
}
@@ -117,7 +118,7 @@
line-height: 1.15;
letter-spacing: -0.01em;
text-transform: uppercase;
- color: var(--uc-cream-light);
+ color: var(--uc-ink);
margin: 0 0 10px;
}
@@ -125,7 +126,7 @@
font-family: type.$font-sans;
font-size: 14px;
line-height: 1.5;
- color: rgba(245, 239, 228, 0.78);
+ color: rgba(15, 26, 20, 0.72);
margin: 0 0 12px;
flex: 1;
}
@@ -136,7 +137,7 @@
gap: 6px;
margin-top: auto;
@include type.micro-label;
- color: rgba(245, 239, 228, 0.7);
+ color: rgba(6, 70, 50, 0.7);
transition: gap 0.25s ease, color 0.25s ease;
}
}
diff --git a/src/components/home-next/sections/UseCasesSection.tsx
b/src/components/home-next/sections/UseCasesSection.tsx
index 887d6b2e3e7..167c771fcb6 100644
--- a/src/components/home-next/sections/UseCasesSection.tsx
+++ b/src/components/home-next/sections/UseCasesSection.tsx
@@ -46,8 +46,7 @@ export function UseCasesSection(): JSX.Element {
<div className="use-cases-next__header">
<div className="use-cases-next__eyebrow">Use Cases</div>
<h2 className="use-cases-next__headline"
id="use-cases-next-title">
- <span>One Engine,</span>
- <span
className="use-cases-next__headline-accent">Built for the Workloads That
Matter.</span>
+ <span>Where Doris Goes To Work</span>
</h2>
</div>
diff --git a/static/images/next/home-page/doris-mascot.png
b/static/images/next/home-page/doris-mascot.png
new file mode 100644
index 00000000000..2dcb7e29dee
Binary files /dev/null and b/static/images/next/home-page/doris-mascot.png
differ
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]