diff --git a/src/components/FlipBook.tsx b/src/components/FlipBook.tsx new file mode 100644 index 0000000..3556bfd --- /dev/null +++ b/src/components/FlipBook.tsx @@ -0,0 +1,328 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' + +type FlipBookSheet = { + id: string + front: React.ReactNode + back?: React.ReactNode +} + +type FlipBookProps = { + title: string + sheets: FlipBookSheet[] + className?: string +} + +const TRANSITION_MS = 720 + +function usePrefersReducedMotion() { + const [reduced, setReduced] = useState(false) + + useEffect(() => { + const media = window.matchMedia('(prefers-reduced-motion: reduce)') + const update = () => setReduced(media.matches) + update() + + media.addEventListener('change', update) + return () => media.removeEventListener('change', update) + }, []) + + return reduced +} + +const PageHalf: React.FC<{ side: 'left' | 'right'; children?: React.ReactNode }> = ({ side, children }) => { + const isLeft = side === 'left' + + return ( +
+
+
{children}
+
+
+ ) +} + +const SheetSide: React.FC<{ + side: 'front' | 'back' + contentSide: 'left' | 'right' + children?: React.ReactNode +}> = ({ side, contentSide, children }) => { + const isBack = side === 'back' + + return ( +
+ {children} +
+ ) +} + +const Sheet: React.FC<{ + sheet: FlipBookSheet + index: number + sheetCount: number + shouldBeFlipped: boolean + isTurning: boolean + transitionMs: number +}> = ({ sheet, index, sheetCount, shouldBeFlipped, isTurning, transitionMs }) => { + const zIndex = isTurning ? sheetCount + 10 : sheetCount - index + const clipPath = useMemo(() => { + if (isTurning) return undefined + if (shouldBeFlipped) return 'inset(0 50% 0 0)' + return 'inset(0 0 0 50%)' + }, [isTurning, shouldBeFlipped]) + + return ( + + ) +} + +export const FlipBook: React.FC = ({ title, sheets, className }) => { + const reducedMotion = usePrefersReducedMotion() + const transitionMs = reducedMotion ? 0 : TRANSITION_MS + + const [currentIndex, setCurrentIndex] = useState(0) + const [turning, setTurning] = useState(null) + + const busy = Boolean(turning) + const canPrev = !busy && currentIndex > 0 + const canNext = !busy && currentIndex < sheets.length - 1 + + const swipeStartX = useRef(null) + + const scheduleTurn = useCallback( + (action: () => void) => { + if (transitionMs === 0) { + action() + setTurning(null) + return + } + + window.setTimeout(() => { + action() + setTurning(null) + }, transitionMs) + }, + [transitionMs], + ) + + const turnToNext = useCallback(() => { + if (!canNext) return + setTurning({ index: currentIndex, direction: 'next' }) + scheduleTurn(() => { + setCurrentIndex((value) => Math.min(sheets.length - 1, value + 1)) + }) + }, [canNext, currentIndex, scheduleTurn, sheets.length]) + + const turnToPrev = useCallback(() => { + if (!canPrev) return + setTurning({ index: currentIndex - 1, direction: 'prev' }) + scheduleTurn(() => { + setCurrentIndex((value) => Math.max(0, value - 1)) + }) + }, [canPrev, currentIndex, scheduleTurn]) + + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'ArrowRight' || event.key === ' ') { + event.preventDefault() + turnToNext() + } + if (event.key === 'ArrowLeft') { + event.preventDefault() + turnToPrev() + } + } + + window.addEventListener('keydown', onKeyDown) + return () => window.removeEventListener('keydown', onKeyDown) + }, [turnToNext, turnToPrev]) + + const onPointerDown: React.PointerEventHandler = (event) => { + swipeStartX.current = event.clientX + } + + const onPointerUp: React.PointerEventHandler = (event) => { + const startX = swipeStartX.current + swipeStartX.current = null + if (startX === null) return + + const deltaX = event.clientX - startX + if (Math.abs(deltaX) < 34) return + + if (deltaX < 0) { + turnToNext() + return + } + + turnToPrev() + } + + const progressLabel = useMemo(() => { + const page = Math.min(currentIndex + 1, sheets.length) + return `${page} / ${sheets.length}` + }, [currentIndex, sheets.length]) + + const currentSheet = sheets[currentIndex] + + return ( +
+
+

+ {title} +

+

点击翻页,或使用键盘 ← / →,移动端可左右滑动。

+
+ +
+
+
+ +
+ +
+ + +
+ {progressLabel} +
+ + +
+
+ ) +} diff --git a/src/components/SiteLayout.tsx b/src/components/SiteLayout.tsx index 52c239d..847cc35 100644 --- a/src/components/SiteLayout.tsx +++ b/src/components/SiteLayout.tsx @@ -5,6 +5,7 @@ import { Footer } from './Footer' export const SiteLayout: React.FC = () => { const location = useLocation() + const isHome = location.pathname === '/' useEffect(() => { if (location.hash) { @@ -21,11 +22,11 @@ export const SiteLayout: React.FC = () => { return (
-
+ {isHome ? null :
}
-
+ {isHome ? null :
}
) } diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 8dfadeb..6964ca8 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,360 +1,127 @@ -import React from 'react' +import React, { useMemo } from 'react' import { Link } from 'react-router-dom' +import { FlipBook } from '../components/FlipBook' -type HomeSectionProps = { - id: string - children: React.ReactNode - variant?: 'default' | 'alt' +type Entry = { + to: string + label: string + desc: string } -const HomeContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return
{children}
-} - -const HomeSection: React.FC = ({ id, children, variant = 'default' }) => { +const SentencePage: React.FC<{ children: React.ReactNode }> = ({ children }) => { return ( -
- {children} -
+
+

+ {children} +

+
) } -const SectionWorldView: React.FC = () => { +const LastPage: React.FC<{ entries: Entry[] }> = ({ entries }) => { return ( - -
-

- 全球化,正在进入高不确定性时代 -

-

- 市场、政策、数据与地缘风险同时叠加, -
- 任何一次跨境决策的失误,代价都在被放大。 -

-

- 对中国企业而言,出海不再只是机会判断, -
- 而是一项系统性能力挑战。 -

-
-
- ) -} - -const SectionDecisionUncertainty: React.FC = () => { - const questions = [ - '现在是否适合进入这个国家或区域?', - '合规风险会不会在后期集中爆发?', - '当前的增长模式,是否能复制到下一个市场?', - '如果业务受挫,是战略判断错误,还是信息不足?', - '是否存在尚未被识别的关键风险?', - ] - - return ( - -
-

- 困难的不是走向全球,而是如何在不确定中做出可控决策 -

- -
- {questions.map((question) => ( -
-

{question}

-
- ))} +
+
+
+ Navigation
-
- - ) -} - -const SectionCoreThesis: React.FC = () => { - return ( - -
-

- 合规,不是成本。 -
- 它是增长的 - 结构性杠杆。 +

+ 从这里进入六个板块

-

- 当合规被前置并系统化,它决定的不是“能否避免风险”, -
- 而是企业能否更快进入市场、建立信任,并实现可复制的增长。 +

+ 你可以把这页当作目录:继续探索 Why / How / System / Discovery / Solutions / Proof。

-
- ) -} -const SectionMethodology: React.FC = () => { - return ( - -
-

- 这是在复杂现实中被反复验证的方法论 -

- -
-

- 这一判断,来自长期服务中国企业全球化过程中的真实实践, - 也来自高度复杂、强监管与多变量环境下的系统性经验。 -

-

- 我们逐渐意识到: -
- 合规并非增长的对立面,而是增长的前置条件。 -

-
- -
-
- - Compliance as Growth - - —— 合规即增长 -
-
-
-
- ) -} - -const SectionSystemOverview: React.FC = () => { - const modules = [ - { - name: 'G-Comply', - desc: '我如何合法进入并持续运营一个海外市场?', - }, - { - name: 'G-Growth', - desc: '我如何在合规前提下,实现可持续且可复制的增长?', - }, - { - name: 'G-Brain', - desc: '多个 AI Agent 如何协同,支撑复杂决策?', - }, - { - name: 'Discovery', - desc: '我如何持续获得高质量、可行动的全球决策信号?', - }, - ] - - return ( - -
-

- 当问题复杂到无法依赖个人经验时,必须用系统解决 -

- -

- 图灵环流(TuringFlow),致力于将复杂的全球化问题, -
- 转化为可执行、可验证、可规模化的系统能力。 -

- -
- {modules.map((module) => ( -
-
-
- {module.name} -
-
Module
+
+ {entries.map((entry) => ( + +
+
+ {entry.label}
-

- {module.desc} -

+
- ))} -
-
- - ) -} - -const SectionDiscoveryIntro: React.FC = () => { - const tags = ['每日晨报', '政策研究', '行业趋势', '深度报告'] - - return ( - -
-

- Discovery,是我们的全球决策雷达 -

- -

- 我们持续追踪全球政策、市场、行业与地缘变化, -
- 并将大量碎片化信息,深度加工为可用于决策的判断。 -

- -
- {tags.map((tag) => ( - - {tag} - - ))} -
-
-
- ) -} - -const SectionMeasurableOutcome: React.FC = () => { - const points = [ - '全球合规与市场进入决策周期显著缩短', - '关键风险提前识别与预警', - '增长路径的可复制性明显提升', - '跨市场决策一致性持续增强', - ] - - return ( - -
-

- 系统带来的,是可以被度量的确定性 -

- -
- {points.map((point) => ( -
-
- ))} -
-
-
- ) -} - -const SectionTrustProof: React.FC = () => { - const logos = ['Gov', 'Enterprise', 'Consortium', 'ThinkTank', 'Academia', 'Partner'] - - return ( - -
-

- 被用于复杂场景,才有价值 -

- -

- 图灵环流的能力,已在政府级项目及多行业实践中得到验证。 -

- -
- {logos.map((label) => ( -
- {label} -
- ))} -
-
-
- ) -} - -const SectionSoftCTA: React.FC = () => { - return ( - -
-

- 探讨你的全球化路径 -

- -
- - - 查看一份真实的全球决策示例 - - - → - +

{entry.desc}

- - - - 与我们交流一个具体市场或业务场景 - - - → - - -
+ ))}
-
+ +
+ Tip: 键盘 → 翻到最后页更快。 +
+
) } export const Home: React.FC = () => { + const entries = useMemo( + () => [ + { to: '/why', label: 'Why', desc: '问题的根源与外部环境。' }, + { to: '/how', label: 'How', desc: '方法论与执行路径。' }, + { to: '/system', label: 'System', desc: '系统构成与模块协同。' }, + { to: '/discovery', label: 'Discovery', desc: '持续获取全球决策信号。' }, + { to: '/solutions', label: 'Solutions', desc: '解决方案与落地方式。' }, + { to: '/proof', label: 'Proof', desc: '案例与可信度证据。' }, + ], + [], + ) + + const sheets = useMemo( + () => [ + { + id: 'p-1', + front: 这不是一个首页。, + }, + { + id: 'p-2', + front: 它是一页页翻开的叙事。, + }, + { + id: 'p-3', + front: 每一页,都只说一句。, + }, + { + id: 'p-4', + front: 把复杂的问题,拆成可读的片段。, + }, + { + id: 'p-5', + front: 把模糊的焦虑,变成清晰的判断。, + }, + { + id: 'p-6', + front: 把“出海”,从机会变成能力。, + }, + { + id: 'p-7', + front: 然后,进入真正的内容。, + }, + { + id: 'p-8', + front: , + }, + ], + [entries], + ) + return ( -
- - - - - - - - - +
+ ) }