Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license

This commit is contained in:
yilsem committed 2026-09-05 13:26:46 +03:00
commit 03634b1ca3
11340 files changed
+3442369

No files matched your search

+289
View File
@@ -0,0 +1,289 @@
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { Codicon } from '@/components/ui/codicon'
import type { TimeAxis } from './time-axis'
interface TimelineProps {
axis: TimeAxis
// Colour for memory stars — matches the map's memory glyph.
memoryColor?: string
onScrub: (reveal: number) => void
onTogglePlay: () => void
playing: boolean
revealStore: RevealSignal
// Reveal positions (0–1) where rings spawn — drawn as anchor ticks.
ringStops?: number[]
}
interface RevealSignal {
get: () => number
subscribe: (listener: (value: number) => void) => () => void
}
interface Star {
delay: number
duration: number
kind: 'memory' | 'skill'
leftPct: number
opacity: number
size: number
topPct: number
}
const ACTIVE_MARKER_CLASS = 'opacity-100'
const INACTIVE_MARKER_CLASS = 'opacity-30'
// Busiest bucket gets this many stars; quieter ones scale down proportionally.
const MAX_STARS_PER_BUCKET = 7
// Deterministic PRNG (mulberry32) so a bucket's stars stay put across renders.
function rng(seed: number): () => number {
let a = seed >>> 0
return () => {
a += 0x6d2b79f5
let t = a
t = Math.imul(t ^ (t >>> 15), t | 1)
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
// Scatter each time bucket's activity into stars: count ∝ events, split between
// skill- and memory-coloured stars, jittered within the bucket's horizontal slot
// and across the track height. Vertical placement is biased toward the midline
// (a triangular distribution) so stars cluster near the centre more often than
// the edges. A starmap timeline for a starmap.
function buildStars(axis: TimeAxis): Star[] {
const n = Math.max(1, axis.buckets.length)
const stars: Star[] = []
axis.buckets.forEach((b, i) => {
if (b.total === 0) {
return
}
const intensity = axis.maxTotal > 0 ? b.total / axis.maxTotal : 0
// sqrt curve so a single co-timed burst (a packed core ring) doesn't crush
// every quieter bucket down to the 1-star floor and read as blank.
const count = Math.max(1, Math.round(Math.sqrt(intensity) * MAX_STARS_PER_BUCKET))
const skillCount = Math.round((b.skill / b.total) * count)
const r = rng(i * 9973 + 7)
const slot = 1 / n
const center = (i + 0.5) / n
for (let s = 0; s < count; s++) {
const jitter = (r() - 0.5) * slot * 0.9
// Average of two uniforms → triangular peak at 0.5, pulling stars toward
// the midline more often while still reaching the edges occasionally.
const vertical = (r() + r()) / 2
stars.push({
delay: r() * 3,
duration: 2.4 + r() * 2.6,
kind: s < skillCount ? 'skill' : 'memory',
leftPct: Math.max(0, Math.min(1, center + jitter)) * 100,
// Brighter, slightly larger stars are rarer.
opacity: 0.5 + r() * 0.5,
// Floor at 2px so a lone star in a quiet bucket still reads on black.
size: 2 + Math.round(r() * r() * 2.2),
topPct: 12 + vertical * 76
})
}
})
return stars
}
// Playback scrubber as a constellation: dim stars are the unrevealed future; a
// scanner sweep ignites them (bright + twinkling) left→right as the reveal
// advances. The bright layer is clipped by the reveal CSS var, so the rAF sweep
// in StarMap drives it with zero per-frame JS.
export const Timeline = memo(function Timeline({
axis,
memoryColor = 'var(--theme-secondary)',
onScrub,
onTogglePlay,
playing,
revealStore,
ringStops = []
}: TimelineProps) {
const trackRef = useRef<HTMLDivElement | null>(null)
const draggingRef = useRef(false)
const markerRefs = useRef<HTMLDivElement[]>([])
const stars = useMemo(() => buildStars(axis), [axis])
const syncReveal = useCallback(
(value: number) => {
const reveal = Math.max(0, Math.min(1, value))
const track = trackRef.current
if (track) {
track.style.setProperty('--starmap-reveal', String(reveal))
track.setAttribute('aria-valuenow', String(Math.round(reveal * 100)))
}
ringStops.forEach((stop, i) => {
const el = markerRefs.current[i]
if (!el) {
return
}
const active = stop <= reveal
el.classList.toggle(ACTIVE_MARKER_CLASS, active)
el.classList.toggle(INACTIVE_MARKER_CLASS, !active)
})
},
[ringStops]
)
useEffect(() => revealStore.subscribe(syncReveal), [revealStore, syncReveal])
useEffect(() => {
markerRefs.current.length = ringStops.length
syncReveal(revealStore.get())
}, [revealStore, ringStops.length, syncReveal])
const ratioAt = (clientX: number): number => {
const rect = trackRef.current?.getBoundingClientRect()
if (!rect || rect.width === 0) {
return revealStore.get()
}
return Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
}
const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
draggingRef.current = true
e.currentTarget.setPointerCapture(e.pointerId)
onScrub(ratioAt(e.clientX))
}
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (draggingRef.current) {
onScrub(ratioAt(e.clientX))
}
}
const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
draggingRef.current = false
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
e.currentTarget.releasePointerCapture(e.pointerId)
}
}
const colorFor = (kind: Star['kind']) => (kind === 'skill' ? 'var(--theme-primary)' : memoryColor)
return (
<div className="pointer-events-auto flex w-[28rem] max-w-full items-center gap-3 [-webkit-app-region:no-drag]">
<style>{'@keyframes starmap-twinkle{0%,100%{opacity:var(--o,1)}50%{opacity:calc(var(--o,1) * 0.35)}}'}</style>
<button
aria-label={playing ? 'Pause' : 'Play timeline'}
className="flex size-5 shrink-0 items-center justify-center text-foreground/75 transition-colors hover:text-foreground"
onClick={onTogglePlay}
type="button"
>
<Codicon name={playing ? 'debug-pause' : 'triangle-right'} size={playing ? '0.8rem' : '0.95rem'} />
</button>
<div
aria-label="Timeline scrubber"
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={Math.round(revealStore.get() * 100)}
className="relative h-7 min-w-0 flex-1 cursor-pointer select-none touch-none"
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
ref={trackRef}
role="slider"
style={{ '--starmap-reveal': revealStore.get() } as React.CSSProperties}
tabIndex={0}
>
{/* Dashed midline — a faint horizontal axis the stars ride over. */}
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 border-t border-dashed border-foreground/5"
/>
{/* Dim constellation — the unrevealed future. */}
<div aria-hidden className="absolute inset-0">
{stars.map((star, i) => (
<div
className="absolute -translate-x-1/2 -translate-y-1/2 rounded-full"
key={i}
style={{
backgroundColor: colorFor(star.kind),
height: star.size,
left: `${star.leftPct}%`,
opacity: 0.22,
top: `${star.topPct}%`,
width: star.size
}}
/>
))}
</div>
{/* Ignited constellation — bright + twinkling, clipped to the reveal. */}
<div
aria-hidden
className="absolute inset-0"
style={{ clipPath: 'inset(0 calc((1 - var(--starmap-reveal, 1)) * 100%) 0 0)' }}
>
{stars.map((star, i) => {
const color = colorFor(star.kind)
return (
<div
className="absolute -translate-x-1/2 -translate-y-1/2 rounded-full"
key={i}
style={
{
'--o': star.opacity,
animation: `starmap-twinkle ${star.duration}s ease-in-out ${star.delay}s infinite`,
backgroundColor: color,
height: star.size,
left: `${star.leftPct}%`,
opacity: star.opacity,
top: `${star.topPct}%`,
width: star.size
} as React.CSSProperties
}
/>
)
})}
</div>
{/* Ring-spawn anchor ticks — small bright stars that light up on pass. */}
<div aria-hidden className="absolute inset-0">
{ringStops.map((stop, i) => (
<div
className={`pointer-events-none absolute top-1/2 size-1 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[var(--theme-primary)] ${INACTIVE_MARKER_CLASS}`}
key={i}
ref={el => {
if (el) {
markerRefs.current[i] = el
}
}}
style={{ left: `${stop * 100}%` }}
/>
))}
</div>
{/* Playhead — a thin white sweep line. */}
<div
aria-hidden
className="pointer-events-none absolute inset-y-0 w-0.5 -translate-x-1/2 bg-foreground"
style={{ left: 'calc(var(--starmap-reveal, 1) * 100%)' }}
/>
</div>
</div>
)
})