/** CourseCard —— 教程的入口载体(孟菲斯骨架 × 拟物纸感) * 卡头三件套=靶子:▲目标 + ○预期结果(16:9 相框) + ●积木点列 * 交互:hover/聚焦拿起(浮起+投影加深)并播放 GIF;desc ⇄ GIF 每 8000ms 翻转; * hover/Tab 锁定;触屏与 prefers-reduced-motion 不翻转。 */ import { useEffect, useRef, useState, type ReactNode } from 'react' import type { Chapter } from '../lib/types' import { Ring, Tri } from './shapes' const FLIP_MS = 8000 export function BrickDots({ bricks }: { bricks: Chapter['target']['bricks'] }) { return ( b.learned).length}/${bricks.length}`}> {bricks.map((b, i) => ( ))} ) } export function EntryBadge({ n }: { n: 1 | 2 | 3 }) { return ( {'①②③'[n - 1]} ) } export function MemphisCard({ chapter, entry, compact = false, zoneText, }: { chapter: Chapter entry?: 1 | 2 | 3 compact?: boolean /** 静置态相框文字(可含 荧光标记):教程卡片的价值区用法示例 */ zoneText?: ReactNode }) { const [gifOn, setGifOn] = useState(false) const [locked, setLocked] = useState(false) const boxRef = useRef(null) useEffect(() => { const coarse = matchMedia('(pointer: coarse)').matches const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches if (coarse || reduced) return const io = new IntersectionObserver( ([e]) => { if (e.isIntersecting) setGifOn(true) else setGifOn(false) }, { threshold: 0.3 }, ) if (boxRef.current) io.observe(boxRef.current) return () => io.disconnect() }, []) useEffect(() => { if (!gifOn) return const t = setInterval(() => { setFlip((f) => !f) }, FLIP_MS) return () => clearInterval(t) }, [gifOn]) const [flip, setFlip] = useState(false) const showGif = gifOn && (locked ? true : flip) return ( setLocked(true)} onMouseLeave={() => setLocked(false)} onFocus={() => setLocked(true)} onBlur={() => setLocked(false)} aria-label={`${chapter.title}:${chapter.target.goal}`} > {entry && } {/* ○ 预期结果:16:9 相框(章节撞色只进这里) */}
{showGif ? ( chapter.gifUrl ? ( {chapter.target.expectation} ) : ( ▶ {chapter.target.expectation} ) ) : flip ? ( {chapter.target.goal} ) : zoneText ? ( /* 静置态(用法示例):过程文字骑在相框里,价值区短语带荧光标记 */ {zoneText} ) : ( /* 静置态:章节 emoji 是相框的视觉主体(对齐设计稿解剖卡) */ {chapter.emoji} )} 预期结果{showGif ? ' · GIF' : ''}
{/* 卡头:▲ 目标 + 标题 + ● 积木点列 */}
{chapter.title} {!compact && }
) }