Skip to content

Commit 7634abc

Browse files
committed
add heart-dialog html
1 parent d1993c9 commit 7634abc

2 files changed

Lines changed: 319 additions & 1 deletion

File tree

source/g2h1a2tn5y.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

source/heart-dialog/index.html

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>❤ 爱心寄语</title>
7+
<style>
8+
* { margin: 0; padding: 0; box-sizing: border-box; }
9+
10+
body {
11+
background: #1e1e2e;
12+
width: 100vw;
13+
height: 100vh;
14+
overflow: hidden;
15+
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
16+
}
17+
18+
/* 仿系统弹窗卡片 */
19+
.popup {
20+
position: fixed;
21+
width: var(--popup-w, 148px);
22+
min-height: 60px;
23+
border-radius: 8px;
24+
box-shadow: 0 4px 18px rgba(0,0,0,0.35);
25+
opacity: 0;
26+
transform: scale(0.4);
27+
transition: opacity 0.25s ease, transform 0.25s ease;
28+
pointer-events: none;
29+
z-index: 10;
30+
}
31+
32+
.popup.visible {
33+
opacity: 1;
34+
transform: scale(1);
35+
}
36+
37+
.popup.fade-out {
38+
opacity: 0;
39+
transform: scale(0.6);
40+
transition: opacity 0.4s ease, transform 0.4s ease;
41+
}
42+
43+
.popup-titlebar {
44+
display: flex;
45+
align-items: center;
46+
justify-content: space-between;
47+
padding: 5px 8px;
48+
background: rgba(255,255,255,0.18);
49+
border-radius: 8px 8px 0 0;
50+
backdrop-filter: blur(2px);
51+
}
52+
53+
.popup-titlebar .dots {
54+
display: flex;
55+
gap: 4px;
56+
}
57+
58+
.popup-titlebar .dot {
59+
width: 8px;
60+
height: 8px;
61+
border-radius: 50%;
62+
background: rgba(255,255,255,0.45);
63+
}
64+
65+
.popup-titlebar .icons {
66+
font-size: 10px;
67+
color: rgba(255,255,255,0.6);
68+
letter-spacing: 3px;
69+
}
70+
71+
.popup-body {
72+
padding: clamp(6px, 1.5vw, 10px) clamp(6px, 2vw, 12px) clamp(8px, 2vw, 12px);
73+
font-size: clamp(11px, 3.8vw, 15px);
74+
font-weight: bold;
75+
color: #333;
76+
line-height: 1.4;
77+
text-align: center;
78+
}
79+
80+
/* 彩色背景 */
81+
.color-pink { background: #ffc2d4; }
82+
.color-green { background: #b7f5c4; }
83+
.color-blue { background: #b3dcf7; }
84+
.color-yellow { background: #fdf3b0; }
85+
.color-lilac { background: #dbbcf6; }
86+
</style>
87+
</head>
88+
<body>
89+
90+
<script>
91+
// ─────────────────────────────────────────────
92+
// ✏️ 各版本寄语文案(通过 URL ?type= 切换)
93+
// 可用值:friend(默认)| crush | lover
94+
// ─────────────────────────────────────────────
95+
const VERSIONS = {
96+
// 好朋友版:?type=friend 或 不带参数
97+
friend: {
98+
title: '❤ 爱心寄语',
99+
messages: [
100+
'好好吃饭',
101+
'天凉了多穿衣服',
102+
'多喝水呀',
103+
'好好爱自己',
104+
'别熬夜',
105+
'我想你了',
106+
'保持好心情',
107+
'顺顺利利',
108+
'早点休息',
109+
'开心每一天',
110+
'注意身体',
111+
'万事顺意',
112+
],
113+
},
114+
115+
// 暗恋版:?type=crush
116+
crush: {
117+
title: '💗 小小心意',
118+
messages: [
119+
'偷偷喜欢你',
120+
'你笑起来真好看',
121+
'见到你心跳加速',
122+
'你出现了就是晴天',
123+
'想牵你的手',
124+
'能认识你真好',
125+
'有你的地方才是家',
126+
'你是我的小秘密',
127+
'好想告诉你',
128+
'你知道吗我好喜欢你',
129+
'你是我最美的心动',
130+
'鼓起勇气',
131+
],
132+
},
133+
134+
// 恋人版:?type=lover
135+
lover: {
136+
title: '🌹 只对你说',
137+
messages: [
138+
'我爱你',
139+
'想你了',
140+
'你是我的宝贝',
141+
'天凉了多穿衣服',
142+
'好好吃饭',
143+
'你是我最好的选择',
144+
'陪你到老',
145+
'多喝水呀',
146+
'别熬夜',
147+
'你笑我才开心',
148+
'以后的路一起走',
149+
'你就是我的答案',
150+
],
151+
},
152+
};
153+
154+
// 读取 URL 参数,决定当前版本
155+
// 支持 crush / cursh(容错拼写)
156+
const rawType = new URLSearchParams(location.search).get('type') || 'friend';
157+
const urlType = rawType === 'cursh' ? 'crush' : rawType;
158+
const currentVersion = VERSIONS[urlType] || VERSIONS.friend;
159+
const MESSAGES = currentVersion.messages;
160+
document.title = currentVersion.title;
161+
// ─────────────────────────────────────────────
162+
163+
const COLORS = ['color-pink', 'color-green', 'color-blue', 'color-yellow', 'color-lilac'];
164+
165+
// ── 根据屏幕尺寸自适应参数 ──────────────────────
166+
const sw0 = window.innerWidth;
167+
const sh0 = window.innerHeight;
168+
const isMobile = Math.min(sw0, sh0) < 600;
169+
170+
// 阶段一:爱心上的弹窗数量
171+
const HEART_COUNT = isMobile ? 65 : 80;
172+
// 阶段三:额外铺屏弹窗数量
173+
const FLOOD_COUNT = isMobile ? 70 : 120;
174+
// 弹窗宽度:移动端更小,确保心形轮廓清晰可见
175+
const POPUP_W = isMobile ? Math.round(sw0 * 0.22) : 148;
176+
// 弹窗生成间隔 ms
177+
const HEART_INTERVAL = isMobile ? 35 : 40;
178+
const FLOOD_INTERVAL = isMobile ? 22 : 25;
179+
// 展示阶段时长 ms
180+
const SHOW_DURATION = 3000;
181+
// 淡出后重播延迟 ms
182+
const REPLAY_DELAY = 800;
183+
184+
let allPopups = [];
185+
186+
// ── 工具函数 ──────────────────────────────────
187+
188+
function rand(min, max) {
189+
return Math.random() * (max - min) + min;
190+
}
191+
192+
function randInt(min, max) {
193+
return Math.floor(rand(min, max));
194+
}
195+
196+
function pickMsg() {
197+
return MESSAGES[randInt(0, MESSAGES.length)];
198+
}
199+
200+
function pickColor() {
201+
return COLORS[randInt(0, COLORS.length)];
202+
}
203+
204+
function createPopup(x, y) {
205+
const el = document.createElement('div');
206+
el.className = `popup ${pickColor()}`;
207+
el.style.left = x + 'px';
208+
el.style.top = y + 'px';
209+
210+
el.innerHTML = `
211+
<div class="popup-titlebar">
212+
<div class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>
213+
<div class="icons">─ □ ×</div>
214+
</div>
215+
<div class="popup-body">${pickMsg()}</div>
216+
`;
217+
218+
document.body.appendChild(el);
219+
// 触发过渡动画
220+
requestAnimationFrame(() => requestAnimationFrame(() => el.classList.add('visible')));
221+
allPopups.push(el);
222+
return el;
223+
}
224+
225+
// ── 爱心坐标生成 ─────────────────────────────
226+
227+
function heartPoints(n) {
228+
const sw = window.innerWidth;
229+
const sh = window.innerHeight;
230+
const cx = sw / 2;
231+
const cy = sh / 2;
232+
233+
// 缩放系数:同时约束宽(32 单位)和高(30 单位),取最小值避免超出屏幕
234+
// 目标:宽占屏幕 85%,高占屏幕 80%,哪个轴先撑满就以哪个为准
235+
const scale = Math.min(sw * 0.85 / 32, sh * 0.80 / 30);
236+
// 移动端上竖屏空间多,稍微上移让心形视觉更居中
237+
const yBias = isMobile ? -sh * 0.04 : 0;
238+
const jitter = isMobile ? 4 : 8;
239+
const halfH = 30; // 弹窗高度约一半
240+
241+
const points = [];
242+
for (let i = 0; i < n; i++) {
243+
const t = (i / n) * 2 * Math.PI;
244+
const hx = 16 * Math.pow(Math.sin(t), 3);
245+
const hy = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
246+
points.push({
247+
x: cx + hx * scale - POPUP_W / 2 + rand(-jitter, jitter),
248+
y: cy + yBias + hy * scale - halfH + rand(-jitter, jitter),
249+
});
250+
}
251+
return points;
252+
}
253+
254+
// ── 主动画流程 ────────────────────────────────
255+
256+
function clearAll(cb) {
257+
allPopups.forEach(el => el.classList.add('fade-out'));
258+
setTimeout(() => {
259+
allPopups.forEach(el => el.remove());
260+
allPopups = [];
261+
cb && cb();
262+
}, 500);
263+
}
264+
265+
function phase1(onDone) {
266+
const points = heartPoints(HEART_COUNT);
267+
let i = 0;
268+
const timer = setInterval(() => {
269+
if (i >= points.length) {
270+
clearInterval(timer);
271+
onDone();
272+
return;
273+
}
274+
const p = points[i++];
275+
createPopup(p.x, p.y);
276+
}, HEART_INTERVAL);
277+
}
278+
279+
function phase2(onDone) {
280+
setTimeout(onDone, SHOW_DURATION);
281+
}
282+
283+
function phase3(onDone) {
284+
const sw = window.innerWidth;
285+
const sh = window.innerHeight;
286+
let i = 0;
287+
const timer = setInterval(() => {
288+
if (i >= FLOOD_COUNT) {
289+
clearInterval(timer);
290+
// 再等一小会儿展示满屏效果
291+
setTimeout(onDone, 1500);
292+
return;
293+
}
294+
i++;
295+
createPopup(
296+
rand(0, sw - POPUP_W),
297+
rand(0, sh - 70)
298+
);
299+
}, FLOOD_INTERVAL);
300+
}
301+
302+
function runAnimation() {
303+
phase1(() => {
304+
phase2(() => {
305+
phase3(() => {
306+
clearAll(() => {
307+
setTimeout(runAnimation, REPLAY_DELAY);
308+
});
309+
});
310+
});
311+
});
312+
}
313+
314+
// 启动:写入 CSS 变量供弹窗宽度使用
315+
document.documentElement.style.setProperty('--popup-w', POPUP_W + 'px');
316+
runAnimation();
317+
</script>
318+
</body>
319+
</html>

0 commit comments

Comments
 (0)