Skip to content

Commit 525a35d

Browse files
committed
feat: 添加复古操作系统风格时钟组件及多版本页面
新增复古时钟组件,支持GEM、RISC、AMIGA和WIN31四种主题风格 创建v2、v3和v3.2页面展示不同风格的时钟界面 更新导航按钮组件以支持新页面跳转 添加全局CSS样式支持Amiga风格三角裁剪效果 更新README文档说明新功能版本信息
1 parent cbb0c85 commit 525a35d

File tree

8 files changed

+4840
-18
lines changed

8 files changed

+4840
-18
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
![preview](./public/backgrounds/bg3.webp)
88
![preview](./public/backgrounds/bg4.webp)
99

10+
## v2
11+
12+
2025.11.20 使用Google最新大模型 Gemini 3 Pro开发,模拟Windows 3.1风格时钟.
13+
1014
## 声明
1115

1216
所有图片素材源来自网络,仅供学习参考,请勿用于商业用途.

app/components/RetroClock.tsx

Lines changed: 338 additions & 0 deletions
Large diffs are not rendered by default.

app/globals.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ body {
1919
background: var(--background);
2020
font-family: Arial, Helvetica, sans-serif;
2121
}
22+
23+
/* 用于 Amiga 右下角三角裁切 */
24+
.clip-triangle {
25+
clip-path: polygon(100% 0, 0% 100%, 100% 100%);
26+
}

app/v2/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import RetroClock from "../components/RetroClock"
2+
3+
export default function V2Page() {
4+
return (
5+
<main>
6+
<RetroClock />
7+
</main>
8+
);
9+
}

app/v3.2/page.tsx

Lines changed: 345 additions & 0 deletions
Large diffs are not rendered by default.

app/v3/page.tsx

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
'use client';
2+
3+
import React, { useState, useEffect } from 'react';
4+
5+
// --- SVG ICONS COMPONENTS ---
6+
7+
const IconFloppy = () => (
8+
<svg viewBox="0 0 24 24" className="w-10 h-10 fill-current text-black">
9+
<path d="M2 2h20v20H2V2zm2 2v6h16V4H4zm4 2h8v2H8V6zm-4 14h16v-8H4v8zm2-2h12v-4H6v4z" />
10+
</svg>
11+
);
12+
13+
const IconFolder = () => (
14+
<svg viewBox="0 0 24 24" className="w-12 h-10 fill-none stroke-black stroke-2">
15+
<path d="M2 4h6l2 2h12v14H2V4z" />
16+
<line x1="2" y1="8" x2="22" y2="8" />
17+
</svg>
18+
);
19+
20+
const IconTrash = () => (
21+
<svg viewBox="0 0 24 24" className="w-10 h-12 fill-none stroke-black stroke-2">
22+
<path d="M4 6h16M8 6V4h8v2M6 6v14h12V6" />
23+
<line x1="10" y1="10" x2="10" y2="16" />
24+
<line x1="14" y1="10" x2="14" y2="16" />
25+
<line x1="6" y1="9" x2="18" y2="9" />
26+
</svg>
27+
);
28+
29+
const IconHardDisk = () => (
30+
<svg viewBox="0 0 24 24" className="w-10 h-10 fill-none stroke-black stroke-2">
31+
<rect x="2" y="4" width="20" height="12" />
32+
<line x1="2" y1="10" x2="22" y2="10" />
33+
<rect x="4" y="12" width="4" height="2" fill="black" />
34+
<path d="M2 16h20l-2 4H4l-2-4z" />
35+
</svg>
36+
);
37+
38+
const IconBee = () => (
39+
<svg viewBox="0 0 24 24" className="w-8 h-8 fill-black">
40+
<path d="M4 8h4v2H4zm6 0h4v2h-4zm6 0h2v4h-2v-2h-2v2h-4v-2H8v2H6v-2H4v6h16V8z" />
41+
<path d="M6 12h2v2H6zm4 0h4v2h-4zm6 0h2v2h-2z" fill="white" />
42+
</svg>
43+
);
44+
45+
// --- UI COMPONENTS ---
46+
47+
const DitherPattern = () => (
48+
<div className="w-full h-full opacity-30" style={{
49+
backgroundImage: 'radial-gradient(black 1px, transparent 1px)',
50+
backgroundSize: '3px 3px'
51+
}}></div>
52+
);
53+
54+
const WindowFrame = ({ title, children, className, width, height, zIndex = 10 }: any) => (
55+
<div
56+
className={`absolute bg-white border-2 border-black shadow-[4px_4px_0px_rgba(0,0,0,0.2)] ${className}`}
57+
style={{ width, height, zIndex }}
58+
>
59+
{/* Title Bar */}
60+
<div className="h-6 border-b-2 border-black flex justify-between items-center bg-white">
61+
<div className="w-6 h-6 border-r-2 border-black flex items-center justify-center relative bg-white">
62+
<div className="absolute inset-0 p-1"><div className="w-full h-full border border-black bg-white relative"><div className="absolute top-0 left-0 w-full h-0.5 bg-black"></div></div></div>
63+
</div>
64+
<div className="flex-1 h-full flex items-center justify-center relative overflow-hidden">
65+
<DitherPattern />
66+
<span className="bg-white px-2 relative z-10 font-bold text-lg tracking-widest uppercase">{title}</span>
67+
</div>
68+
<div className="w-6 h-6 border-l-2 border-black flex items-center justify-center">
69+
<div className="w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-b-[8px] border-b-black"></div>
70+
</div>
71+
</div>
72+
73+
{/* Content Area */}
74+
<div className="relative h-[calc(100%-24px)] w-full overflow-hidden">
75+
{children}
76+
77+
{/* Scroll Bars (Decoration) */}
78+
<div className="absolute right-0 top-0 w-6 h-full border-l-2 border-black flex flex-col justify-between bg-white">
79+
<div className="h-6 border-b-2 border-black flex items-center justify-center">
80+
<div className="w-0 h-0 border-l-[4px] border-l-transparent border-r-[4px] border-r-transparent border-b-[6px] border-b-black"></div>
81+
</div>
82+
<div className="flex-1 relative"><DitherPattern /></div>
83+
<div className="h-6 border-t-2 border-black flex items-center justify-center">
84+
<div className="w-0 h-0 border-l-[4px] border-l-transparent border-r-[4px] border-r-transparent border-t-[6px] border-t-black"></div>
85+
</div>
86+
</div>
87+
88+
<div className="absolute bottom-0 left-0 w-full h-6 border-t-2 border-black flex justify-between bg-white">
89+
<div className="w-6 border-r-2 border-black flex items-center justify-center">
90+
<div className="w-0 h-0 border-t-[4px] border-t-transparent border-b-[4px] border-b-transparent border-r-[6px] border-r-black"></div>
91+
</div>
92+
<div className="flex-1 relative"><DitherPattern /></div>
93+
<div className="w-6 border-l-2 border-black bg-black"></div> {/* Corner box */}
94+
</div>
95+
</div>
96+
</div>
97+
);
98+
99+
// --- CLOCK LOGIC ---
100+
101+
const AnalogClock = () => {
102+
const [time, setTime] = useState(new Date());
103+
const [mounted, setMounted] = useState(false);
104+
105+
useEffect(() => {
106+
setMounted(true);
107+
const timer = setInterval(() => setTime(new Date()), 1000);
108+
return () => clearInterval(timer);
109+
}, []);
110+
111+
if (!mounted) return null; // Prevent hydration mismatch
112+
113+
const seconds = time.getSeconds();
114+
const minutes = time.getMinutes();
115+
const hours = time.getHours();
116+
117+
const secondDeg = seconds * 6;
118+
const minuteDeg = minutes * 6 + seconds * 0.1;
119+
const hourDeg = (hours % 12) * 30 + minutes * 0.5;
120+
121+
return (
122+
<div className="relative w-full h-full bg-white overflow-hidden">
123+
{/* Grid Background */}
124+
<div className="absolute inset-0" style={{
125+
backgroundImage: `linear-gradient(black 1px, transparent 1px), linear-gradient(90deg, black 1px, transparent 1px)`,
126+
backgroundSize: '20px 20px',
127+
opacity: 0.15
128+
}}></div>
129+
130+
{/* Clock Face Numbers */}
131+
<div className="absolute inset-0 rounded-full m-4 border-2 border-black bg-white shadow-sm flex items-center justify-center">
132+
{[...Array(12)].map((_, i) => {
133+
const num = i + 1;
134+
const rotation = num * 30;
135+
// Simple math to position numbers in a circle
136+
const angle = (rotation - 90) * (Math.PI / 180);
137+
const radius = 42; // percentage
138+
const x = 50 + radius * Math.cos(angle);
139+
const y = 50 + radius * Math.sin(angle);
140+
141+
return (
142+
<div key={num} className="absolute font-bold text-xl" style={{ left: `${x}%`, top: `${y}%`, transform: 'translate(-50%, -50%)' }}>
143+
{num}
144+
</div>
145+
);
146+
})}
147+
148+
{/* Center Nut */}
149+
<div className="absolute w-4 h-4 bg-yellow-600 rounded-full border border-black z-50 shadow-md"></div>
150+
151+
{/* Hands */}
152+
{/* Hour */}
153+
<div className="absolute bg-black z-20 rounded-full origin-bottom"
154+
style={{
155+
width: '6px',
156+
height: '25%',
157+
bottom: '50%',
158+
left: 'calc(50% - 3px)',
159+
transform: `rotate(${hourDeg}deg)`
160+
}}>
161+
<div className="absolute -top-2 -left-1 w-0 h-0 border-l-[5px] border-l-transparent border-r-[5px] border-r-transparent border-b-[10px] border-b-black"></div>
162+
</div>
163+
164+
{/* Minute */}
165+
<div className="absolute bg-black z-30 rounded-full origin-bottom"
166+
style={{
167+
width: '4px',
168+
height: '35%',
169+
bottom: '50%',
170+
left: 'calc(50% - 2px)',
171+
transform: `rotate(${minuteDeg}deg)`
172+
}}>
173+
<div className="absolute -top-2 -left-1 w-0 h-0 border-l-[4px] border-l-transparent border-r-[4px] border-r-transparent border-b-[8px] border-b-black"></div>
174+
</div>
175+
176+
{/* Second */}
177+
<div className="absolute bg-black z-40 origin-bottom"
178+
style={{
179+
width: '1px',
180+
height: '40%',
181+
bottom: '50%',
182+
left: '50%',
183+
transform: `rotate(${secondDeg}deg)`
184+
}}></div>
185+
</div>
186+
</div>
187+
);
188+
};
189+
190+
// --- MAIN PAGE ---
191+
192+
export default function RetroDesktop() {
193+
return (
194+
<div className="min-h-screen flex items-center justify-center bg-neutral-100 p-4">
195+
{/* The Main "Board" / Monitor */}
196+
<div className="relative w-[500px] h-[700px] bg-[#2fa046] border border-black shadow-2xl overflow-hidden select-none font-vt323 text-black">
197+
198+
{/* Top Menu Bar */}
199+
<div className="absolute top-0 left-0 right-0 h-8 bg-white border-b-2 border-black flex items-center px-2 z-50 text-xl">
200+
<div className="mr-6 font-bold cursor-pointer hover:underline">Desk</div>
201+
<div className="bg-black text-white px-3 h-full flex items-center cursor-pointer relative group">
202+
File
203+
{/* Dropdown Menu */}
204+
<div className="absolute top-8 left-0 bg-white border-2 border-black text-black w-64 shadow-[4px_4px_0px_rgba(0,0,0,0.5)] p-1 leading-tight">
205+
<div className="px-4 py-1 hover:bg-black hover:text-white cursor-pointer">Open</div>
206+
<div className="px-4 py-1 hover:bg-black hover:text-white cursor-pointer font-bold">Show Info...</div>
207+
<div className="h-[1px] bg-black my-1 border-b border-black border-dotted"></div>
208+
<div className="px-4 py-1 hover:bg-black hover:text-white cursor-pointer font-bold">New Folder...</div>
209+
<div className="px-4 py-1 hover:bg-black hover:text-white cursor-pointer">Close</div>
210+
<div className="px-4 py-1 hover:bg-black hover:text-white cursor-pointer font-bold">Close Window</div>
211+
<div className="h-[1px] bg-black my-1 border-b border-black border-dotted"></div>
212+
<div className="px-4 py-1 text-gray-400 pointer-events-none">Format...</div>
213+
</div>
214+
</div>
215+
<div className="ml-6 cursor-pointer hover:underline">View</div>
216+
<div className="ml-6 cursor-pointer hover:underline">Options</div>
217+
</div>
218+
219+
{/* Desktop Icons */}
220+
<div className="absolute top-16 left-4 flex flex-col items-center gap-1">
221+
<IconFloppy />
222+
<span className="bg-white px-1 text-sm border border-black">FLOPPY A</span>
223+
</div>
224+
225+
<div className="absolute top-40 left-4 flex flex-col items-center gap-1">
226+
<IconFloppy />
227+
<span className="bg-black text-white px-1 text-sm">FLOPPY DISK</span>
228+
</div>
229+
230+
<div className="absolute bottom-4 left-4 flex flex-col items-center gap-1 z-0">
231+
<IconTrash />
232+
<span className="bg-white px-1 text-sm border border-black">TRASH</span>
233+
</div>
234+
235+
<div className="absolute bottom-4 right-4 flex flex-col items-center gap-1 z-0">
236+
<IconHardDisk />
237+
<span className="bg-white px-1 text-sm border border-black">HARD DISK</span>
238+
</div>
239+
240+
<div className="absolute top-1/2 left-16 z-0 opacity-50">
241+
<IconBee />
242+
</div>
243+
244+
{/* Background Window (Files) */}
245+
<WindowFrame
246+
title=""
247+
className="top-28 right-[-20px]"
248+
width="300px"
249+
height="200px"
250+
zIndex={5}
251+
>
252+
<div className="p-4 flex justify-around pt-8">
253+
<div className="flex flex-col items-center">
254+
<IconFolder />
255+
<span className="mt-1 uppercase">Space</span>
256+
</div>
257+
<div className="flex flex-col items-center">
258+
<IconFolder />
259+
<span className="mt-1 uppercase">Time</span>
260+
</div>
261+
</div>
262+
<div className="absolute top-0 left-0 w-full h-6 border-b border-black bg-white px-2">
263+
{/* Fake Path */}
264+
C:\...
265+
</div>
266+
</WindowFrame>
267+
268+
{/* Main Clock Window */}
269+
<WindowFrame
270+
title="CLOCK"
271+
className="top-56 left-1/2 -translate-x-1/2"
272+
width="280px"
273+
height="280px"
274+
zIndex={20}
275+
>
276+
<div className="w-[calc(100%-24px)] h-[calc(100%-24px)] relative">
277+
<AnalogClock />
278+
</div>
279+
</WindowFrame>
280+
281+
{/* Tic Tac Toe Window */}
282+
<WindowFrame
283+
title="by" // Trying to match the clipped title in the image
284+
className="bottom-20 left-2"
285+
width="240px"
286+
height="180px"
287+
zIndex={15}
288+
>
289+
<div className="flex h-full w-[calc(100%-24px)]">
290+
{/* Game Board */}
291+
<div className="w-2/3 border-r-2 border-black flex items-center justify-center p-2 relative">
292+
{/* Grid Lines */}
293+
<div className="absolute w-full h-2 bg-black top-1/3"></div>
294+
<div className="absolute w-full h-2 bg-black top-2/3"></div>
295+
<div className="absolute h-full w-2 bg-black left-1/3"></div>
296+
<div className="absolute h-full w-2 bg-black left-2/3"></div>
297+
298+
{/* X and 0 placeholder */}
299+
<span className="absolute top-[10%] left-[60%] font-bold text-2xl">0</span>
300+
<span className="absolute top-[45%] left-[45%] font-bold text-2xl">X</span>
301+
</div>
302+
303+
{/* Sidebar Buttons */}
304+
<div className="w-1/3 flex flex-col bg-[#c41c1c] p-1 gap-1 border-l-2 border-black relative">
305+
<DitherPattern />
306+
{['Start', 'Setup', 'Score', 'Info'].map((text) => (
307+
<div key={text} className="bg-white border-2 border-black text-center text-sm cursor-pointer hover:bg-gray-200 z-10 relative shadow-sm">
308+
{text}
309+
</div>
310+
))}
311+
</div>
312+
</div>
313+
</WindowFrame>
314+
315+
</div>
316+
</div>
317+
);
318+
}

0 commit comments

Comments
 (0)