feat:加上 Vue 学习站与可嵌入机芯

站点按 bloub 三栏做自定义、动画时间线和设置,支持序列导出。
形体只留常见几种并配中文名;动画页右栏拉满时间线旁空位,滚动条隐藏。
机芯补 GROK.create / <grok-bot>,以及暂停冻结。
This commit is contained in:
Edom
2026-08-19 01:29:13 +08:00
parent 891f6ef7e7
commit 1ba126c92b
55 changed files with 8998 additions and 224 deletions
+87
View File
@@ -0,0 +1,87 @@
/** Load local geometry, then the replica IIFE engine. Missing geometry keeps the chrome. */
export type ReplicaBot = {
setState: (name: string, opts?: { resetEyes?: boolean }) => void
setShape: (name: string) => void
setColor: (id: string, scheme?: string) => void
setFollowPointer: (on: boolean) => void
setEyeColor: (color: string | null) => void
setPaused: (on: boolean | 'hold-pose') => void
spinOnce: (turns?: number) => void
bounceOnce: () => void
burstOnce: () => void
orbitGaze: (ms?: number) => void
holdFrame: (at?: number) => void
step: (dt: number) => void
destroy: () => void
}
declare global {
interface Window {
GROK_GEO?: {
Re?: number
shapes?: Record<string, unknown>
palette?: Record<string, { light: string; dark: string }>
}
GROK_TABLES?: {
GROUPS: Array<{ label: string; states: string[] }>
INK: Record<string, { lightFrom?: string; lightTo: string; darkFrom?: string; darkTo?: string }>
}
GrokCharacter?: new (svg: SVGSVGElement, opts: Record<string, unknown>) => ReplicaBot
}
}
let pending: Promise<boolean> | null = null
export function replicaReady(): boolean {
return typeof window !== 'undefined' && !!window.GROK_GEO?.shapes && typeof window.GrokCharacter === 'function'
}
function loadScript(src: string): Promise<void> {
return new Promise((resolve, reject) => {
const existing = document.querySelector(`script[data-replica="${src}"]`)
if (existing) {
resolve()
return
}
const el = document.createElement('script')
el.src = src
el.dataset.replica = src
el.onload = () => resolve()
el.onerror = () => reject(new Error(`replica: failed to load ${src}`))
document.head.appendChild(el)
})
}
async function loadOptional(src: string): Promise<boolean> {
try {
const res = await fetch(src)
if (!res.ok) return false
if (document.querySelector(`script[data-replica="${src}"]`)) return true
const el = document.createElement('script')
el.dataset.replica = src
el.textContent = await res.text()
document.head.appendChild(el)
return true
} catch {
return false
}
}
export function loadReplica(): Promise<boolean> {
if (replicaReady()) return Promise.resolve(true)
if (pending) return pending
pending = (async () => {
try {
const geo = await loadOptional('/replica/geometry-data.js')
if (!geo || !window.GROK_GEO) return false
for (const name of ['math', 'tables', 'pose', 'tricks', 'fx', 'eyes', 'character']) {
await loadScript(`/replica/src/${name}.js`)
}
return replicaReady()
} catch {
return false
}
})()
return pending
}