初始提交

Win11 动态壁纸引擎:WebView2 + systray + 和风天气
- WebGL 极光背景动画
- 实时天气(24h/7d预报)
- 星座运势(托盘切换)
- 暂停/继续控制
- 单实例互斥锁防双开
- vendor systray 修复 ClickedCh 静默丢弃
This commit is contained in:
2026-05-25 19:03:21 +08:00
commit 196d59269d
11 changed files with 2031 additions and 0 deletions

356
wallpaper.html Normal file
View File

@@ -0,0 +1,356 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>动态壁纸</title>
<style>
* { margin: 0; padding: 0; }
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
#canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
#info {
position: fixed;
top: 50%;
right: 80px;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
border-radius: 16px;
padding: 24px 32px;
color: #ffffff;
font-family: "Microsoft YaHei", sans-serif;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
text-align: right;
min-width: 300px;
max-height: 90vh;
overflow-y: auto;
z-index: 10;
}
.time {
font-size: 64px;
font-weight: 300;
color: #ffffff;
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.8);
letter-spacing: -1px;
}
.date {
font-size: 15px;
color: rgba(255, 255, 255, 0.9);
margin-top: 6px;
font-weight: 400;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
}
.weather-section {
margin-top: 20px;
}
.current-weather {
font-size: 17px;
color: rgba(255, 255, 255, 0.95);
font-weight: 400;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
margin-bottom: 12px;
}
.forecast-title {
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 8px;
}
.weather-forecast {
display: flex;
gap: 6px;
overflow-x: auto;
padding-bottom: 8px;
justify-content: flex-end;
}
.forecast-item {
background: rgba(255, 255, 255, 0.08);
border-radius: 8px;
padding: 8px 10px;
text-align: center;
min-width: 55px;
font-size: 11px;
color: rgba(255, 255, 255, 0.85);
}
.forecast-time {
margin-bottom: 4px;
opacity: 0.8;
}
.forecast-temp {
font-weight: 500;
}
.daily-forecast {
display: flex;
gap: 6px;
overflow-x: auto;
padding-bottom: 8px;
justify-content: flex-end;
}
.daily-item {
background: rgba(255, 255, 255, 0.06);
border-radius: 6px;
padding: 6px 8px;
text-align: center;
min-width: 48px;
font-size: 11px;
color: rgba(255, 255, 255, 0.85);
}
.zodiac {
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.15);
font-size: 15px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.5;
cursor: default;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
}
.current-weather {
font-size: 17px;
color: rgba(255, 255, 255, 0.95);
font-weight: 400;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
margin-bottom: 12px;
}
.weather-forecast::-webkit-scrollbar,
#info::-webkit-scrollbar {
display: none;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="info">
<div class="time" id="time">00:00</div>
<div class="date" id="date">1月1日 周一</div>
<div class="weather-section">
<div class="current-weather" id="currentWeather">🌤️ 加载中...</div>
<div class="forecast-title">未来24小时</div>
<div class="weather-forecast" id="hourlyForecast"></div>
<div class="forecast-title" style="margin-top: 12px;">未来7天</div>
<div class="daily-forecast" id="dailyForecast"></div>
</div>
<div class="zodiac" id="zodiac">✨ 射手座运势</div>
</div>
<script>
console.log('=== 页面已加载 ===');
let lastTimeStr = '';
let lastDateStr = '';
let lastZodiac = '';
// 天气渲染:由 Go 层推送数据
window.updateWeatherFromGo = function(data) {
if (typeof data === 'string') data = JSON.parse(data);
const currentEl = document.getElementById('currentWeather');
if (currentEl && data.current) currentEl.textContent = data.current;
const forecastEl = document.getElementById('hourlyForecast');
if (forecastEl) {
if (data.hourly && data.hourly.length > 0) {
forecastEl.innerHTML = data.hourly.map(item =>
'<div class="forecast-item"><div class="forecast-time">' + item.time + '</div><div>' + item.icon + '</div><div class="forecast-temp">' + item.temp + '</div></div>'
).join('');
} else {
forecastEl.innerHTML = '<div style="font-size:11px; opacity:0.5;">暂无数据</div>';
}
}
const dailyEl = document.getElementById('dailyForecast');
if (dailyEl) {
if (data.daily && data.daily.length > 0) {
dailyEl.innerHTML = data.daily.map(item =>
'<div class="daily-item"><div style="opacity:0.8;margin-bottom:3px">' + item.date + '</div><div>' + item.icon + '</div><div class="forecast-temp">' + item.tempMin + '°~' + item.tempMax + '°</div></div>'
).join('');
} else {
dailyEl.innerHTML = '<div style="font-size:11px; opacity:0.5;">暂无数据</div>';
}
}
console.log('✅ 天气已更新:', data.current);
};
// 星座运势
const fortunes = {
'白羊座': '今日运势旺盛,适合开展新计划。',
'金牛座': '财运不错,但需注意健康。',
'双子座': '人际关系活跃,社交运势佳。',
'巨蟹座': '情绪敏感,适合独处思考。',
'狮子座': '自信爆棚,工作表现突出。',
'处女座': '细节决定成败,专注当下。',
'天秤座': '感情运佳,单身者有机会。',
'天蝎座': '直觉敏锐,适合做决策。',
'射手座': '冒险精神旺盛,出行注意安全。',
'摩羯座': '事业运佳,工作效率高。',
'水瓶座': '创新思维活跃,灵感不断。',
'双鱼座': '艺术灵感丰富,适合创作。'
};
// WebGL 极光背景
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
const vsSource = 'attribute vec4 aVertexPosition;void main(){gl_Position=aVertexPosition;}';
const fsSource = `
precision mediump float;
uniform float uTime;
uniform vec2 uResolution;
void main() {
vec2 uv=gl_FragCoord.xy/uResolution.xy;
vec3 c1=vec3(0.1,0.4,0.8);
vec3 c2=vec3(0.4,0.2,0.8);
vec3 c3=vec3(0.1,0.6,0.4);
float w1=sin(uv.x*3.0+uTime*0.5)*0.5+0.5;
float w2=sin(uv.x*4.0+uTime*0.3+uv.y*2.0)*0.5+0.5;
float w3=sin(uv.x*2.0+uTime*0.7+uv.y*3.0)*0.5+0.5;
float d=uv.y*2.0-1.0;
float g=exp(-d*d*2.0);
vec3 a=(c1*w1+c2*w2+c3*w3)*g*0.6;
vec3 b=mix(vec3(0.02,0.02,0.08),vec3(0.05,0.05,0.15),uv.y);
gl_FragColor=vec4(b+a,1.0);
}
`;
function loadShader(gl,type,source){
const s=gl.createShader(type);
gl.shaderSource(s,source);
gl.compileShader(s);
if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)){
console.error('Shader error:',gl.getShaderInfoLog(s));
gl.deleteShader(s);
return null;
}
return s;
}
const vs=loadShader(gl,gl.VERTEX_SHADER,vsSource);
const fs=loadShader(gl,gl.FRAGMENT_SHADER,fsSource);
const prog=gl.createProgram();
gl.attachShader(prog,vs);
gl.attachShader(prog,fs);
gl.linkProgram(prog);
if(!gl.getProgramParameter(prog,gl.LINK_STATUS)){
console.error('Program link error:',gl.getProgramInfoLog(prog));
}
const posLoc=gl.getAttribLocation(prog,'aVertexPosition');
const timeLoc=gl.getUniformLocation(prog,'uTime');
const resLoc=gl.getUniformLocation(prog,'uResolution');
const posBuf=gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,posBuf);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,1,1,1,-1,-1,1,-1]),gl.STATIC_DRAW);
let startTime=Date.now();
const FPS = 15;
let lastFrame = 0;
function render(ts){
requestAnimationFrame(render);
if (ts - lastFrame < 1000 / FPS) return;
lastFrame = ts;
gl.viewport(0,0,canvas.width,canvas.height);
gl.clearColor(0,0,0,1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(prog);
gl.enableVertexAttribArray(posLoc);
gl.bindBuffer(gl.ARRAY_BUFFER,posBuf);
gl.vertexAttribPointer(posLoc,2,gl.FLOAT,false,0,0);
gl.uniform1f(timeLoc,(Date.now()-startTime)/1000);
gl.uniform2f(resLoc,canvas.width,canvas.height);
gl.drawArrays(gl.TRIANGLE_STRIP,0,4);
}
requestAnimationFrame(render);
console.log('✅ WebGL 极光背景已启动');
// 时间和星座
function getUserZodiac() {
return window.userZodiac || '射手座';
}
function updateZodiacDisplay() {
var name = getUserZodiac();
if (name === lastZodiac) return;
lastZodiac = name;
var zodiacEl = document.getElementById('zodiac');
if (!zodiacEl) return;
var fortune = fortunes[name] || '运势平稳,保持平常心。';
zodiacEl.innerHTML = '✨ ' + name + '运势<br><small style="opacity:0.7">' + fortune + '</small>';
}
function updateTime() {
var now = new Date();
var hh = String(now.getHours()).padStart(2, '0');
var mm = String(now.getMinutes()).padStart(2, '0');
var month = now.getMonth() + 1;
var day = now.getDate();
var week = ['周日','周一','周二','周三','周四','周五','周六'][now.getDay()];
var timeEl = document.getElementById('time');
var dateEl = document.getElementById('date');
var timeStr = hh + ':' + mm;
var dateStr = month + '月' + day + '日 ' + week;
if (timeEl && timeStr !== lastTimeStr) {
timeEl.textContent = timeStr;
lastTimeStr = timeStr;
}
if (dateEl && dateStr !== lastDateStr) {
dateEl.textContent = dateStr;
lastDateStr = dateStr;
}
updateZodiacDisplay();
}
updateTime();
setInterval(updateTime, 1000);
console.log('=== 初始化完成 ===');
</script>
</body>
</html>