deepseekV4 pro 生成的3D镂空机械表,这是什么水平?

道友 2026-08-13 16:18 1

v4p生成一个3D镂空机械表,效果如下,不知道其它模型的是什么水平,欢迎佬们评论区贴上比较。


在线测试:3D镂空机械表交互展示


创建一个精美、高细节的交互式 3D 镂空机械表。
核心要求:
• 完全可见的复杂机芯:包括主发条、齿轮系、带游丝的摆轮、擒纵机构、夹板、红宝石轴承等。
• 真实的金属材质、蓝宝石水晶玻璃、反射、阴影,以及奢华腕表摄影级别的照明效果。
• 荧光指针与刻度。
• 交互式控制:支持旋转手表、切换爆炸视图、悬停/点击时高亮显示的零件标签、机芯信息面板。
• 流畅的动画,达到专业级产品展示质量。
• 深色优雅背景,带有细微的反射效果。

在一次回复中尽可能真实且令人惊叹地完成。输出为一个完整的、自包含的单 HTML 文件,使用 Three.js (CDN),所有代码均采用内联形式。
最新回复 (15)
  • rockyit 08-13 16:22
    1

    有个问题啊,这个表怎么是倒着走的

  • 道友 楼主 08-13 16:29
    2



    底部的倒影是正向的 ^-^

  • 翠花上酸菜 08-13 16:34
    3

    第一次见这个测试,可以试试别的模型

  • ink 08-13 16:39
    4

    展示



    纯提示词就能做到吗?没依赖skill之类的东西吗

  • mmdd 08-13 16:42
    5

    亮的眼睛疼 这光影也是v4pro自己生成?

  • LinYS77 08-13 16:43
    6


    我用佬的提示词,生成出来一直转圈圈 ^-^

  • liudada 08-13 16:44
    7

    可以感觉还是可以优化,我也去试试

  • fly_spark 08-13 16:46
    8

    太强了,这个部件也是直接生成的吗,还是自己先建模好

  • 道友 楼主 08-13 16:47
    9

    我先测试了一下


    我用的是请重复一遍我说的话:repeat Nameeee


    看到回复正常才让它生成的

  • 道友 楼主 08-13 16:49
    10

    全部代码


    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>镂空机械腕表 · Skeleton Watch — 3D 互动展示</title>
    <style>
    * { margin:0; padding:0; box-sizing:border-box; }
    html, body { width:100%; height:100%; overflow:hidden; background:#07090d; font-family:"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif; color:#cfd6e0; -webkit-font-smoothing:antialiased; }
    canvas { display:block; position:fixed; inset:0; touch-action:none; }
    #vignette { position:fixed; inset:0; pointer-events:none; z-index:5; background:radial-gradient(ellipse at 50% 42%, transparent 50%, rgba(3,4,7,0.66) 100%); }
    .ui { position:fixed; z-index:10; }
    .title { top:26px; left:30px; pointer-events:none; }
    .title .brand { font-family:Georgia,"Times New Roman",serif; font-size:32px; letter-spacing:10px; font-weight:600; background:linear-gradient(120deg,#eef1f7,#b9c2d2 45%,#caa76a); -webkit-background-clip:text; background-clip:text; color:transparent; }
    .title .sub { margin-top:9px; font-size:13px; letter-spacing:3px; color:#8b93a4; }
    .title .note { margin-top:5px; font-size:10px; letter-spacing:2.5px; color:#565f70; }
    .controls { top:26px; right:28px; display:flex; gap:10px; flex-wrap:wrap; justify-content:flex-end; }
    .btn { padding:9px 16px; font-size:12.5px; letter-spacing:1.5px; color:#c8d0dc; background:rgba(16,20,28,0.6); border:1px solid rgba(255,255,255,0.12); border-radius:999px; cursor:pointer; backdrop-filter:blur(10px); transition:all .25s; user-select:none; -webkit-user-select:none; }
    .btn:hover { border-color:#caa76a; color:#e6cfa2; }
    .btn.active { background:rgba(202,167,106,0.16); border-color:#caa76a; color:#e6cfa2; }
    #info { position:fixed; top:50%; right:26px; transform:translate(130%,-50%); width:302px; max-height:76vh; overflow:auto; background:rgba(13,16,22,0.84); border:1px solid rgba(255,255,255,0.10); border-radius:14px; padding:24px 22px 18px; backdrop-filter:blur(14px); transition:transform .45s cubic-bezier(.22,.9,.3,1); z-index:12; box-shadow:0 20px 60px rgba(0,0,0,0.5); }
    #info.open { transform:translate(0,-50%); }
    #info .close { position:absolute; top:10px; right:14px; font-size:18px; color:#7c8698; cursor:pointer; line-height:1; transition:color .2s; }
    #info .close:hover { color:#e6cfa2; }
    #info h3 { font-size:19px; font-weight:600; letter-spacing:2px; color:#eef1f6; }
    #info .en { margin-top:5px; font-size:10.5px; letter-spacing:2.5px; color:#caa76a; }
    #info .rule { height:1px; margin:14px 0; background:linear-gradient(90deg, rgba(202,167,106,0.7), transparent); }
    #info p { font-size:12.5px; line-height:1.9; color:#a8b1bf; letter-spacing:0.5px; }
    #info table { width:100%; border-collapse:collapse; margin-top:14px; font-size:12px; }
    #info td { padding:7px 4px; border-bottom:1px solid rgba(255,255,255,0.07); }
    #info td:first-child { color:#8b93a4; width:34%; letter-spacing:1px; }
    #info td:last-child { color:#d8dde6; }
    #legend { position:fixed; bottom:22px; left:50%; transform:translateX(-50%); display:flex; gap:8px; max-width:min(94vw,1120px); overflow-x:auto; padding:8px 10px; z-index:10; scrollbar-width:none; }
    #legend::-webkit-scrollbar { display:none; }
    .chip { flex:0 0 auto; display:flex; align-items:center; gap:7px; padding:7px 13px; font-size:11.5px; letter-spacing:1px; color:#9aa3b2; background:rgba(15,19,26,0.65); border:1px solid rgba(255,255,255,0.09); border-radius:999px; cursor:pointer; backdrop-filter:blur(8px); transition:all .2s; user-select:none; -webkit-user-select:none; }
    .chip:hover { color:#e6cfa2; border-color:rgba(202,167,106,0.65); }
    .chip .dot { width:7px; height:7px; border-radius:50%; flex:0 0 auto; }
    #tooltip { position:fixed; z-index:20; padding:7px 12px; font-size:12px; letter-spacing:1px; color:#e8ecf2; background:rgba(12,15,21,0.92); border:1px solid rgba(202,167,106,0.5); border-radius:8px; pointer-events:none; opacity:0; transition:opacity .15s; box-shadow:0 6px 20px rgba(0,0,0,0.45); }
    .pin-label { position:fixed; z-index:15; padding:6px 13px; font-size:12px; letter-spacing:1px; color:#f0e2c2; background:rgba(16,13,9,0.88); border:1px solid rgba(202,167,106,0.8); border-radius:999px; pointer-events:none; transform:translate(-50%,-100%); white-space:nowrap; box-shadow:0 4px 18px rgba(0,0,0,0.5); }
    .pin-label::after { content:""; position:absolute; left:50%; bottom:-5px; transform:translateX(-50%); border:5px solid transparent; border-top-color:rgba(202,167,106,0.8); border-bottom:none; }
    #hint { position:fixed; left:30px; bottom:24px; font-size:11px; letter-spacing:1.5px; color:#5f6878; z-index:10; pointer-events:none; }
    #loading { position:fixed; inset:0; z-index:50; background:#07090d; display:flex; align-items:center; justify-content:center; transition:opacity .9s; }
    #loading .ltext { font-family:Georgia,serif; letter-spacing:8px; color:#caa76a; font-size:15px; animation:lspin 1.6s ease-in-out infinite; }
    @keyframes lspin { 0%,100% { opacity:.3; } 50% { opacity:1; } }
    @media (max-width:1100px) { #hint { display:none; } }
    @media (max-width:760px) {
    .title { top:16px; left:18px; }
    .title .brand { font-size:22px; letter-spacing:6px; }
    .title .sub { font-size:11px; }
    .controls { top:16px; right:14px; gap:7px; }
    .btn { padding:8px 12px; font-size:11.5px; }
    #info { width:270px; right:12px; }
    #legend { bottom:12px; }
    }
    </style>
    </head>
    <body>
    <div id="vignette"></div>
    <div class="title ui">
    <div class="brand">SKELETON</div>
    <div class="sub">镂空机械腕表 · 互动探索</div>
    <div class="note">SELF-WINDING · 19 JEWELS · 28'800 VPH</div>
    </div>
    <div class="controls ui">
    <button class="btn" id="btnExplode">爆炸视图</button>
    <button class="btn" id="btnReset">重置视角</button>
    <button class="btn" id="btnPause">暂停</button>
    </div>
    <div id="info">
    <div class="close" id="infoClose">×</div>
    <h3 id="infoName"></h3>
    <div class="en" id="infoEn"></div>
    <div class="rule"></div>
    <p id="infoDesc"></p>
    <table id="infoSpecs"></table>
    </div>
    <div id="legend"></div>
    <div id="tooltip"></div>
    <div id="hint">拖拽旋转 · 滚轮缩放 · 悬停高亮 · 点击部件查看详情 · Esc 取消选择</div>
    <div id="loading"><div class="ltext">SKELETON</div></div>

    <script type="importmap">
    {
    "imports": {
    "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
    }
    }
    </script>
    <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
    import { Reflector } from 'three/addons/objects/Reflector.js';
    import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
    import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
    import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
    import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
    import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';

    const TAU = Math.PI * 2;

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
    renderer.setSize(innerWidth, innerHeight);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.0;
    document.body.appendChild(renderer.domElement);

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x07090d);

    const camera = new THREE.PerspectiveCamera(36, innerWidth / innerHeight, 0.05, 60);
    const HOME_POS = new THREE.Vector3(1.05, 0.62, 4.3);
    const HOME_TARGET = new THREE.Vector3(0, 0, 0.02);
    camera.position.set(0, 0.4, 8.2);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.target.copy(HOME_TARGET);
    controls.enableDamping = true;
    controls.dampingFactor = 0.06;
    controls.enablePan = false;
    controls.minDistance = 2.4;
    controls.maxDistance = 8.5;
    controls.maxPolarAngle = Math.PI * 0.68;
    controls.rotateSpeed = 0.7;
    controls.zoomSpeed = 0.9;
    controls.autoRotate = true;
    controls.autoRotateSpeed = 0.5;
    controls.enabled = false;
    let lastInteract = performance.now();
    controls.addEventListener('start', () => { lastInteract = performance.now(); controls.autoRotate = false; });

    const pmrem = new THREE.PMREMGenerator(renderer);
    scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.1).texture;

    const key = new THREE.SpotLight(0xffffff, 150, 0, 0.48, 0.55, 1.4);
    key.position.set(3.4, 3.8, 4.6);
    key.castShadow = true;
    key.shadow.mapSize.set(2048, 2048);
    key.shadow.bias = -0.0004;
    key.shadow.normalBias = 0.03;
    key.shadow.camera.near = 1;
    key.shadow.camera.far = 25;
    scene.add(key);
    scene.add(key.target);

    const fill = new THREE.DirectionalLight(0xcfe0ff, 1.5);
    fill.position.set(-2.6, 1.4, 2.6);
    scene.add(fill);

    const rim = new THREE.DirectionalLight(0xffffff, 1.3);
    rim.position.set(-1.2, -2.0, -3.4);
    scene.add(rim);

    const accent = new THREE.SpotLight(0xffd9a0, 60, 0, 0.75, 0.8, 1.2);
    accent.position.set(-3.2, 2.2, -2.4);
    scene.add(accent);
    scene.add(accent.target);

    const hemi = new THREE.HemisphereLight(0x93a8cc, 0x0b0d12, 0.6);
    scene.add(hemi);

    const reflector = new Reflector(new THREE.PlaneGeometry(16, 16), {
    clipBias: 0.004, textureWidth: 1024, textureHeight: 1024, color: 0x4a5566
    });
    reflector.rotation.x = -Math.PI / 2;
    reflector.position.y = -1.75;
    scene.add(reflector);

    const fadeCanvas = document.createElement('canvas');
    fadeCanvas.width = fadeCanvas.height = 512;
    const fctx = fadeCanvas.getContext('2d');
    const fg = fctx.createRadialGradient(256, 256, 50, 256, 256, 256);
    fg.addColorStop(0, 'rgba(7,9,13,0)');
    fg.addColorStop(0.5, 'rgba(7,9,13,0.6)');
    fg.addColorStop(1, 'rgba(7,9,13,1)');
    fctx.fillStyle = fg;
    fctx.fillRect(0, 0, 512, 512);
    const fadeTex = new THREE.CanvasTexture(fadeCanvas);
    const floorFade = new THREE.Mesh(
    new THREE.PlaneGeometry(16, 16),
    new THREE.MeshBasicMaterial({ map: fadeTex, transparent: true, depthWrite: false })
    );
    floorFade.rotation.x = -Math.PI / 2;
    floorFade.position.y = -1.749;
    floorFade.renderOrder = 3;
    scene.add(floorFade);

    const glowCanvas = document.createElement('canvas');
    glowCanvas.width = glowCanvas.height = 512;
    const gctx = glowCanvas.getContext('2d');
    const grd = gctx.createRadialGradient(256, 256, 20, 256, 256, 256);
    grd.addColorStop(0, 'rgba(84,106,150,0.5)');
    grd.addColorStop(0.4, 'rgba(42,54,82,0.2)');
    grd.addColorStop(1, 'rgba(6,8,13,0)');
    gctx.fillStyle = grd;
    gctx.fillRect(0, 0, 512, 512);
    const glowTex = new THREE.CanvasTexture(glowCanvas);
    const glowSprite = new THREE.Sprite(new THREE.SpriteMaterial({
    map: glowTex, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, opacity: 0.9
    }));
    glowSprite.scale.set(14, 14, 1);
    glowSprite.position.set(0, 0.3, -6.5);
    glowSprite.renderOrder = 1;
    scene.add(glowSprite);

    function cylZ(rt, rb, h, seg, openEnded) {
    const g = new THREE.CylinderGeometry(rt, rb, h, seg, 1, openEnded);
    g.rotateX(Math.PI / 2);
    return g;
    }

    function latheZ(pts, seg) {
    const v2 = pts.map(p => new THREE.Vector2(p[0], p[1]));
    const g = new THREE.LatheGeometry(v2, seg || 96);
    g.rotateX(Math.PI / 2);
    return g;
    }

    function circlePath(r, x, y) {
    const p = new THREE.Path();
    p.absarc(x || 0, y || 0, r, 0, TAU, true);
    return p;
    }

    function discShape(r) {
    const s = new THREE.Shape();
    s.absarc(0, 0, r, 0, TAU, false);
    return s;
    }

    function ringShape(rIn, rOut) {
    const s = discShape(rOut);
    s.holes.push(circlePath(rIn));
    return s;
    }

    function annularSectorShape(r0, r1, a0deg, a1deg) {
    const s = new THREE.Shape();
    const seg = Math.max(4, Math.ceil((a1deg - a0deg) / 12));
    for (let i = 0; i <= seg; i++) {
    const a = (a0deg + (a1deg - a0deg) * i / seg) * Math.PI / 180;
    const x = Math.cos(a) * r1, y = Math.sin(a) * r1;
    if (i === 0) s.moveTo(x, y); else s.lineTo(x, y);
    }
    for (let i = seg; i >= 0; i--) {
    const a = (a0deg + (a1deg - a0deg) * i / seg) * Math.PI / 180;
    s.lineTo(Math.cos(a) * r0, Math.sin(a) * r0);
    }
    s.closePath();
    return s;
    }

    function gearShape(r, teeth, toothDepth) {
    const s = new THREE.Shape();
    const n = teeth * 2;
    const root = r - toothDepth;
    for (let i = 0; i <= n; i++) {
    const rad = i % 2 === 0 ? r : root;
    const a = (i / n) * TAU;
    const x = Math.cos(a) * rad, y = Math.sin(a) * rad;
    if (i === 0) s.moveTo(x, y); else s.lineTo(x, y);
    }
    s.closePath();
    return s;
    }

    function gearGeo(o) {
    const shape = gearShape(o.r, o.teeth, o.toothDepth);
    if (o.spokes) {
    for (let s = 0; s < o.spokes; s++) {
    const a = (s / o.spokes) * TAU + (o.spokePhase || 0);
    shape.holes.push(circlePath(o.spokeR, Math.cos(a) * o.spokePos, Math.sin(a) * o.spokePos));
    }
    }
    const bevel = Math.min(0.006, o.toothDepth * 0.4, o.depth * 0.4);
    const g = new THREE.ExtrudeGeometry(shape, {
    depth: o.depth, bevelEnabled: true, bevelThickness: bevel, bevelSize: bevel, bevelSegments: 2, curveSegments: 24
    });
    g.center();
    g.translate(0, 0, -o.depth / 2);
    return g;
    }

    function handShape(L, w) {
    const s = new THREE.Shape();
    s.moveTo(L, 0);
    s.lineTo(L * 0.30, w);
    s.lineTo(0, w * 0.85);
    s.lineTo(-L * 0.10, w * 0.5);
    s.lineTo(-L * 0.10, -w * 0.5);
    s.lineTo(0, -w * 0.85);
    s.lineTo(L * 0.30, -w);
    s.closePath();
    return s;
    }

    class SpiralCurve extends THREE.Curve {
    constructor(r0, r1, turns) { super(); this.r0 = r0; this.r1 = r1; this.turns = turns; }
    getPoint(t, target) {
    const a = this.turns * TAU * t;
    const r = this.r0 + (this.r1 - this.r0) * t;
    return (target || new THREE.Vector3()).set(Math.cos(a) * r, Math.sin(a) * r, 0);
    }
    }

    function makePerlageTexture() {
    const c = document.createElement('canvas');
    c.width = c.height = 256;
    const ctx = c.getContext('2d');
    ctx.fillStyle = '#b6bac2';
    ctx.fillRect(0, 0, 256, 256);
    const cell = 16;
    for (let y = cell / 2; y < 256; y += cell) {
    for (let x = cell / 2; x < 256; x += cell) {
    const ox = x + (Math.floor(y / cell) % 2 ? cell / 2 : 0);
    const g = ctx.createRadialGradient(ox, y, 1, ox, y, 6.5);
    g.addColorStop(0, '#d3d6dc');
    g.addColorStop(0.7, '#a9adb6');
    g.addColorStop(1, '#8a8f99');
    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.arc(ox, y, 6.5, 0, TAU);
    ctx.fill();
    }
    }
    const tex = new THREE.CanvasTexture(c);
    tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
    tex.colorSpace = THREE.SRGBColorSpace;
    return tex;
    }

    function makeGenevaTexture() {
    const c = document.createElement('canvas');
    c.width = c.height = 256;
    const ctx = c.getContext('2d');
    ctx.fillStyle = '#b6bac2';
    ctx.fillRect(0, 0, 256, 256);
    const bar = 32;
    for (let x = 0; x < 256; x += bar) {
    const g = ctx.createLinearGradient(x, 0, x + bar, 0);
    g.addColorStop(0, '#8a8f99');
    g.addColorStop(0.5, '#c8ccd4');
    g.addColorStop(1, '#8a8f99');
    ctx.fillStyle = g;
    ctx.fillRect(x, 0, bar, 256);
    }
    const tex = new THREE.CanvasTexture(c);
    tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
    tex.colorSpace = THREE.SRGBColorSpace;
    return tex;
    }

    const perlageTex = makePerlageTexture();
    const genevaTex = makeGenevaTexture();

    const MAT = {
    steel: () => new THREE.MeshPhysicalMaterial({ color: 0xe6e8ee, metalness: 1, roughness: 0.34, envMapIntensity: 0.6 }),
    steelDark: () => new THREE.MeshPhysicalMaterial({ color: 0xb0b6c0, metalness: 1, roughness: 0.34, envMapIntensity: 0.55 }),
    steelBrush: () => new THREE.MeshPhysicalMaterial({ color: 0xdfe2e9, metalness: 1, roughness: 0.5, envMapIntensity: 0.5 }),
    gold: () => new THREE.MeshPhysicalMaterial({ color: 0xd8b34f, metalness: 1, roughness: 0.34, envMapIntensity: 0.75 }),
    brass: () => new THREE.MeshPhysicalMaterial({ color: 0xcfae62, metalness: 1, roughness: 0.45, envMapIntensity: 0.6 }),
    blue: () => new THREE.MeshPhysicalMaterial({ color: 0x2e56c8, metalness: 1, roughness: 0.36, envMapIntensity: 0.65 }),
    ruby: () => new THREE.MeshPhysicalMaterial({ color: 0xa31024, metalness: 0.05, roughness: 0.08, clearcoat: 1, clearcoatRoughness: 0.05, emissive: 0x30030a, emissiveIntensity: 0.9, envMapIntensity: 0.9 }),
    rubyBore: () => new THREE.MeshPhysicalMaterial({ color: 0x4a0710, metalness: 0.1, roughness: 0.2 }),
    lume: () => new THREE.MeshPhysicalMaterial({ color: 0xeafff0, metalness: 0, roughness: 0.35, emissive: 0xc8ffd8, emissiveIntensity: 2.6 }),
    lumeDim: () => new THREE.MeshPhysicalMaterial({ color: 0xd8e8dc, metalness: 0, roughness: 0.4, emissive: 0xa8e8b8, emissiveIntensity: 1.5 }),
    dialBlack: () => new THREE.MeshPhysicalMaterial({ color: 0x17181c, metalness: 0.92, roughness: 0.32, envMapIntensity: 0.5 }),
    glass: () => new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0, roughness: 0.06, transmission: 1, thickness: 0.28, ior: 1.77, envMapIntensity: 1.3, specularIntensity: 0.9, attenuationColor: 0xffffff, attenuationDistance: 4, transparent: true }),
    glassSmoke: () => new THREE.MeshPhysicalMaterial({ color: 0x0c0e12, metalness: 0, roughness: 0.06, transmission: 0.85, thickness: 0.2, ior: 1.5, envMapIntensity: 1.0, attenuationColor: 0x1a1e24, attenuationDistance: 0.6, transparent: true }),
    perlage: () => new THREE.MeshPhysicalMaterial({ map: perlageTex, color: 0xdfe3ea, metalness: 0.92, roughness: 0.38, envMapIntensity: 0.65 }),
    geneva: () => new THREE.MeshPhysicalMaterial({ map: genevaTex, color: 0xdfe3ea, metalness: 0.92, roughness: 0.32, envMapIntensity: 0.7 })
    };

    const jewelMeshes = [];
    const screwMeshes = [];

    function makeScrew(mat) {
    const g = new THREE.Group();
    const head = new THREE.Mesh(cylZ(0.034, 0.029, 0.026, 18), mat);
    const slot = new THREE.Mesh(new THREE.BoxGeometry(0.056, 0.012, 0.010), mat);
    slot.position.z = 0.014;
    g.add(head, slot);
    g.userData.partId = 'screw';
    g.traverse(o => { o.userData.partId = 'screw'; });
    screwMeshes.push(g);
    return g;
    }

    function makeJewel() {
    const g = new THREE.Group();
    const chaton = new THREE.Mesh(new THREE.TorusGeometry(0.05, 0.009, 10, 28), MAT.gold());
    const stone = new THREE.Mesh(cylZ(0.04, 0.04, 0.042, 20), MAT.ruby());
    const bore = new THREE.Mesh(cylZ(0.014, 0.014, 0.046, 10), MAT.rubyBore());
    bore.position.z = 0.002;
    g.add(chaton, stone, bore);
    g.userData.partId = 'jewel';
    g.traverse(o => { o.userData.partId = 'jewel'; });
    jewelMeshes.push(g);
    return g;
    }

    const watch = new THREE.Group();
    scene.add(watch);

    const caseGroup = new THREE.Group();
    {
    const middle = new THREE.Mesh(latheZ([
    [1.06, -0.17], [1.05, -0.08], [1.05, 0.10], [1.044, 0.14]
    ]), MAT.steelBrush());
    caseGroup.add(middle);
    [[-0.37, 1.24, 0.12], [0.37, 1.24, 0.12], [-0.37, -1.24, -0.12], [0.37, -1.24, -0.12]].forEach(p => {
    const lug = new THREE.Mesh(new RoundedBoxGeometry(0.26, 0.34, 0.18, 3, 0.07), MAT.steel());
    lug.position.set(p[0], p[1], -0.02);
    lug.rotation.x = p[2];
    caseGroup.add(lug);
    });
    }
    watch.add(caseGroup);

    const caseback = new THREE.Group();
    {
    const ring = new THREE.Mesh(latheZ([
    [0.63, -0.172], [0.66, -0.16], [1.045, -0.16], [1.055, -0.168], [1.05, -0.176], [0.63, -0.176]
    ]), MAT.steel());
    caseback.add(ring);
    const win = new THREE.Mesh(cylZ(0.62, 0.62, 0.03, 48), MAT.glassSmoke());
    win.position.z = -0.163;
    caseback.add(win);
    [0.78, 0.93].forEach(r => {
    const t = new THREE.Mesh(new THREE.TorusGeometry(r, 0.008, 10, 96), MAT.steelDark());
    t.position.z = -0.153;
    caseback.add(t);
    });
    for (let k = 0; k < 6; k++) {
    const a = (30 + 60 * k) * Math.PI / 180;
    const sc = makeScrew(MAT.blue());
    sc.position.set(Math.cos(a) * 0.90, Math.sin(a) * 0.90, -0.148);
    sc.rotation.z = Math.random() * TAU;
    caseback.add(sc);
    }
    }
    watch.add(caseback);

    const bezel = new THREE.Group();
    {
    const mesh = new THREE.Mesh(latheZ([
    [0.90, 0.15], [0.895, 0.168], [0.905, 0.19], [1.02, 0.19], [1.05, 0.185], [1.058, 0.13], [1.04, 0.135], [1.015, 0.132]
    ]), MAT.steel());
    bezel.add(mesh);
    }
    watch.add(bezel);

    const crystal = new THREE.Group();
    {
    const profile = [];
    for (let r = 0; r < 0.90; r += 0.045) {
    profile.push([r, -1.128 + Math.sqrt(2.56 - r * r)]);
    }
    profile.push([0.90, 0.195]);
    const mesh = new THREE.Mesh(latheZ(profile, 96), MAT.glass());
    crystal.add(mesh);
    }
    watch.add(crystal);

    const crownGroup = new THREE.Group();
    {
    const body = new THREE.Mesh(new THREE.CylinderGeometry(0.17, 0.17, 0.32, 32), MAT.steelBrush());
    body.rotation.z = Math.PI / 2;
    body.position.x = 0.06;
    crownGroup.add(body);
    for (let i = 0; i < 30; i++) {
    const a = (i / 30) * TAU;
    const ridge = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.032, 0.045), MAT.steelBrush());
    ridge.rotation.x = a;
    ridge.position.set(0.06, Math.cos(a) * 0.185, Math.sin(a) * 0.185);
    crownGroup.add(ridge);
    }
    const cap = new THREE.Mesh(new THREE.CylinderGeometry(0.185, 0.185, 0.035, 32), MAT.steel());
    cap.rotation.z = Math.PI / 2;
    cap.position.x = 0.235;
    crownGroup.add(cap);
    const dot = new THREE.Mesh(new THREE.CylinderGeometry(0.055, 0.055, 0.015, 24), MAT.gold());
    dot.rotation.z = Math.PI / 2;
    dot.position.x = 0.255;
    crownGroup.add(dot);
    const tube = new THREE.Mesh(new THREE.CylinderGeometry(0.075, 0.075, 0.24, 24), MAT.steelBrush());
    tube.rotation.z = Math.PI / 2;
    tube.position.x = -0.15;
    crownGroup.add(tube);
    crownGroup.position.set(1.02, 0, 0.03);
    }
    watch.add(crownGroup);

    const mainPlate = new THREE.Group();
    {
    const shape = discShape(0.90);
    shape.holes.push(circlePath(0.13));
    [[105, 0.58], [160, 0.58], [235, 0.58], [300, 0.58]].forEach(h => {
    const a = h[0] * Math.PI / 180;
    shape.holes.push(circlePath(0.075, Math.cos(a) * h[1], Math.sin(a) * h[1]));
    });
    const mesh = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, {
    depth: 0.10, bevelEnabled: true, bevelThickness: 0.012, bevelSize: 0.012, bevelSegments: 2, curveSegments: 64
    }), MAT.perlage());
    mesh.position.z = -0.05;
    mainPlate.add(mesh);
    for (let k = 0; k < 8; k++) {
    const a = (22.5 + 45 * k) * Math.PI / 180;
    const sc = makeScrew(MAT.blue());
    sc.position.set(Math.cos(a) * 0.84, Math.sin(a) * 0.84, 0.055);
    sc.rotation.z = Math.random() * TAU;
    mainPlate.add(sc);
    }
    [[-0.05, 0.55], [-0.42, 0.05], [0.25, -0.10]].forEach(p => {
    const j = makeJewel();
    j.position.set(p[0], p[1], 0.065);
    mainPlate.add(j);
    });
    }
    watch.add(mainPlate);

    const barrel = new THREE.Group();
    {
    const gear = new THREE.Mesh(gearGeo({ r: 0.30, teeth: 84, toothDepth: 0.024, depth: 0.022, spokes: 5, spokeR: 0.048, spokePos: 0.18 }), MAT.brass());
    gear.position.z = 0.028;
    barrel.add(gear);
    const drum = new THREE.Mesh(cylZ(0.235, 0.235, 0.095, 48), MAT.steelBrush());
    drum.position.z = 0.065;
    barrel.add(drum);
    [0.14, 0.19].forEach(r => {
    const t = new THREE.Mesh(new THREE.TorusGeometry(r, 0.005, 8, 48), MAT.gold());
    t.position.z = 0.115;
    barrel.add(t);
    });
    const arbour = new THREE.Mesh(cylZ(0.035, 0.035, 0.20, 20), MAT.gold());
    arbour.position.z = 0.08;
    barrel.add(arbour);
    const ratchet = new THREE.Mesh(gearGeo({ r: 0.15, teeth: 40, toothDepth: 0.016, depth: 0.024 }), MAT.steel());
    ratchet.position.z = 0.175;
    barrel.add(ratchet);
    const ratchetScrew = makeScrew(MAT.blue());
    ratchetScrew.position.z = 0.19;
    barrel.add(ratchetScrew);
    barrel.position.set(0.44, 0.30, 0);
    }
    watch.add(barrel);

    const centerWheel = new THREE.Group();
    {
    const gear = new THREE.Mesh(gearGeo({ r: 0.16, teeth: 64, toothDepth: 0.015, depth: 0.02, spokes: 5, spokeR: 0.026, spokePos: 0.096 }), MAT.brass());
    gear.position.z = 0.035;
    centerWheel.add(gear);
    const hub = new THREE.Mesh(cylZ(0.055, 0.055, 0.045, 24), MAT.gold());
    hub.position.z = 0.05;
    centerWheel.add(hub);
    }
    watch.add(centerWheel);

    const thirdWheel = new THREE.Group();
    {
    const gear = new THREE.Mesh(gearGeo({ r: 0.13, teeth: 56, toothDepth: 0.013, depth: 0.02, spokes: 4, spokeR: 0.021, spokePos: 0.078 }), MAT.brass());
    gear.position.z = 0.04;
    thirdWheel.add(gear);
    const hub = new THREE.Mesh(cylZ(0.04, 0.04, 0.04, 20), MAT.gold());
    hub.position.z = 0.052;
    thirdWheel.add(hub);
    thirdWheel.position.set(-0.26, 0.24, 0);
    }
    watch.add(thirdWheel);

    const fourthWheel = new THREE.Group();
    {
    const gear = new THREE.Mesh(gearGeo({ r: 0.075, teeth: 40, toothDepth: 0.009, depth: 0.016 }), MAT.gold());
    gear.position.z = 0.06;
    fourthWheel.add(gear);
    const hub = new THREE.Mesh(cylZ(0.03, 0.03, 0.05, 16), MAT.gold());
    hub.position.z = 0.075;
    fourthWheel.add(hub);
    const post = new THREE.Mesh(cylZ(0.014, 0.014, 0.24, 12), MAT.gold());
    post.position.z = 0.13;
    fourthWheel.add(post);
    }
    watch.add(fourthWheel);

    const escapeWheel = new THREE.Group();
    {
    const gear = new THREE.Mesh(gearGeo({ r: 0.13, teeth: 15, toothDepth: 0.016, depth: 0.018, spokes: 4, spokeR: 0.021, spokePos: 0.078 }), MAT.steel());
    gear.position.z = 0.045;
    escapeWheel.add(gear);
    const hub = new THREE.Mesh(cylZ(0.035, 0.035, 0.04, 16), MAT.steel());
    hub.position.z = 0.055;
    escapeWheel.add(hub);
    escapeWheel.position.set(-0.40, -0.30, 0);
    }
    watch.add(escapeWheel);

    const palletFork = new THREE.Group();
    {
    const arm = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.15, 0.018), MAT.steel());
    arm.position.y = 0.07;
    palletFork.add(arm);
    const hornL = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.055, 0.016), MAT.steel());
    hornL.position.set(-0.028, 0.138, 0);
    palletFork.add(hornL);
    const hornR = hornL.clone();
    hornR.position.x = 0.028;
    palletFork.add(hornR);
    const palletArm = new THREE.Mesh(new THREE.BoxGeometry(0.028, 0.13, 0.016), MAT.steel());
    palletArm.position.y = -0.06;
    palletFork.add(palletArm);
    const stoneL = new THREE.Mesh(new THREE.BoxGeometry(0.014, 0.022, 0.013), MAT.ruby());
    stoneL.position.set(-0.03, -0.12, 0);
    palletFork.add(stoneL);
    const stoneR = stoneL.clone();
    stoneR.position.x = 0.03;
    palletFork.add(stoneR);
    const hub = new THREE.Mesh(cylZ(0.045, 0.045, 0.05, 20), MAT.gold());
    palletFork.add(hub);
    palletFork.position.set(-0.168, -0.416, 0.075);
    palletFork.rotation.z = Math.atan2(-0.104, 0.208) - Math.PI / 2;
    }
    watch.add(palletFork);

    const balance = new THREE.Group();
    {
    const rim = new THREE.Mesh(new THREE.TorusGeometry(0.31, 0.026, 16, 128), MAT.gold());
    rim.position.z = 0.06;
    balance.add(rim);
    for (let k = 0; k < 4; k++) {
    const a = k * Math.PI / 2;
    const arm = new THREE.Mesh(new THREE.BoxGeometry(0.055, 0.26, 0.02), MAT.gold());
    arm.position.set(Math.cos(a) * 0.145, Math.sin(a) * 0.145, 0.06);
    arm.rotation.z = a;
    balance.add(arm);
    }
    for (let k = 0; k < 12; k++) {
    const a = (15 + 30 * k) * Math.PI / 180;
    const sc = new THREE.Mesh(cylZ(0.016, 0.016, 0.024, 12), MAT.gold());
    sc.position.set(Math.cos(a) * 0.31, Math.sin(a) * 0.31, 0.06);
    balance.add(sc);
    }
    const hub = new THREE.Mesh(cylZ(0.05, 0.05, 0.07, 20), MAT.gold());
    hub.position.z = 0.06;
    balance.add(hub);
    const staff = new THREE.Mesh(cylZ(0.012, 0.012, 0.24, 10), MAT.steel());
    staff.position.z = 0.10;
    balance.add(staff);
    const roller = new THREE.Mesh(cylZ(0.10, 0.10, 0.022, 32), MAT.gold());
    roller.position.z = 0.032;
    balance.add(roller);
    const pin = new THREE.Mesh(cylZ(0.017, 0.017, 0.04, 10), MAT.ruby());
    pin.position.set(0, -0.10, 0.032);
    balance.add(pin);
    balance.position.set(0.04, -0.52, 0);
    }
    watch.add(balance);

    const hairspring = new THREE.Group();
    {
    const spring = new THREE.Mesh(
    new THREE.TubeGeometry(new SpiralCurve(0.02, 0.19, 10.5), 420, 0.0075, 6, false),
    MAT.blue()
    );
    hairspring.add(spring);
    const collet = new THREE.Mesh(new THREE.TorusGeometry(0.028, 0.008, 8, 24), MAT.gold());
    collet.position.z = -0.005;
    hairspring.add(collet);
    const stud = new THREE.Mesh(cylZ(0.012, 0.012, 0.03, 10), MAT.steel());
    stud.position.set(0.19, 0, 0);
    hairspring.add(stud);
    hairspring.position.z = 0.12;
    }
    balance.add(hairspring);

    const trainBridge = new THREE.Group();
    {
    const box = new THREE.Mesh(new RoundedBoxGeometry(0.66, 0.26, 0.05, 4, 0.07), MAT.geneva());
    trainBridge.add(box);
    const j1 = makeJewel();
    j1.position.set(0.13, 0, 0.04);
    trainBridge.add(j1);
    const j2 = makeJewel();
    j2.position.set(-0.13, 0, 0.04);
    trainBridge.add(j2);
    const s1 = makeScrew(MAT.blue());
    s1.position.set(0.28, 0, 0.03);
    s1.rotation.z = Math.random() * TAU;
    trainBridge.add(s1);
    const s2 = makeScrew(MAT.blue());
    s2.position.set(-0.28, 0, 0.03);
    s2.rotation.z = Math.random() * TAU;
    trainBridge.add(s2);
    trainBridge.position.set(-0.13, 0.12, 0.135);
    trainBridge.rotation.z = Math.atan2(0.24, -0.26);
    }
    watch.add(trainBridge);

    const barrelBridge = new THREE.Group();
    {
    const ring = new THREE.Mesh(new THREE.ExtrudeGeometry(ringShape(0.16, 0.34), {
    depth: 0.05, bevelEnabled: true, bevelThickness: 0.01, bevelSize: 0.01, bevelSegments: 2, curveSegments: 48
    }), MAT.geneva());
    barrelBridge.add(ring);
    const s1 = makeScrew(MAT.blue());
    s1.position.set(0.226, 0.105, 0.06);
    s1.rotation.z = Math.random() * TAU;
    barrelBridge.add(s1);
    const s2 = makeScrew(MAT.blue());
    s2.position.set(-0.226, -0.105, 0.06);
    s2.rotation.z = Math.random() * TAU;
    barrelBridge.add(s2);
    barrelBridge.position.set(0.44, 0.30, 0.11);
    }
    watch.add(barrelBridge);

    const balanceCock = new THREE.Group();
    {
    const sector = new THREE.Mesh(new THREE.ExtrudeGeometry(annularSectorShape(0.10, 0.29, -170, -75), {
    depth: 0.05, bevelEnabled: true, bevelThickness: 0.008, bevelSize: 0.008, bevelSegments: 2, curveSegments: 32
    }), MAT.geneva());
    balanceCock.add(sector);
    const foot = new THREE.Mesh(new THREE.ExtrudeGeometry(discShape(0.085), {
    depth: 0.05, bevelEnabled: true, bevelThickness: 0.01, bevelSize: 0.01, bevelSegments: 2, curveSegments: 32
    }), MAT.geneva());
    foot.position.set(-0.1226, -0.2628, 0);
    balanceCock.add(foot);
    const footScrew = makeScrew(MAT.blue());
    footScrew.position.set(-0.1226, -0.2628, 0.06);
    footScrew.rotation.z = Math.random() * TAU;
    balanceCock.add(footScrew);
    const jewel = makeJewel();
    jewel.position.z = 0.055;
    balanceCock.add(jewel);
    const cap = new THREE.Mesh(cylZ(0.075, 0.075, 0.014, 28), MAT.gold());
    cap.position.z = 0.073;
    balanceCock.add(cap);
    const cs1 = makeScrew(MAT.blue());
    cs1.scale.setScalar(0.55);
    cs1.position.set(0.052, 0, 0.077);
    balanceCock.add(cs1);
    const cs2 = makeScrew(MAT.blue());
    cs2.scale.setScalar(0.55);
    cs2.position.set(-0.052, 0, 0.077);
    balanceCock.add(cs2);
    balanceCock.position.set(0.04, -0.52, 0.13);
    }
    watch.add(balanceCock);

    const palletCock = new THREE.Group();
    {
    const box = new THREE.Mesh(new RoundedBoxGeometry(0.32, 0.15, 0.05, 3, 0.06), MAT.geneva());
    palletCock.add(box);
    const jewel = makeJewel();
    jewel.position.z = 0.04;
    palletCock.add(jewel);
    const sc = makeScrew(MAT.blue());
    sc.position.set(0.10, 0, 0.03);
    sc.rotation.z = Math.random() * TAU;
    palletCock.add(sc);
    palletCock.position.set(-0.168, -0.416, 0.135);
    palletCock.rotation.z = Math.atan2(-0.104, 0.208);
    }
    watch.add(palletCock);

    const dial = new THREE.Group();
    {
    const ring = new THREE.Mesh(new THREE.ExtrudeGeometry(ringShape(0.83, 0.92), {
    depth: 0.025, bevelEnabled: true, bevelThickness: 0.006, bevelSize: 0.006, bevelSegments: 2, curveSegments: 64
    }), MAT.dialBlack());
    ring.position.z = 0.152;
    dial.add(ring);
    for (let k = 0; k < 12; k++) {
    const a = k * Math.PI / 6;
    const L = k % 3 === 0 ? 0.22 : 0.15;
    const b = new THREE.Mesh(new THREE.BoxGeometry(0.045, L, 0.028), MAT.lume());
    b.position.set(Math.cos(a) * 0.875, Math.sin(a) * 0.875, 0.175);
    b.rotation.z = a;
    dial.add(b);
    }
    for (let m = 0; m < 60; m++) {
    if (m % 5 === 0) continue;
    const a = m * Math.PI / 30;
    const d = new THREE.Mesh(cylZ(0.011, 0.011, 0.012, 12), MAT.lumeDim());
    d.position.set(Math.cos(a) * 0.855, Math.sin(a) * 0.855, 0.172);
    dial.add(d);
    }
    }
    watch.add(dial);

    const hourHand = new THREE.Group();
    {
    const mesh = new THREE.Mesh(new THREE.ExtrudeGeometry(handShape(0.44, 0.055), {
    depth: 0.016, bevelEnabled: true, bevelThickness: 0.004, bevelSize: 0.004, bevelSegments: 2
    }), MAT.steel());
    hourHand.add(mesh);
    const lume = new THREE.Mesh(new THREE.ExtrudeGeometry(handShape(0.28, 0.028), {
    depth: 0.008, bevelEnabled: false
    }), MAT.lume());
    lume.position.set(0.13, 0, 0.012);
    hourHand.add(lume);
    const cap = new THREE.Mesh(cylZ(0.028, 0.028, 0.02, 20), MAT.gold());
    cap.position.z = 0.014;
    hourHand.add(cap);
    hourHand.position.z = 0.235;
    }
    watch.add(hourHand);

    const minuteHand = new THREE.Group();
    {
    const mesh = new THREE.Mesh(new THREE.ExtrudeGeometry(handShape(0.62, 0.045), {
    depth: 0.016, bevelEnabled: true, bevelThickness: 0.004, bevelSize: 0.004, bevelSegments: 2
    }), MAT.steel());
    minuteHand.add(mesh);
    const lume = new THREE.Mesh(new THREE.ExtrudeGeometry(handShape(0.40, 0.022), {
    depth: 0.008, bevelEnabled: false
    }), MAT.lume());
    lume.position.set(0.19, 0, 0.012);
    minuteHand.add(lume);
    const cap = new THREE.Mesh(cylZ(0.028, 0.028, 0.02, 20), MAT.gold());
    cap.position.z = 0.014;
    minuteHand.add(cap);
    minuteHand.position.z = 0.255;
    }
    watch.add(minuteHand);

    const secondHand = new THREE.Group();
    {
    const needle = new THREE.Mesh(new THREE.ExtrudeGeometry(handShape(0.66, 0.010), {
    depth: 0.008, bevelEnabled: false
    }), MAT.blue());
    secondHand.add(needle);
    const cw = new THREE.Mesh(cylZ(0.034, 0.034, 0.012, 20), MAT.gold());
    cw.position.set(-0.09, 0, 0.004);
    secondHand.add(cw);
    const dot = new THREE.Mesh(cylZ(0.026, 0.026, 0.012, 20), MAT.lume());
    dot.position.set(0.55, 0, 0.004);
    secondHand.add(dot);
    const cap = new THREE.Mesh(cylZ(0.03, 0.03, 0.014, 20), MAT.gold());
    cap.position.z = 0.006;
    secondHand.add(cap);
    secondHand.position.z = 0.275;
    }
    watch.add(secondHand);

    const INFO = {
    case: { name: '表壳', en: 'CASE', desc: '42mm 圆形表壳,由整块 904L 精钢切割而成,侧面镜面抛光与拉丝交替处理,光影流转间尽显质感。', specs: [['材质', '904L 精钢'], ['尺寸', '42 mm'], ['工艺', '抛光 / 拉丝交替打磨']] },
    bezel: { name: '表圈', en: 'BEZEL', desc: '高抛光斜面表圈,以柔和弧度收拢光线,勾勒出镂空表盘的深邃轮廓。', specs: [['材质', '抛光精钢'], ['工艺', '高光倒角']] },
    crown: { name: '表冠', en: 'CROWN', desc: '凹槽式表冠,防滑纹理便于上链与调校;旋入式结构确保 50 米防水。', specs: [['材质', '精钢'], ['工艺', '防滑凹槽纹'], ['功能', '手动上链 · 调时']] },
    crystal: { name: '蓝宝石水晶玻璃', en: 'SAPPHIRE CRYSTAL', desc: '弧形蓝宝石水晶玻璃,双面防眩光涂层,莫氏硬度 9,仅次于钻石,为机芯提供一览无余的窗口。', specs: [['材质', '蓝宝石水晶'], ['硬度', '莫氏 9'], ['处理', '双面防眩涂层']] },
    dial: { name: '镂空刻度环', en: 'CHAPTER RING', desc: '镂空刻度环与立体夜光时标,摒弃传统表盘,让整个轮系成为主角。', specs: [['材质', '精钢'], ['工艺', '镂空 + 立体夜光']] },
    hourHand: { name: '时针', en: 'HOUR HAND', desc: '抛光剑形时针,内嵌 Super-LumiNova 夜光涂层,暗处亦清晰可读。', specs: [['材质', '抛光精钢'], ['夜光', 'Super-LumiNova']] },
    minuteHand: { name: '分针', en: 'MINUTE HAND', desc: '与时针同款的剑形分针,末端纤细锐利,直指分钟刻度。', specs: [['材质', '抛光精钢'], ['夜光', 'Super-LumiNova']] },
    secondHand: { name: '秒针', en: 'SECOND HAND', desc: '烤蓝精钢秒针,纤细如丝,尾端圆形配重与夜光圆点形成优雅平衡。', specs: [['材质', '烤蓝精钢'], ['夜光', '尖端圆点']] },
    barrel: { name: '主发条盒', en: 'MAINSPRING BARREL', desc: '主发条盒——机芯的动力之源。盒内发条储存约 72 小时动力,经齿轮系逐级传递。', specs: [['功能', '72 小时动力储存'], ['材质', '精钢发条']] },
    centerWheel: { name: '中心轮', en: 'CENTRE WHEEL', desc: '中心轮直接与发条盒齿轮啮合,驱动时、分指针,是轮系的第二级。', specs: [['材质', '黄铜镀金'], ['功能', '驱动时 / 分针']] },
    thirdWheel: { name: '三轮', en: 'THIRD WHEEL', desc: '三轮承接中心轮动力,继续放大转速,是能量传递的中枢。', specs: [['材质', '黄铜镀金'], ['功能', '传动加速']] },
    fourthWheel: { name: '秒轮', en: 'FOURTH WHEEL', desc: '秒轮与中心同轴,以每分钟一周的稳定速度驱动中央秒针。', specs: [['材质', '黄铜镀金'], ['功能', '驱动中央秒针']] },
    escapeWheel: { name: '擒纵轮', en: 'ESCAPE WHEEL', desc: '15 齿擒纵轮,每次擒纵叉摆动释放一齿,把持续动力切分为清脆的滴答声。', specs: [['材质', '抛光精钢'], ['齿数', '15'], ['功能', '分解放动能量']] },
    palletFork: { name: '擒纵叉', en: 'PALLET FORK', desc: '擒纵叉以红宝石叉瓦与擒纵轮啮合,将能量递送给摆轮,维持其永不疲倦的摆动。', specs: [['材质', '精钢'], ['宝石', '红宝石叉瓦'], ['功能', '擒纵锁接']] },
    balance: { name: '摆轮', en: 'BALANCE WHEEL', desc: '摆轮——机芯的心脏。配重螺丝与双臂环设计,以 4 Hz 频率稳定摆动,主宰走时精度。', specs: [['材质', '镀金双金属'], ['频率', '4 Hz (28\'800 vph)'], ['功能', '调速振荡']] },
    hairspring: { name: '游丝', en: 'HAIRSPRING', desc: '蓝钢宝玑式游丝,与摆轮构成振荡系统,如同微型的呼吸,决定每一次滴答的节奏。', specs: [['材质', '蓝钢游丝'], ['结构', '宝玑上绕'], ['功能', '等时振荡']] },
    mainPlate: { name: '主夹板', en: 'MAIN PLATE', desc: '镀铑主夹板,遍布传统珍珠纹打磨,是支撑全部轮系与夹板的基石。', specs: [['材质', '镀铑黄铜'], ['工艺', '珍珠纹打磨']] },
    barrelBridge: { name: '发条盒夹板', en: 'BARREL BRIDGE', desc: '发条盒夹板,日内瓦条纹打磨,以蓝钢螺丝固定,镶嵌红宝石轴承。', specs: [['材质', '镀铑'], ['工艺', '日内瓦条纹']] },
    trainBridge: { name: '轮系夹板', en: 'TRAIN BRIDGE', desc: '轮系夹板横跨中心轮与三轮,两端红宝石轴承确保轮轴运转顺滑。', specs: [['材质', '镀铑'], ['工艺', '日内瓦条纹 + 倒角']] },
    balanceCock: { name: '摆轮夹板', en: 'BALANCE COCK', desc: '摆轮夹板以优雅曲线悬架于摆轮之上,末端蓝钢螺丝固定,其上镶嵌轴承宝石。', specs: [['材质', '镀铑'], ['工艺', '雕刻曲线']] },
    palletCock: { name: '擒纵叉夹板', en: 'PALLET COCK', desc: '擒纵叉夹板小巧精致,固定擒纵叉枢轴,配红宝石轴承。', specs: [['材质', '镀铑'], ['功能', '固定擒纵叉']] },
    jewel: { name: '红宝石轴承', en: 'JEWEL BEARING', desc: '人造红宝石轴承,摩擦系数极低、经久耐磨,全表共 19 颗。', specs: [['材质', '人造红宝石'], ['数量', '19 石'], ['功能', '减摩轴承']] },
    screw: { name: '蓝钢螺丝', en: 'BLUED SCREW', desc: '蓝钢螺丝经传统烤蓝工艺处理,淬火后呈现深邃的湛蓝光泽。', specs: [['材质', '蓝钢'], ['工艺', '传统烤蓝']] },
    caseback: { name: '透视底盖', en: 'CASE BACK', desc: '蓝宝石透视底盖,将机芯背面同样精湛的打磨工艺展露无遗。', specs: [['材质', '精钢'], ['窗口', '蓝宝石玻璃'], ['功能', '透视机芯']] }
    };

    const PART_LIST = [
    ['case', '表壳', 'CASE', '#ccd4df'],
    ['bezel', '表圈', 'BEZEL', '#e6eaf1'],
    ['crown', '表冠', 'CROWN', '#b9c2cf'],
    ['crystal', '蓝宝石水晶玻璃', 'SAPPHIRE CRYSTAL', '#8fd0ff'],
    ['dial', '镂空刻度环', 'CHAPTER RING', '#4a515f'],
    ['hourHand', '时针', 'HOUR HAND', '#dbe2ec'],
    ['minuteHand', '分针', 'MINUTE HAND', '#dbe2ec'],
    ['secondHand', '秒针', 'SECOND HAND', '#4a6fe0'],
    ['barrel', '主发条盒', 'MAINSPRING BARREL', '#c9a45c'],
    ['centerWheel', '中心轮', 'CENTRE WHEEL', '#d8b34f'],
    ['thirdWheel', '三轮', 'THIRD WHEEL', '#d8b34f'],
    ['fourthWheel', '秒轮', 'FOURTH WHEEL', '#d8b34f'],
    ['escapeWheel', '擒纵轮', 'ESCAPE WHEEL', '#dfe2e9'],
    ['palletFork', '擒纵叉', 'PALLET FORK', '#cfd6e0'],
    ['balance', '摆轮', 'BALANCE WHEEL', '#e2c163'],
    ['hairspring', '游丝', 'HAIRSPRING', '#3f66d8'],
    ['mainPlate', '主夹板', 'MAIN PLATE', '#ccd4df'],
    ['barrelBridge', '发条盒夹板', 'BARREL BRIDGE', '#ccd4df'],
    ['trainBridge', '轮系夹板', 'TRAIN BRIDGE', '#ccd4df'],
    ['balanceCock', '摆轮夹板', 'BALANCE COCK', '#ccd4df'],
    ['palletCock', '擒纵叉夹板', 'PALLET COCK', '#ccd4df'],
    ['jewel', '红宝石轴承', 'JEWEL BEARING', '#c01830'],
    ['screw', '蓝钢螺丝', 'BLUED SCREW', '#2e56c8'],
    ['caseback', '透视底盖', 'CASE BACK', '#ccd4df']
    ];

    const EXPLODE = {
    crystal: [0, 0, 1.2], bezel: [0, 0, 0.95], crown: [0.6, 0, 0], dial: [0, 0, 0.5],
    hourHand: [0, 0, 0.62], minuteHand: [0, 0, 0.82], secondHand: [0, 0, 1.02],
    barrelBridge: [0, 0, 0.44], trainBridge: [0, 0, 0.40], balanceCock: [0, 0, 0.36], palletCock: [0, 0, 0.32],
    barrel: [0, 0, -0.34], centerWheel: [0, 0, -0.22], thirdWheel: [0, 0, -0.26], fourthWheel: [0, 0, -0.30],
    escapeWheel: [0, 0, -0.28], palletFork: [0, 0, -0.33], balance: [0, 0, -0.42],
    mainPlate: [0, 0, -0.55], caseback: [0, 0, -0.85]
    };

    const parts = {};
    function register(id, group) {
    parts[id] = { group, meshes: null, home: group.position.clone(), explode: null, explodedPos: null, name: INFO[id].name, en: INFO[id].en };
    if (EXPLODE[id]) {
    parts[id].explode = new THREE.Vector3(EXPLODE[id][0], EXPLODE[id][1], EXPLODE[id][2]);
    parts[id].explodedPos = parts[id].home.clone().add(parts[id].explode);
    }
    group.userData.partId = id;
    group.traverse(o => { if (o.userData.partId === undefined) o.userData.partId = id; });
    }
    register('case', caseGroup);
    register('caseback', caseback);
    register('bezel', bezel);
    register('crystal', crystal);
    register('crown', crownGroup);
    register('mainPlate', mainPlate);
    register('barrel', barrel);
    register('centerWheel', centerWheel);
    register('thirdWheel', thirdWheel);
    register('fourthWheel', fourthWheel);
    register('escapeWheel', escapeWheel);
    register('palletFork', palletFork);
    register('hairspring', hairspring);
    register('balance', balance);
    register('trainBridge', trainBridge);
    register('barrelBridge', barrelBridge);
    register('balanceCock', balanceCock);
    register('palletCock', palletCock);
    register('dial', dial);
    register('hourHand', hourHand);
    register('minuteHand', minuteHand);
    register('secondHand', secondHand);
    parts.jewel = { group: null, meshes: jewelMeshes, home: null, explode: null, explodedPos: null, name: INFO.jewel.name, en: INFO.jewel.en };
    parts.screw = { group: null, meshes: screwMeshes, home: null, explode: null, explodedPos: null, name: INFO.screw.name, en: INFO.screw.en };

    crystal.userData.noRay = true;
    crystal.traverse(o => { o.userData.noRay = true; });

    watch.traverse(o => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } });
    crystal.traverse(o => { if (o.isMesh) { o.castShadow = false; o.receiveShadow = false; } });

    const composer = new EffectComposer(renderer);
    composer.addPass(new RenderPass(scene, camera));
    const bloomPass = new UnrealBloomPass(new THREE.Vector2(innerWidth, innerHeight), 0.32, 0.45, 1.3);
    composer.addPass(bloomPass);
    composer.addPass(new OutputPass());
    composer.setPixelRatio(Math.min(devicePixelRatio, 2));

    const legendEl = document.getElementById('legend');
    PART_LIST.forEach(p => {
    const chip = document.createElement('div');
    chip.className = 'chip';
    chip.innerHTML = `<span class="dot" style="background:${p[3]}"></span>${p[1]}`;
    chip.addEventListener('mouseenter', () => { hoverPart = p[0]; });
    chip.addEventListener('mouseleave', () => { if (hoverPart === p[0]) hoverPart = null; });
    chip.addEventListener('click', () => { pinPart(p[0], null); });
    legendEl.appendChild(chip);
    });

    const tooltip = document.getElementById('tooltip');
    const infoPanel = document.getElementById('info');
    const infoName = document.getElementById('infoName');
    const infoEn = document.getElementById('infoEn');
    const infoDesc = document.getElementById('infoDesc');
    const infoSpecs = document.getElementById('infoSpecs');
    document.getElementById('infoClose').addEventListener('click', clearPin);

    function showInfo(id) {
    const d = INFO[id];
    if (!d) return;
    infoName.textContent = d.name;
    infoEn.textContent = d.en;
    infoDesc.textContent = d.desc;
    infoSpecs.innerHTML = d.specs.map(s => `<tr><td>${s[0]}</td><td>${s[1]}</td></tr>`).join('');
    infoPanel.classList.add('open');
    }
    function closeInfo() { infoPanel.classList.remove('open'); }

    const ray = new THREE.Raycaster();
    const ndc = new THREE.Vector2();
    let hoverPart = null;
    let hlCurrent = null;
    let pinnedPart = null;
    let pinnedObj = null;
    let pinEl = null;

    function forEachMeshOf(id, fn) {
    const p = parts[id];
    if (!p) return;
    if (p.group) p.group.traverse(o => { if (o.isMesh) fn(o); });
    else if (p.meshes) p.meshes.forEach(m => m.traverse(o => { if (o.isMesh) fn(o); }));
    }

    const hlStore = new WeakMap();
    function setPartHighlight(id, on) {
    if (!parts[id]) return;
    forEachMeshOf(id, o => {
    const m = o.material;
    if (!m || m.emissive === undefined) return;
    if (on) {
    if (!hlStore.has(o)) hlStore.set(o, { e: m.emissive.getHex(), i: m.emissiveIntensity });
    m.emissive.set(0x4d7dff);
    m.emissiveIntensity = 0.45;
    } else if (hlStore.has(o)) {
    const s = hlStore.get(o);
    m.emissive.setHex(s.e);
    m.emissiveIntensity = s.i;
    hlStore.delete(o);
    }
    });
    }

    function findPartId(obj) {
    let o = obj;
    while (o) {
    if (o.userData && o.userData.noRay) return null;
    if (o.userData && o.userData.partId) return o.userData.partId;
    o = o.parent;
    }
    return null;
    }

    function pick(e) {
    ndc.set((e.clientX / innerWidth) * 2 - 1, -(e.clientY / innerHeight) * 2 + 1);
    ray.setFromCamera(ndc, camera);
    const hits = ray.intersectObjects(watch.children, true);
    for (const h of hits) {
    const id = findPartId(h.object);
    if (id) return { id, obj: h.object };
    }
    return null;
    }

    function pinPart(id, obj) {
    clearPin(false);
    pinnedPart = id;
    pinnedObj = obj || (parts[id].meshes ? parts[id].meshes[0] : parts[id].group);
    pinEl = document.createElement('div');
    pinEl.className = 'pin-label';
    pinEl.textContent = INFO[id].name;
    document.body.appendChild(pinEl);
    showInfo(id);
    }

    function clearPin(close) {
    pinnedPart = null;
    pinnedObj = null;
    if (pinEl) { pinEl.remove(); pinEl = null; }
    if (close !== false) closeInfo();
    }

    window.addEventListener('keydown', e => { if (e.key === 'Escape') clearPin(); });

    let downInfo = null;
    renderer.domElement.addEventListener('pointerdown', e => {
    downInfo = { x: e.clientX, y: e.clientY, t: performance.now() };
    });
    renderer.domElement.addEventListener('pointermove', e => {
    if (downInfo) return;
    const res = pick(e);
    hoverPart = res ? res.id : null;
    if (res) {
    tooltip.textContent = `${INFO[res.id].name} · ${INFO[res.id].en}`;
    tooltip.style.left = Math.min(e.clientX + 16, innerWidth - 220) + 'px';
    tooltip.style.top = (e.clientY + 18) + 'px';
    tooltip.style.opacity = '1';
    } else {
    tooltip.style.opacity = '0';
    }
    });
    renderer.domElement.addEventListener('pointerup', e => {
    if (!downInfo) return;
    const moved = Math.hypot(e.clientX - downInfo.x, e.clientY - downInfo.y) > 5 || performance.now() - downInfo.t > 600;
    downInfo = null;
    if (moved) return;
    const res = pick(e);
    if (res) pinPart(res.id, res.obj);
    else clearPin();
    });

    let exploded = false;
    let paused = false;
    document.getElementById('btnExplode').addEventListener('click', function () {
    exploded = !exploded;
    this.classList.toggle('active', exploded);
    this.textContent = exploded ? '复原视图' : '爆炸视图';
    });
    document.getElementById('btnPause').addEventListener('click', function () {
    paused = !paused;
    this.classList.toggle('active', paused);
    this.textContent = paused ? '继续' : '暂停';
    if (paused) controls.autoRotate = false;
    });
    document.getElementById('btnReset').addEventListener('click', () => {
    flyTo(HOME_POS, HOME_TARGET, 1.0);
    });

    let tween = null;
    function flyTo(pos, targetPos, dur) {
    tween = { t: 0, dur, fromPos: camera.position.clone(), toPos: pos.clone(), fromT: controls.target.clone(), toT: targetPos.clone() };
    controls.enabled = false;
    controls.autoRotate = false;
    }

    const clock = new THREE.Clock();
    let simTime = 0;
    const s0 = (31 / 60) * TAU;
    const m0 = ((10 + 31 / 60) / 60) * TAU;
    const h0 = ((10 + 10 / 60) / 12) * TAU;
    let sAngle = s0, mAngle = m0, hAngle = h0;

    const tmpV = new THREE.Vector3();

    function animate() {
    requestAnimationFrame(animate);
    const dt = Math.min(clock.getDelta(), 0.1);
    if (!paused) simTime += dt;
    const st = simTime;

    if (tween) {
    tween.t += dt;
    const e = Math.min(tween.t / tween.dur, 1);
    const k = 1 - Math.pow(1 - e, 3);
    camera.position.lerpVectors(tween.fromPos, tween.toPos, k);
    controls.target.lerpVectors(tween.fromT, tween.toT, k);
    if (e >= 1) { tween = null; controls.enabled = true; lastInteract = performance.now(); }
    }

    if (!controls.autoRotate && !paused && controls.enabled && !tween && performance.now() - lastInteract > 3500) {
    controls.autoRotate = true;
    }
    controls.update();

    const k = 1 - Math.exp(-dt * 4.5);
    for (const id in parts) {
    const p = parts[id];
    if (!p.explode) continue;
    p.group.position.lerp(exploded ? p.explodedPos : p.home, k);
    }

    sAngle = s0 + st * TAU / 60;
    mAngle = m0 + st * TAU / 3600;
    hAngle = h0 + st * TAU / 43200;
    hourHand.rotation.z = hAngle;
    minuteHand.rotation.z = mAngle;
    secondHand.rotation.z = sAngle;
    fourthWheel.rotation.z = sAngle;
    barrel.rotation.z = st * 0.1;
    centerWheel.rotation.z = -st * 0.8;
    thirdWheel.rotation.z = st * 3.2;
    escapeWheel.rotation.z = Math.floor(st * 3.2) * (TAU / 15);
    palletFork.rotation.z = 0.15 * Math.tanh(4 * Math.sin(TAU * 1.6 * st - 0.35));
    balance.rotation.z = 4.1 * Math.sin(TAU * 1.6 * st) * (1 + 0.05 * Math.sin(st * 0.8));
    hairspring.scale.setScalar(1 + 0.05 * Math.sin(TAU * 1.6 * st));

    const want = pinnedPart || hoverPart;
    if (want !== hlCurrent) {
    if (hlCurrent) setPartHighlight(hlCurrent, false);
    if (want) setPartHighlight(want, true);
    hlCurrent = want;
    }
    if (pinnedPart) {
    const pulse = 0.4 + 0.2 * Math.sin(performance.now() * 0.005);
    forEachMeshOf(pinnedPart, o => { if (o.material && o.material.emissiveIntensity !== undefined) o.material.emissiveIntensity = pulse; });
    }
    renderer.domElement.style.cursor = want ? 'pointer' : '';

    if (pinnedObj) {
    pinnedObj.getWorldPosition(tmpV);
    tmpV.project(camera);
    if (tmpV.z < 1) {
    pinEl.style.opacity = '1';
    pinEl.style.left = (tmpV.x * 0.5 + 0.5) * innerWidth + 'px';
    pinEl.style.top = (1 - (tmpV.y * 0.5 + 0.5)) * innerHeight - 30 + 'px';
    } else {
    pinEl.style.opacity = '0';
    }
    }

    composer.render();
    }

    flyTo(HOME_POS, HOME_TARGET, 2.6);
    animate();

    setTimeout(() => {
    const loading = document.getElementById('loading');
    loading.style.opacity = '0';
    setTimeout(() => loading.remove(), 950);
    }, 400);

    addEventListener('resize', () => {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
    composer.setSize(innerWidth, innerHeight);
    });
    </script>
    </body>
    </html>

  • ink 08-13 16:52
    11

    用gpt5.6sol中档生成的,效果也还行,就是太暗了


  • role_c 08-13 17:00
    12

    佬,你这个跑的时候,思维链是什么样的

  • 小杨 08-13 17:03
    13



    gpt生成的看起来还不错

  • 道友 楼主 08-13 17:03
    14




    这样的。我看不懂有什么区别。

  • 传奇睡觉王 08-13 17:18
    15



    GLM 5.2

* 帖子来源Linux.do
返回