auto-save 2026-05-15 18:17 (+1, ~4)
This commit is contained in:
@@ -1,18 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 1,
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 02:30 (+2, ~4)",
|
||||
"ts": "2026-05-13T18:33:11Z",
|
||||
"type": "session-heartbeat"
|
||||
},
|
||||
{
|
||||
"files_changed": 2,
|
||||
"hash": "2b7eb00",
|
||||
"message": "auto-save 2026-05-14 02:36 (~2)",
|
||||
"ts": "2026-05-14T02:36:34+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 4,
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-14 02:36 (~2)",
|
||||
@@ -3254,6 +3241,19 @@
|
||||
"message": "auto-save 2026-05-15 18:06 (~1)",
|
||||
"hash": "84143bc",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-15T18:12:39+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-15 18:12 (~1)",
|
||||
"hash": "6c8bc42",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-15T10:14:49Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-15 18:12 (~1)",
|
||||
"files_changed": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1
RULES.md
1
RULES.md
@@ -57,6 +57,7 @@
|
||||
- 不允许编造不存在的部署域名、账号、密码
|
||||
- 没有公网地址时,`.project.json.urls` 保持空数组
|
||||
- 任何部署或域名变化,都要先改元数据,再视为任务完成
|
||||
- 用户给到源码 / 下载包 / 参考实现时,默认优先按源码实现和复刻,不先自创“类似效果”;如果因安全、依赖、性能或部署限制必须改写,必须先说明差异和原因。
|
||||
|
||||
## 注意事项
|
||||
- 项目内源码解析页:`docs/source-analysis.html`
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
z-index: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border: 0;
|
||||
background: #030303;
|
||||
}
|
||||
.login-oasis-shade {
|
||||
|
||||
@@ -1,183 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
type Blade = {
|
||||
x: number
|
||||
z: number
|
||||
h: number
|
||||
lean: number
|
||||
phase: number
|
||||
warmth: number
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.max(min, Math.min(max, value))
|
||||
}
|
||||
|
||||
export function OasisCanvas() {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
|
||||
const ctx = canvas.getContext("2d", { alpha: true })
|
||||
if (!ctx) return
|
||||
|
||||
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
const pointer = { x: 0.5, y: 0.5, active: false }
|
||||
let width = 0
|
||||
let height = 0
|
||||
let dpr = 1
|
||||
let raf = 0
|
||||
let blades: Blade[] = []
|
||||
|
||||
const makeField = () => {
|
||||
const count = width < 640 ? 520 : width < 1100 ? 880 : 1280
|
||||
blades = Array.from({ length: count }, (_, index) => {
|
||||
const row = index / count
|
||||
const depth = Math.pow(row, 0.44)
|
||||
const spread = 0.12 + depth * 1.12
|
||||
return {
|
||||
x: 0.5 + (Math.random() - 0.5) * spread,
|
||||
z: depth,
|
||||
h: 0.55 + Math.random() * 1.15,
|
||||
lean: (Math.random() - 0.5) * 0.9,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
warmth: Math.random(),
|
||||
}
|
||||
}).sort((a, b) => a.z - b.z)
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
width = Math.max(1, Math.floor(rect.width))
|
||||
height = Math.max(1, Math.floor(rect.height))
|
||||
dpr = Math.min(window.devicePixelRatio || 1, width < 900 ? 1.35 : 1.65)
|
||||
canvas.width = Math.floor(width * dpr)
|
||||
canvas.height = Math.floor(height * dpr)
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
makeField()
|
||||
}
|
||||
|
||||
const draw = (timeMs: number) => {
|
||||
const time = timeMs / 1000
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
const sky = ctx.createLinearGradient(0, 0, 0, height)
|
||||
sky.addColorStop(0, "#030405")
|
||||
sky.addColorStop(0.36, "#070809")
|
||||
sky.addColorStop(0.7, "#0d0b05")
|
||||
sky.addColorStop(1, "#010101")
|
||||
ctx.fillStyle = sky
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
const horizonY = height * 0.44
|
||||
const glow = ctx.createRadialGradient(width * 0.5, horizonY, 0, width * 0.5, horizonY, width * 0.65)
|
||||
glow.addColorStop(0, "rgba(225, 195, 82, 0.34)")
|
||||
glow.addColorStop(0.26, "rgba(166, 160, 50, 0.2)")
|
||||
glow.addColorStop(0.62, "rgba(16, 22, 10, 0.18)")
|
||||
glow.addColorStop(1, "rgba(0, 0, 0, 0)")
|
||||
ctx.fillStyle = glow
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
const floor = ctx.createLinearGradient(0, horizonY, 0, height)
|
||||
floor.addColorStop(0, "rgba(166, 152, 42, 0.05)")
|
||||
floor.addColorStop(0.36, "rgba(156, 145, 34, 0.18)")
|
||||
floor.addColorStop(1, "rgba(7, 8, 2, 0.92)")
|
||||
ctx.fillStyle = floor
|
||||
ctx.fillRect(0, horizonY, width, height)
|
||||
|
||||
ctx.save()
|
||||
ctx.globalCompositeOperation = "lighter"
|
||||
for (let i = 0; i < 42; i += 1) {
|
||||
const t = i / 41
|
||||
const x = (width * (0.08 + t * 0.86) + Math.sin(time * 0.18 + i) * 18) % width
|
||||
const y = horizonY + Math.sin(time * 0.25 + i * 0.7) * 28 + Math.cos(i) * 16
|
||||
const r = 1.5 + ((i * 13) % 9)
|
||||
ctx.fillStyle = `rgba(214, 179, 106, ${0.02 + (i % 5) * 0.006})`
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, r, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
ctx.restore()
|
||||
|
||||
for (const blade of blades) {
|
||||
const depth = blade.z
|
||||
const perspective = 0.2 + depth * 1.22
|
||||
const baseX = width * (0.5 + (blade.x - 0.5) * perspective)
|
||||
const baseY = horizonY + Math.pow(depth, 1.85) * height * 0.68
|
||||
if (baseX < -60 || baseX > width + 60 || baseY < horizonY - 30 || baseY > height + 90) continue
|
||||
|
||||
const focus = 1 - Math.abs(depth - 0.58)
|
||||
const wind = Math.sin(time * 1.35 + blade.phase + blade.x * 8) * (4 + depth * 13)
|
||||
const pointerPush = pointer.active ? (pointer.x - 0.5) * 18 * Math.pow(depth, 1.2) : 0
|
||||
const bladeHeight = (18 + blade.h * 82) * Math.pow(depth, 1.35)
|
||||
const tipX = baseX + blade.lean * 20 * depth + wind + pointerPush
|
||||
const tipY = baseY - bladeHeight
|
||||
const midX = baseX + (tipX - baseX) * 0.52 + Math.sin(blade.phase) * 8 * depth
|
||||
const midY = baseY - bladeHeight * 0.55
|
||||
const alpha = clamp(0.14 + depth * 0.72, 0.12, 0.86)
|
||||
const lineWidth = clamp(0.45 + depth * 2.4, 0.5, 3.2)
|
||||
|
||||
const hueWarmth = blade.warmth > 0.42 ? "214, 179, 70" : "98, 123, 42"
|
||||
ctx.strokeStyle = `rgba(${hueWarmth}, ${alpha})`
|
||||
ctx.lineWidth = lineWidth
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(baseX, baseY)
|
||||
ctx.quadraticCurveTo(midX, midY, tipX, tipY)
|
||||
ctx.stroke()
|
||||
|
||||
if (focus > 0.78 && blade.warmth > 0.82) {
|
||||
ctx.fillStyle = `rgba(238, 220, 112, ${alpha * 0.34})`
|
||||
ctx.beginPath()
|
||||
ctx.arc(tipX, tipY, lineWidth * 0.9, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
const vignette = ctx.createRadialGradient(width * 0.5, height * 0.52, height * 0.1, width * 0.5, height * 0.52, height * 0.82)
|
||||
vignette.addColorStop(0, "rgba(0, 0, 0, 0)")
|
||||
vignette.addColorStop(0.56, "rgba(0, 0, 0, 0.12)")
|
||||
vignette.addColorStop(1, "rgba(0, 0, 0, 0.86)")
|
||||
ctx.fillStyle = vignette
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
const frontBlur = ctx.createLinearGradient(0, height * 0.62, 0, height)
|
||||
frontBlur.addColorStop(0, "rgba(0, 0, 0, 0)")
|
||||
frontBlur.addColorStop(1, "rgba(190, 174, 48, 0.24)")
|
||||
ctx.fillStyle = frontBlur
|
||||
ctx.fillRect(0, height * 0.62, width, height * 0.38)
|
||||
|
||||
if (!prefersReducedMotion) {
|
||||
raf = window.requestAnimationFrame(draw)
|
||||
}
|
||||
}
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
pointer.x = clamp((event.clientX - rect.left) / rect.width, 0, 1)
|
||||
pointer.y = clamp((event.clientY - rect.top) / rect.height, 0, 1)
|
||||
pointer.active = true
|
||||
}
|
||||
const onPointerLeave = () => {
|
||||
pointer.active = false
|
||||
}
|
||||
|
||||
resize()
|
||||
window.addEventListener("resize", resize)
|
||||
canvas.addEventListener("pointermove", onPointerMove)
|
||||
canvas.addEventListener("pointerleave", onPointerLeave)
|
||||
raf = window.requestAnimationFrame(draw)
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(raf)
|
||||
window.removeEventListener("resize", resize)
|
||||
canvas.removeEventListener("pointermove", onPointerMove)
|
||||
canvas.removeEventListener("pointerleave", onPointerLeave)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="login-oasis-canvas" aria-hidden="true" />
|
||||
return (
|
||||
<iframe
|
||||
aria-hidden="true"
|
||||
className="login-oasis-canvas"
|
||||
src="/oasis-source/index.html"
|
||||
title="Digital Oasis WebGPU background"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
2544
web/public/oasis-source/index.html
Normal file
2544
web/public/oasis-source/index.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user