auto-save 2026-05-15 18:17 (+1, ~4)
This commit is contained in:
@@ -1,18 +1,5 @@
|
|||||||
{
|
{
|
||||||
"entries": [
|
"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,
|
"files_changed": 4,
|
||||||
"message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-14 02:36 (~2)",
|
"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)",
|
"message": "auto-save 2026-05-15 18:06 (~1)",
|
||||||
"hash": "84143bc",
|
"hash": "84143bc",
|
||||||
"files_changed": 1
|
"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` 保持空数组
|
- 没有公网地址时,`.project.json.urls` 保持空数组
|
||||||
- 任何部署或域名变化,都要先改元数据,再视为任务完成
|
- 任何部署或域名变化,都要先改元数据,再视为任务完成
|
||||||
|
- 用户给到源码 / 下载包 / 参考实现时,默认优先按源码实现和复刻,不先自创“类似效果”;如果因安全、依赖、性能或部署限制必须改写,必须先说明差异和原因。
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
- 项目内源码解析页:`docs/source-analysis.html`
|
- 项目内源码解析页:`docs/source-analysis.html`
|
||||||
|
|||||||
@@ -182,6 +182,7 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
border: 0;
|
||||||
background: #030303;
|
background: #030303;
|
||||||
}
|
}
|
||||||
.login-oasis-shade {
|
.login-oasis-shade {
|
||||||
|
|||||||
@@ -1,183 +1,12 @@
|
|||||||
"use client"
|
"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() {
|
export function OasisCanvas() {
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
return (
|
||||||
|
<iframe
|
||||||
useEffect(() => {
|
aria-hidden="true"
|
||||||
const canvas = canvasRef.current
|
className="login-oasis-canvas"
|
||||||
if (!canvas) return
|
src="/oasis-source/index.html"
|
||||||
|
title="Digital Oasis WebGPU background"
|
||||||
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" />
|
|
||||||
}
|
}
|
||||||
|
|||||||
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