From a851ce1c590985af958e267f82e352ecea2cd104 Mon Sep 17 00:00:00 2001 From: kang Date: Fri, 15 May 2026 18:18:10 +0800 Subject: [PATCH] auto-save 2026-05-15 18:17 (+1, ~4) --- .memory/worklog.json | 26 +- RULES.md | 1 + web/app/globals.css | 1 + web/components/login/oasis-canvas.tsx | 187 +- web/public/oasis-source/index.html | 2544 +++++++++++++++++++++++++ 5 files changed, 2567 insertions(+), 192 deletions(-) create mode 100644 web/public/oasis-source/index.html diff --git a/.memory/worklog.json b/.memory/worklog.json index 81a07f1..44cdab0 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -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 } ] } diff --git a/RULES.md b/RULES.md index d5930f1..daa3cfa 100644 --- a/RULES.md +++ b/RULES.md @@ -57,6 +57,7 @@ - 不允许编造不存在的部署域名、账号、密码 - 没有公网地址时,`.project.json.urls` 保持空数组 - 任何部署或域名变化,都要先改元数据,再视为任务完成 +- 用户给到源码 / 下载包 / 参考实现时,默认优先按源码实现和复刻,不先自创“类似效果”;如果因安全、依赖、性能或部署限制必须改写,必须先说明差异和原因。 ## 注意事项 - 项目内源码解析页:`docs/source-analysis.html` diff --git a/web/app/globals.css b/web/app/globals.css index fc089e5..d594a48 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -182,6 +182,7 @@ z-index: 0; width: 100vw; height: 100vh; + border: 0; background: #030303; } .login-oasis-shade { diff --git a/web/components/login/oasis-canvas.tsx b/web/components/login/oasis-canvas.tsx index df9029b..d1648bf 100644 --- a/web/components/login/oasis-canvas.tsx +++ b/web/components/login/oasis-canvas.tsx @@ -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(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