This commit is contained in:
eric
2026-03-27 18:38:36 -05:00
parent 326bad4336
commit 759875ec36
4 changed files with 414 additions and 233 deletions

View File

@@ -9,8 +9,8 @@ export async function generateMetadata({
const titles: Record<string, string> = {
cloudphone: "云手机 | 低成本自建云手机",
livestream: "无人直播 | TikTok/YouTube 24小时自动化",
indie: "独立开发 | 即将上线",
aiagent: "AI Agent | 即将上线",
indie: "独立开发 | 课程筹备",
aiagent: "AI Agent | 课程筹备",
};
return {
title: titles[topicId] ?? "模块 | 异度星球",

View File

@@ -15,7 +15,7 @@ export function TopicComingSoon({ topicId }: TopicComingSoonProps) {
<section className="border-t border-[var(--border)] py-16 md:py-24">
<div className="mx-auto max-w-2xl px-4 text-center sm:px-6">
<div className="mb-6 text-6xl">{config.icon}</div>
<h2 className="mb-4 text-xl font-bold sm:text-2xl">线</h2>
<h2 className="mb-4 text-xl font-bold sm:text-2xl"></h2>
<p className="mb-8 text-[var(--muted-foreground)]">{config.desc}</p>
<Link
href={config.externalUrl}

View File

@@ -27,208 +27,370 @@ function disposeObject3D(root: THREE.Object3D) {
materials.forEach((m) => m.dispose());
}
/** 独立开发:居家书桌 + 显示器 + homelab 机架 */
function buildIndieDeskScene(root: THREE.Group) {
const wood = new THREE.MeshStandardMaterial({
color: 0x6b5344,
function applyFilmicRenderer(renderer: THREE.WebGLRenderer, exposure: number) {
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = exposure;
}
/** 独立开发:三面墙+顶+地 半封闭小房间(朝镜头开口),萌系配色与小狗 */
function buildIndieEnclosedRoom(root: THREE.Group): { dog: THREE.Group; tail: THREE.Mesh } {
const wall = new THREE.MeshStandardMaterial({
color: 0xfff0f3,
roughness: 0.94,
metalness: 0,
});
const floorMat = new THREE.MeshStandardMaterial({
color: 0xf5e0d4,
roughness: 0.88,
metalness: 0.05,
metalness: 0,
});
const deskDark = new THREE.MeshStandardMaterial({
color: 0x18181b,
roughness: 0.55,
metalness: 0.25,
});
const screenGlow = new THREE.MeshStandardMaterial({
color: 0x052e16,
emissive: 0x4ade80,
emissiveIntensity: 0.55,
roughness: 0.35,
});
const keyCap = new THREE.MeshStandardMaterial({
color: 0x3f3f46,
roughness: 0.45,
metalness: 0.15,
});
const rackLed = new THREE.MeshStandardMaterial({
color: 0x0c0a09,
emissive: 0xf59e0b,
emissiveIntensity: 0.5,
roughness: 0.4,
const ceilMat = new THREE.MeshStandardMaterial({
color: 0xfff7fb,
roughness: 0.92,
metalness: 0,
});
const floor = new THREE.Mesh(new THREE.PlaneGeometry(7, 5.5), wood);
const floor = new THREE.Mesh(new THREE.PlaneGeometry(5.2, 4.2), floorMat);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -1.15;
floor.position.y = -1;
root.add(floor);
const rug = new THREE.Mesh(new THREE.PlaneGeometry(2.8, 2), new THREE.MeshStandardMaterial({
color: 0x3d3832,
roughness: 0.95,
}));
rug.rotation.x = -Math.PI / 2;
rug.position.set(0, -1.14, 0.1);
root.add(rug);
const back = new THREE.Mesh(new THREE.PlaneGeometry(5.2, 3.2), wall);
back.position.set(0, 0.6, -2.1);
root.add(back);
const deskTop = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.07, 1), deskDark);
deskTop.position.set(0, -0.38, 0.15);
const left = new THREE.Mesh(new THREE.PlaneGeometry(4.2, 3.2), wall);
left.rotation.y = Math.PI / 2;
left.position.set(-2.6, 0.6, 0);
root.add(left);
const right = new THREE.Mesh(new THREE.PlaneGeometry(4.2, 3.2), wall);
right.rotation.y = -Math.PI / 2;
right.position.set(2.6, 0.6, 0);
root.add(right);
const ceiling = new THREE.Mesh(new THREE.PlaneGeometry(5.2, 4.2), ceilMat);
ceiling.rotation.x = Math.PI / 2;
ceiling.position.y = 2.2;
root.add(ceiling);
const lamp = new THREE.Mesh(
new THREE.PlaneGeometry(1.4, 0.9),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xfff1f2,
emissiveIntensity: 0.55,
roughness: 1,
}),
);
lamp.rotation.x = Math.PI / 2;
lamp.position.set(0, 2.18, 0.2);
root.add(lamp);
const deskMat = new THREE.MeshStandardMaterial({
color: 0xf5e6d8,
roughness: 0.5,
metalness: 0.04,
});
const deskTop = new THREE.Mesh(new THREE.BoxGeometry(1.65, 0.055, 0.72), deskMat);
deskTop.position.set(-0.25, -0.45, 0.05);
root.add(deskTop);
const legGeo = new THREE.BoxGeometry(0.08, 0.45, 0.08);
const legPositions: [number, number, number][] = [
[-1, -0.65, -0.25],
[1, -0.65, -0.25],
[-1, -0.65, 0.55],
[1, -0.65, 0.55],
];
for (const [x, y, z] of legPositions) {
const leg = new THREE.Mesh(legGeo, deskDark);
leg.position.set(x, y, z);
const legGeo = new THREE.CylinderGeometry(0.038, 0.042, 0.36, 14);
const legMat = new THREE.MeshStandardMaterial({ color: 0xe8d5c4, roughness: 0.58 });
for (const [x, z] of [
[-0.72, -0.22],
[0.22, -0.22],
[-0.72, 0.38],
[0.22, 0.38],
]) {
const leg = new THREE.Mesh(legGeo, legMat);
leg.position.set(x, -0.66, z);
root.add(leg);
}
const monitorBezel = new THREE.Mesh(new THREE.BoxGeometry(1, 0.62, 0.045), deskDark);
monitorBezel.position.set(0, 0.12, -0.28);
root.add(monitorBezel);
const bezel = new THREE.MeshStandardMaterial({
color: 0x3f3f46,
roughness: 0.42,
metalness: 0.12,
});
const monitor = new THREE.Mesh(new THREE.BoxGeometry(0.62, 0.42, 0.028), bezel);
monitor.position.set(-0.25, -0.08, -0.38);
root.add(monitor);
const screen = new THREE.Mesh(new THREE.PlaneGeometry(0.88, 0.52), screenGlow);
screen.position.set(0, 0.12, -0.28 + 0.023);
const screenMat = new THREE.MeshStandardMaterial({
color: 0xa5d8ff,
emissive: 0xc5eaff,
emissiveIntensity: 0.28,
roughness: 0.4,
});
const screen = new THREE.Mesh(new THREE.PlaneGeometry(0.56, 0.36), screenMat);
screen.position.set(-0.25, -0.08, -0.38 + 0.015);
root.add(screen);
const stand = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.12, 0.15), deskDark);
stand.position.set(0, -0.28, -0.22);
root.add(stand);
const keyboard = new THREE.Mesh(new THREE.BoxGeometry(0.75, 0.035, 0.3), keyCap);
keyboard.position.set(0, -0.32, 0.38);
keyboard.rotation.x = -0.06;
root.add(keyboard);
const mug = new THREE.Mesh(
new THREE.CylinderGeometry(0.09, 0.08, 0.12, 16),
new THREE.MeshStandardMaterial({ color: 0x3f3f46, roughness: 0.4 }),
const kb = new THREE.Mesh(
new THREE.BoxGeometry(0.48, 0.022, 0.18),
new THREE.MeshStandardMaterial({ color: 0xa8a29e, roughness: 0.55 }),
);
mug.position.set(-0.85, -0.31, 0.42);
root.add(mug);
kb.position.set(-0.25, -0.4, 0.22);
kb.rotation.x = -0.035;
root.add(kb);
const rack = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.95, 0.42), deskDark);
rack.position.set(1.05, 0.1, -0.05);
root.add(rack);
const tower = new THREE.Mesh(
new THREE.BoxGeometry(0.18, 0.36, 0.32),
new THREE.MeshStandardMaterial({ color: 0xfafafa, roughness: 0.32 }),
);
tower.position.set(0.38, -0.38, -0.28);
root.add(tower);
const rackFace = new THREE.Mesh(new THREE.PlaneGeometry(0.3, 0.82), rackLed);
rackFace.position.set(1.05 + 0.191, 0.1, -0.05);
root.add(rackFace);
const chairSeat = new THREE.Mesh(
new THREE.BoxGeometry(0.4, 0.055, 0.36),
new THREE.MeshStandardMaterial({ color: 0x78716c, roughness: 0.72 }),
);
chairSeat.position.set(-0.25, -0.72, 0.55);
root.add(chairSeat);
for (let row = 0; row < 4; row++) {
const led = new THREE.Mesh(
new THREE.PlaneGeometry(0.22, 0.04),
new THREE.MeshStandardMaterial({
color: 0x000000,
emissive: row % 2 === 0 ? 0x22c55e : 0xf59e0b,
emissiveIntensity: 0.65,
}),
);
led.position.set(1.05 + 0.192, 0.35 - row * 0.18, -0.05);
root.add(led);
const fur = new THREE.MeshStandardMaterial({
color: 0xffc4a8,
roughness: 0.82,
metalness: 0,
});
const furDeep = new THREE.MeshStandardMaterial({
color: 0xf59e7c,
roughness: 0.85,
metalness: 0,
});
const dog = new THREE.Group();
dog.position.set(0.75, -0.86, 0.22);
const body = new THREE.Mesh(new THREE.SphereGeometry(0.2, 22, 18), fur);
body.scale.set(1.05, 0.78, 1.12);
body.position.y = 0.11;
dog.add(body);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.16, 20, 18), fur);
head.position.set(0, 0.3, 0.1);
dog.add(head);
const snout = new THREE.Mesh(new THREE.SphereGeometry(0.065, 14, 14), furDeep);
snout.position.set(0, 0.26, 0.22);
snout.scale.set(1, 0.88, 1.15);
dog.add(snout);
const nose = new THREE.Mesh(
new THREE.SphereGeometry(0.022, 10, 10),
new THREE.MeshStandardMaterial({ color: 0x292524, roughness: 0.35 }),
);
nose.position.set(0, 0.28, 0.3);
dog.add(nose);
const eyeWhiteL = new THREE.Mesh(
new THREE.SphereGeometry(0.045, 12, 12),
new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.25 }),
);
eyeWhiteL.position.set(-0.055, 0.32, 0.18);
const eyeWhiteR = eyeWhiteL.clone();
eyeWhiteR.position.x = 0.055;
dog.add(eyeWhiteL, eyeWhiteR);
const pupilL = new THREE.Mesh(
new THREE.SphereGeometry(0.022, 10, 10),
new THREE.MeshStandardMaterial({ color: 0x1c1917, roughness: 0.2 }),
);
pupilL.position.set(-0.052, 0.32, 0.2);
const pupilR = pupilL.clone();
pupilR.position.x = 0.052;
dog.add(pupilL, pupilR);
const blushMat = new THREE.MeshStandardMaterial({
color: 0xff9eb5,
roughness: 0.6,
emissive: 0xff8fab,
emissiveIntensity: 0.12,
});
const blushL = new THREE.Mesh(new THREE.SphereGeometry(0.035, 10, 10), blushMat);
blushL.scale.set(1.2, 0.45, 0.6);
blushL.position.set(-0.1, 0.24, 0.16);
const blushR = blushL.clone();
blushR.position.x = 0.1;
dog.add(blushL, blushR);
const earL = new THREE.Mesh(new THREE.SphereGeometry(0.055, 12, 12), furDeep);
earL.position.set(-0.11, 0.36, 0.05);
earL.scale.set(0.45, 0.95, 0.4);
const earR = earL.clone();
earR.position.x = 0.11;
dog.add(earL, earR);
const tail = new THREE.Mesh(
new THREE.CylinderGeometry(0.028, 0.036, 0.2, 12),
furDeep,
);
tail.rotation.z = Math.PI / 2.8;
tail.rotation.x = 0.25;
tail.position.set(-0.18, 0.16, -0.06);
dog.add(tail);
for (const [x, z] of [
[-0.09, 0.07],
[0.09, 0.07],
[-0.07, -0.07],
[0.07, -0.07],
]) {
const paw = new THREE.Mesh(new THREE.SphereGeometry(0.048, 12, 12), furDeep);
paw.scale.set(1, 0.58, 1);
paw.position.set(x, 0, z);
dog.add(paw);
}
const miniServer = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.15, 0.18), deskDark);
miniServer.position.set(1.05, -0.28, 0.45);
root.add(miniServer);
root.add(dog);
return { dog, tail };
}
/** AI Agent赛博空间网格 + 机器人 + 霓虹光环 */
function buildAiCyberScene(root: THREE.Group) {
const grid = new THREE.GridHelper(9, 28, 0x6366f1, 0x312e81);
grid.position.y = -1.05;
const gMat = grid.material;
if (Array.isArray(gMat)) {
gMat.forEach((m) => {
if ("opacity" in m) {
(m as THREE.Material & { opacity: number }).opacity = 0.45;
m.transparent = true;
}
});
} else if (gMat && "opacity" in gMat) {
(gMat as THREE.Material & { opacity: number }).opacity = 0.45;
gMat.transparent = true;
}
root.add(grid);
const bodyMat = new THREE.MeshStandardMaterial({
color: 0x52525b,
metalness: 0.55,
roughness: 0.28,
flatShading: true,
});
const glowCyan = new THREE.MeshStandardMaterial({
color: 0x06b6d4,
emissive: 0x22d3ee,
emissiveIntensity: 0.85,
});
const glowMagenta = new THREE.MeshStandardMaterial({
color: 0xd946ef,
emissive: 0xc026d3,
emissiveIntensity: 0.55,
});
const robot = new THREE.Group();
robot.position.y = -0.15;
const torso = new THREE.Mesh(new THREE.BoxGeometry(0.52, 0.58, 0.32), bodyMat);
torso.position.y = 0.1;
robot.add(torso);
const chestPanel = new THREE.Mesh(new THREE.PlaneGeometry(0.35, 0.25), glowCyan);
chestPanel.position.set(0, 0.12, 0.161);
robot.add(chestPanel);
const head = new THREE.Mesh(new THREE.BoxGeometry(0.44, 0.36, 0.38), bodyMat);
head.position.y = 0.55;
robot.add(head);
const visor = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.12, 0.02), glowCyan);
visor.position.set(0, 0.52, 0.19);
robot.add(visor);
const eyeL = new THREE.Mesh(new THREE.SphereGeometry(0.055, 16, 16), glowCyan);
eyeL.position.set(-0.1, 0.56, 0.18);
const eyeR = new THREE.Mesh(new THREE.SphereGeometry(0.055, 16, 16), glowCyan);
eyeR.position.set(0.1, 0.56, 0.18);
robot.add(eyeL, eyeR);
const antenna = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.03, 0.28, 8), glowMagenta);
antenna.position.set(0, 0.82, 0);
robot.add(antenna);
const armL = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.45, 0.12), bodyMat);
armL.position.set(-0.38, 0.05, 0);
const armR = armL.clone();
armR.position.x = 0.38;
robot.add(armL, armR);
root.add(robot);
for (let i = 0; i < 3; i++) {
const ringMat = new THREE.MeshBasicMaterial({
color: i === 1 ? 0xa855f7 : 0x22d3ee,
transparent: true,
opacity: 0.35 - i * 0.08,
});
const ring = new THREE.Mesh(new THREE.TorusGeometry(0.95 + i * 0.22, 0.012, 8, 64), ringMat);
ring.rotation.x = Math.PI / 2;
ring.position.y = -0.85 + i * 0.15;
root.add(ring);
}
const holo = new THREE.Mesh(
new THREE.OctahedronGeometry(0.18, 0),
new THREE.MeshBasicMaterial({ color: 0x67e8f9, wireframe: true, transparent: true, opacity: 0.5 }),
/** AI:赛博空间,无网格线;萌系圆润机器人 + 实体光晕环(非线框) */
function buildAiCuteCyber(root: THREE.Group): {
bot: THREE.Group;
halo: THREE.Mesh;
orbs: THREE.Mesh[];
} {
const backdrop = new THREE.Mesh(
new THREE.SphereGeometry(16, 48, 48),
new THREE.MeshBasicMaterial({ color: 0x0c0618, side: THREE.BackSide }),
);
holo.position.set(-0.55, 0.35, 0.35);
root.add(holo);
root.add(backdrop);
return { robot, holo };
const floor = new THREE.Mesh(
new THREE.CircleGeometry(4.5, 64),
new THREE.MeshStandardMaterial({
color: 0x1e1435,
roughness: 0.55,
metalness: 0.15,
emissive: 0x4c1d95,
emissiveIntensity: 0.06,
}),
);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -1.05;
root.add(floor);
const haloMat = new THREE.MeshStandardMaterial({
color: 0xa78bfa,
emissive: 0x8b5cf6,
emissiveIntensity: 0.25,
roughness: 0.35,
metalness: 0.25,
transparent: true,
opacity: 0.55,
side: THREE.DoubleSide,
});
const halo = new THREE.Mesh(new THREE.TorusGeometry(1.05, 0.035, 16, 64), haloMat);
halo.rotation.x = Math.PI / 2;
halo.position.y = -0.35;
root.add(halo);
const shell = new THREE.MeshStandardMaterial({
color: 0xe8e0ff,
roughness: 0.42,
metalness: 0.18,
});
const shellDark = new THREE.MeshStandardMaterial({
color: 0xc4b5fd,
roughness: 0.45,
metalness: 0.22,
});
const eyeGlow = new THREE.MeshStandardMaterial({
color: 0x67e8f9,
emissive: 0x22d3ee,
emissiveIntensity: 0.35,
roughness: 0.28,
});
const bot = new THREE.Group();
bot.position.set(0, -0.25, 0);
const body = new THREE.Mesh(new THREE.SphereGeometry(0.32, 28, 24), shell);
body.scale.set(1, 0.88, 0.92);
body.position.y = 0.05;
bot.add(body);
const tummy = new THREE.Mesh(new THREE.CircleGeometry(0.18, 24), shellDark);
tummy.rotation.x = -0.15;
tummy.position.set(0, 0.02, 0.27);
bot.add(tummy);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.26, 28, 24), shell);
head.position.y = 0.48;
bot.add(head);
const eyeWL = new THREE.Mesh(new THREE.SphereGeometry(0.07, 14, 14), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.2 }));
eyeWL.position.set(-0.08, 0.5, 0.22);
const eyeWR = eyeWL.clone();
eyeWR.position.x = 0.08;
bot.add(eyeWL, eyeWR);
const eyeL = new THREE.Mesh(new THREE.SphereGeometry(0.038, 12, 12), eyeGlow);
eyeL.position.set(-0.078, 0.5, 0.26);
const eyeR = eyeL.clone();
eyeR.position.x = 0.078;
bot.add(eyeL, eyeR);
const cheek = new THREE.MeshStandardMaterial({
color: 0xf9a8d4,
emissive: 0xf472b6,
emissiveIntensity: 0.15,
roughness: 0.5,
});
const chL = new THREE.Mesh(new THREE.SphereGeometry(0.04, 10, 10), cheek);
chL.scale.set(1.3, 0.5, 0.5);
chL.position.set(-0.14, 0.42, 0.2);
const chR = chL.clone();
chR.position.x = 0.14;
bot.add(chL, chR);
const antStem = new THREE.Mesh(
new THREE.CylinderGeometry(0.02, 0.025, 0.16, 10),
shellDark,
);
antStem.position.set(0, 0.72, 0);
bot.add(antStem);
const antBall = new THREE.Mesh(new THREE.SphereGeometry(0.055, 16, 16), eyeGlow);
antBall.position.set(0, 0.82, 0);
bot.add(antBall);
const armGeo = new THREE.CapsuleGeometry(0.07, 0.22, 6, 12);
const armL = new THREE.Mesh(armGeo, shell);
armL.position.set(-0.42, 0.08, 0);
armL.rotation.z = 0.35;
const armR = new THREE.Mesh(armGeo, shell);
armR.position.set(0.42, 0.08, 0);
armR.rotation.z = -0.35;
bot.add(armL, armR);
const leg = new THREE.Mesh(new THREE.CapsuleGeometry(0.09, 0.12, 6, 12), shellDark);
leg.position.set(-0.12, -0.32, 0.05);
const leg2 = leg.clone();
leg2.position.x = 0.12;
bot.add(leg, leg2);
root.add(bot);
const orbMat = [
new THREE.MeshStandardMaterial({ color: 0xf0abfc, roughness: 0.35, metalness: 0.2 }),
new THREE.MeshStandardMaterial({ color: 0x7dd3fc, roughness: 0.35, metalness: 0.2 }),
new THREE.MeshStandardMaterial({ color: 0xfde68a, roughness: 0.35, metalness: 0.15 }),
];
const orbs: THREE.Mesh[] = [];
for (let i = 0; i < 3; i++) {
const o = new THREE.Mesh(new THREE.SphereGeometry(0.09, 16, 16), orbMat[i]);
const a = (i / 3) * Math.PI * 2;
o.position.set(Math.cos(a) * 1.15, 0.25 + i * 0.08, Math.sin(a) * 1.15);
root.add(o);
orbs.push(o);
}
return { bot, halo, orbs };
}
export function TopicPrepThreeScene({ variant }: Props) {
@@ -243,59 +405,70 @@ export function TopicPrepThreeScene({ variant }: Props) {
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true,
alpha: false,
powerPreference: "high-performance",
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 100);
camera.position.set(0, 0.35, 5.2);
camera.position.set(0.35, 0.45, 3.85);
const group = new THREE.Group();
scene.add(group);
let bobRefs: { robot?: THREE.Group; holo?: THREE.Mesh } | null = null;
let anim: {
dog?: THREE.Group;
tail?: THREE.Mesh;
bot?: THREE.Group;
halo?: THREE.Mesh;
orbs?: THREE.Mesh[];
} | null = null;
if (variant === "indie") {
scene.background = null;
const ambient = new THREE.AmbientLight(0xfff5e6, 0.45);
scene.add(ambient);
const warm = new THREE.PointLight(0xffb86b, 1.1, 12);
warm.position.set(0.6, 1.2, 1.2);
scene.add(warm);
const cool = new THREE.PointLight(0x4ade80, 0.35, 8);
cool.position.set(-0.3, 0.5, 0.5);
scene.add(cool);
const dir = new THREE.DirectionalLight(0xffffff, 0.35);
dir.position.set(-2, 4, 3);
scene.add(dir);
scene.fog = new THREE.Fog(0xfff5f7, 3.8, 11);
applyFilmicRenderer(renderer, 1.02);
buildIndieDeskScene(group);
group.rotation.y = 0.25;
} else {
scene.fog = new THREE.FogExp2(0x0a0a14, 0.055);
const ambient = new THREE.AmbientLight(0x4c1d95, 0.35);
scene.add(ambient);
const key = new THREE.PointLight(0x22d3ee, 1.2, 16);
key.position.set(1.5, 2, 2);
const hemi = new THREE.HemisphereLight(0xffffff, 0xfce7f3, 0.95);
scene.add(hemi);
const key = new THREE.DirectionalLight(0xffffff, 0.48);
key.position.set(2, 5, 3);
scene.add(key);
const rim = new THREE.PointLight(0xd946ef, 0.85, 14);
rim.position.set(-2, 0.5, -1);
scene.add(rim);
const dir = new THREE.DirectionalLight(0xa78bfa, 0.4);
dir.position.set(0, 3, 4);
scene.add(dir);
const fill = new THREE.DirectionalLight(0xffe4e8, 0.28);
fill.position.set(-3, 2, -1);
scene.add(fill);
const scr = new THREE.PointLight(0xbfdbfe, 0.35, 5);
scr.position.set(-0.25, 0, 0);
scene.add(scr);
bobRefs = buildAiCyberScene(group);
anim = buildIndieEnclosedRoom(group);
group.rotation.set(0.1, 0.28, 0);
} else {
scene.fog = new THREE.FogExp2(0x0c0618, 0.048);
applyFilmicRenderer(renderer, 1.05);
const hemi = new THREE.HemisphereLight(0xc4b5fd, 0x1e1038, 0.75);
scene.add(hemi);
const key = new THREE.DirectionalLight(0xf5f3ff, 0.5);
key.position.set(2, 4.5, 3);
scene.add(key);
const rim = new THREE.DirectionalLight(0x7c3aed, 0.22);
rim.position.set(-2.5, 1, -1.5);
scene.add(rim);
const glow = new THREE.PointLight(0xa78bfa, 0.4, 9);
glow.position.set(0.8, 1.2, 1);
scene.add(glow);
anim = buildAiCuteCyber(group);
group.rotation.set(0.06, -0.15, 0);
}
let rx = 0.1;
let ry = variant === "indie" ? 0.2 : 0;
let rx = group.rotation.x;
let ry = group.rotation.y;
let vx = 0;
let vy = 0;
const sens = 0.0052;
const damping = 0.93;
const autoRy = variant === "indie" ? 0.0018 : 0.0024;
const sens = 0.0046;
const damping = 0.936;
const autoRy = variant === "indie" ? 0.001 : 0.0013;
let dragging = false;
let lastX = 0;
@@ -333,7 +506,7 @@ export function TopicPrepThreeScene({ variant }: Props) {
const resize = () => {
const w = Math.max(280, container.clientWidth);
const h = Math.min(440, Math.max(236, Math.round(w * 0.5)));
const h = Math.min(440, Math.max(240, Math.round(w * 0.52)));
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
@@ -357,12 +530,27 @@ export function TopicPrepThreeScene({ variant }: Props) {
group.rotation.x = rx;
group.rotation.y = ry;
if (bobRefs?.robot) {
bobRefs.robot.position.y = -0.15 + Math.sin(t * 1.8) * 0.035;
if (anim?.dog) {
anim.dog.position.y = -0.86 + Math.sin(t * 2) * 0.016;
anim.dog.rotation.y = Math.sin(t * 0.85) * 0.05;
}
if (bobRefs?.holo) {
bobRefs.holo.rotation.y = t * 0.9;
bobRefs.holo.rotation.x = t * 0.45;
if (anim?.tail) {
anim.tail.rotation.z = Math.PI / 2.8 + Math.sin(t * 4.5) * 0.22;
}
if (anim?.bot) {
anim.bot.position.y = -0.25 + Math.sin(t * 1.5) * 0.03;
anim.bot.rotation.y = Math.sin(t * 0.6) * 0.04;
}
if (anim?.halo) {
anim.halo.rotation.z = t * 0.15;
}
if (anim?.orbs) {
anim.orbs.forEach((o, i) => {
const a = t * 0.5 + (i / 3) * Math.PI * 2;
o.position.y = 0.25 + i * 0.08 + Math.sin(t * 1.8 + i) * 0.06;
o.position.x = Math.cos(a) * 1.15;
o.position.z = Math.sin(a) * 1.15;
});
}
renderer.render(scene, camera);
@@ -383,20 +571,13 @@ export function TopicPrepThreeScene({ variant }: Props) {
};
}, [variant]);
const label =
variant === "indie" ? "独立开发:居家工作台与 homelab 预览" : "AI Agent赛博空间与机器人预览";
const hint =
variant === "indie"
? "拖动旋转 · 居家书桌、显示器与 homelab 机架(课程筹备视觉预告)"
: "拖动旋转 · 赛博网格空间与机器人(课程筹备视觉预告)";
const label = variant === "indie" ? "独立开发互动预览" : "AI Agent 互动预览";
return (
<div ref={containerRef} className="mx-auto mt-2 w-full max-w-2xl">
<p className="mb-2 text-center text-xs text-[var(--muted-foreground)]">{hint}</p>
<canvas
ref={canvasRef}
className="mx-auto block w-full cursor-grab touch-none rounded-2xl border border-[var(--border)] bg-[var(--muted)]/25 active:cursor-grabbing dark:bg-[var(--muted)]/15"
className="mx-auto block w-full cursor-grab touch-none rounded-2xl shadow-md outline-none active:cursor-grabbing dark:shadow-lg dark:shadow-violet-950/20"
aria-label={label}
role="img"
/>

View File

@@ -51,10 +51,10 @@ export const TOPIC_CONFIGS: Record<string, TopicConfig> = {
icon: "🛠️",
title: "独立开发",
subtitle: "从想法到产品,独立开发者全流程指南",
desc: "即将上线,敬请期待。",
desc: "",
stats: [
{ value: "—", label: "课程筹备中" },
{ value: "—", label: "敬请期待" },
{ value: "—", label: "内容策划中" },
],
moduleIndices: [],
payUrl: "/",
@@ -64,10 +64,10 @@ export const TOPIC_CONFIGS: Record<string, TopicConfig> = {
icon: "🤖",
title: "AI Agent",
subtitle: "AIGC 与智能体Vibe Coding 跨界创造",
desc: "即将上线,敬请期待。",
desc: "",
stats: [
{ value: "—", label: "课程筹备中" },
{ value: "—", label: "敬请期待" },
{ value: "—", label: "内容策划中" },
],
moduleIndices: [],
payUrl: "/",