新增: 星座运势+AI资讯+知识卡片+桌面设置窗口+秒显示开关
- 星座运势: 天聚数行API集成,5维进度条+幸运标签+今日概述 - AI资讯: 天聚数行API,图文布局5条展示,文件缓存2小时刷新 - 知识卡片: AI生成,关键字+提示词配置,30分钟刷新 - 桌面设置: 独立WebView2窗口,760x1350,含壁纸/布局/城市/颜色等配置 - 显示控制: 壁纸/时间/天气/星座/知识/AI资讯独立开关,秒显示开关 - 文件缓存: 星座运势+AI资讯缓存到本地,启动即显示上次数据 - initDone防抖: 防止设置窗口初始化触发卡片重载
This commit is contained in:
401
web/overlay.html
401
web/overlay.html
@@ -22,7 +22,6 @@ html, body {
|
||||
border-radius: 20px;
|
||||
padding: 24px 28px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 0 0 1px rgba(255,255,255,0.08);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.time {
|
||||
@@ -34,6 +33,20 @@ html, body {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
@keyframes hourlyGlow {
|
||||
0% { text-shadow: 0 0 30px rgba(255,255,255,1), 0 0 80px rgba(180,200,255,0.8), 0 0 120px rgba(100,150,255,0.5); color: #fff; }
|
||||
8% { text-shadow: 0 0 50px rgba(255,255,255,1), 0 0 100px rgba(180,200,255,1), 0 0 160px rgba(100,150,255,0.6); }
|
||||
18% { text-shadow: 0 0 20px rgba(255,255,255,0.6), 0 0 40px rgba(180,200,255,0.3); }
|
||||
28% { text-shadow: 0 0 35px rgba(255,255,255,0.85), 0 0 70px rgba(180,200,255,0.6), 0 0 100px rgba(100,150,255,0.35); }
|
||||
38% { text-shadow: 0 0 15px rgba(255,255,255,0.4), 0 0 30px rgba(180,200,255,0.2); }
|
||||
48% { text-shadow: 0 0 25px rgba(255,255,255,0.6), 0 0 50px rgba(180,200,255,0.4); }
|
||||
60% { text-shadow: 0 0 10px rgba(255,255,255,0.25), 0 0 20px rgba(180,200,255,0.12); }
|
||||
75% { text-shadow: 0 0 15px rgba(255,255,255,0.35), 0 0 30px rgba(180,200,255,0.2); }
|
||||
100% { text-shadow: 0 2px 20px rgba(0,0,0,0.5); color: #fff; }
|
||||
}
|
||||
.time.hourly-glow {
|
||||
animation: hourlyGlow 6s ease-out forwards;
|
||||
}
|
||||
.date {
|
||||
font-size: 14px;
|
||||
color: rgba(255,255,255,0.7);
|
||||
@@ -101,6 +114,152 @@ html, body {
|
||||
line-height: 1.6;
|
||||
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
.zodiac-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.zodiac-date {
|
||||
font-size: 11px;
|
||||
opacity: 0.4;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.zodiac-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 7px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.zodiac-bar-label {
|
||||
width: 28px;
|
||||
opacity: 0.55;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.zodiac-bar-track {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.zodiac-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
.zodiac-bar-val {
|
||||
width: 30px;
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.zodiac-tags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.zodiac-tag {
|
||||
font-size: 10px;
|
||||
background: rgba(255,255,255,0.06);
|
||||
padding: 2px 8px;
|
||||
border-radius: 5px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.zodiac-summary {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ===== AI 资讯 ===== */
|
||||
.ainews-header {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: rgba(255,255,255,0.45);
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ainews-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.ainews-item:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
.ainews-img {
|
||||
width: 80px;
|
||||
height: 54px;
|
||||
border-radius: 6px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.ainews-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.ainews-title-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
.ainews-title {
|
||||
font-size: 13px;
|
||||
color: rgba(255,255,255,0.9);
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.ainews-source {
|
||||
font-size: 10px;
|
||||
opacity: 0.4;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ainews-desc {
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ===== 知识卡片 ===== */
|
||||
.knowledge-header {
|
||||
font-size: 11px;
|
||||
color: rgba(255,255,255,0.45);
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.knowledge-keyword-tag {
|
||||
background: rgba(255,255,255,0.08);
|
||||
padding: 1px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.knowledge-content {
|
||||
font-size: 14px;
|
||||
color: rgba(255,255,255,0.9);
|
||||
line-height: 1.7;
|
||||
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
@@ -131,7 +290,8 @@ body.layout-single #info {
|
||||
top: 40px;
|
||||
right: 40px;
|
||||
text-align: left;
|
||||
min-width: 320px;
|
||||
width: calc(50vw - 60px);
|
||||
z-index: 10;
|
||||
}
|
||||
body.layout-single #info .date {
|
||||
text-align: left;
|
||||
@@ -147,20 +307,65 @@ body.layout-multi #card-time {
|
||||
right: 40px;
|
||||
text-align: left;
|
||||
min-width: 280px;
|
||||
z-index: 10;
|
||||
}
|
||||
body.layout-multi #card-weather {
|
||||
body.layout-multi #card-bottom {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
right: 40px;
|
||||
text-align: right;
|
||||
min-width: 420px;
|
||||
width: calc(50vw - 60px);
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-end;
|
||||
z-index: 10;
|
||||
}
|
||||
body.layout-multi #card-zodiac {
|
||||
body.layout-multi #card-right-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
body.layout-multi #card-right-col #card-knowledge {
|
||||
text-align: left;
|
||||
}
|
||||
body.layout-multi #card-right-col #card-ainews {
|
||||
text-align: left;
|
||||
}
|
||||
body.layout-multi #card-bottom #card-weather {
|
||||
text-align: right;
|
||||
}
|
||||
body.layout-multi #card-ainews {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 40px;
|
||||
min-width: 200px;
|
||||
width: calc(50vw - 80px);
|
||||
z-index: 10;
|
||||
}
|
||||
body.layout-multi #card-zodiac {
|
||||
position: fixed;
|
||||
top: 200px;
|
||||
right: 40px;
|
||||
width: 280px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* ===== 卡片隐藏 ===== */
|
||||
body.hide-time #card-time,
|
||||
body.hide-time #info .time,
|
||||
body.hide-time #info .date { display: none !important; }
|
||||
body.hide-weather #card-weather,
|
||||
body.hide-weather #info .weather-section,
|
||||
body.hide-weather #info .current-weather,
|
||||
body.hide-weather #info .forecast-title,
|
||||
body.hide-weather #info .weather-forecast,
|
||||
body.hide-weather #info .daily-forecast { display: none !important; }
|
||||
body.hide-zodiac #card-zodiac,
|
||||
body.hide-zodiac #info .zodiac-text { display: none !important; }
|
||||
body.hide-ainews #card-ainews,
|
||||
body.hide-ainews #info .ainews-section { display: none !important; }
|
||||
body.hide-knowledge #card-knowledge,
|
||||
body.hide-knowledge #info .knowledge-section { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="layout-{{LAYOUT}}">
|
||||
@@ -172,6 +377,16 @@ body.layout-multi #card-zodiac {
|
||||
<div class="date" id="date">1月1日 周一</div>
|
||||
<div class="time" id="time">00:00</div>
|
||||
<div class="divider"></div>
|
||||
<div class="ainews-section">
|
||||
<div class="ainews-header">🤖 AI 资讯</div>
|
||||
<div id="ainews">加载中...</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="knowledge-section">
|
||||
<div class="knowledge-header">💡 知识卡片 <span class="knowledge-keyword-tag" id="knowledgeTag"></span></div>
|
||||
<div class="knowledge-content" id="knowledge">请设置知识关键字</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div>
|
||||
<div class="current-weather" id="currentWeather">加载中...</div>
|
||||
<div class="forecast-title">24小时预报</div>
|
||||
@@ -190,38 +405,53 @@ body.layout-multi #card-zodiac {
|
||||
<div class="date" id="date2">1月1日 周一</div>
|
||||
<div class="time" id="time2">00:00</div>
|
||||
</div>
|
||||
<div id="card-weather" class="card">
|
||||
<div class="current-weather" id="currentWeather2">加载中...</div>
|
||||
<div class="forecast-title">24小时预报</div>
|
||||
<div class="weather-forecast" id="hourlyForecast2"></div>
|
||||
<div class="forecast-title" style="margin-top:12px">7日预报</div>
|
||||
<div class="daily-forecast" id="dailyForecast2"></div>
|
||||
</div>
|
||||
<div id="card-zodiac" class="card">
|
||||
<div class="zodiac-text" id="zodiac2">加载中...</div>
|
||||
</div>
|
||||
<div id="card-ainews" class="card">
|
||||
<div class="ainews-header">🤖 AI 资讯</div>
|
||||
<div id="ainews2">加载中...</div>
|
||||
</div>
|
||||
<div id="card-bottom">
|
||||
<div id="card-right-col">
|
||||
<div id="card-knowledge" class="card">
|
||||
<div class="knowledge-header">💡 知识卡片 <span class="knowledge-keyword-tag" id="knowledgeTag2"></span></div>
|
||||
<div class="knowledge-content" id="knowledge2">请设置知识关键字</div>
|
||||
</div>
|
||||
<div id="card-weather" class="card">
|
||||
<div class="current-weather" id="currentWeather2">加载中...</div>
|
||||
<div class="forecast-title">24小时预报</div>
|
||||
<div class="weather-forecast" id="hourlyForecast2"></div>
|
||||
<div class="forecast-title" style="margin-top:12px">7日预报</div>
|
||||
<div class="daily-forecast" id="dailyForecast2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="author">绝尘</div>
|
||||
|
||||
<script>
|
||||
var lastTimeStr='', lastDateStr='', lastZodiac='';
|
||||
var horoscopeInfo=null;
|
||||
|
||||
var zodiacData = {
|
||||
'白羊座':{icon:'♈',date:'3.21-4.19',fortune:'今日运势旺盛,适合开展新计划。'},
|
||||
'金牛座':{icon:'♉',date:'4.20-5.20',fortune:'财运不错,但需注意健康。'},
|
||||
'双子座':{icon:'♊',date:'5.21-6.21',fortune:'人际关系活跃,社交运势佳。'},
|
||||
'巨蟹座':{icon:'♋',date:'6.22-7.22',fortune:'情绪敏感,适合独处思考。'},
|
||||
'狮子座':{icon:'♌',date:'7.23-8.22',fortune:'自信爆棚,工作表现突出。'},
|
||||
'处女座':{icon:'♍',date:'8.23-9.22',fortune:'细节决定成败,专注当下。'},
|
||||
'天秤座':{icon:'♎',date:'9.23-10.23',fortune:'感情运佳,单身者有机会。'},
|
||||
'天蝎座':{icon:'♏',date:'10.24-11.22',fortune:'直觉敏锐,适合做决策。'},
|
||||
'射手座':{icon:'♐',date:'11.23-12.21',fortune:'冒险精神旺盛,出行注意安全。'},
|
||||
'摩羯座':{icon:'♑',date:'12.22-1.19',fortune:'事业运佳,工作效率高。'},
|
||||
'水瓶座':{icon:'♒',date:'1.20-2.18',fortune:'创新思维活跃,灵感不断。'},
|
||||
'双鱼座':{icon:'♓',date:'2.19-3.20',fortune:'艺术灵感丰富,适合创作。'}
|
||||
'白羊座':{icon:'♈',date:'3.21-4.19'},
|
||||
'金牛座':{icon:'♉',date:'4.20-5.20'},
|
||||
'双子座':{icon:'♊',date:'5.21-6.21'},
|
||||
'巨蟹座':{icon:'♋',date:'6.22-7.22'},
|
||||
'狮子座':{icon:'♌',date:'7.23-8.22'},
|
||||
'处女座':{icon:'♍',date:'8.23-9.22'},
|
||||
'天秤座':{icon:'♎',date:'9.23-10.23'},
|
||||
'天蝎座':{icon:'♏',date:'10.24-11.22'},
|
||||
'射手座':{icon:'♐',date:'11.23-12.21'},
|
||||
'摩羯座':{icon:'♑',date:'12.22-1.19'},
|
||||
'水瓶座':{icon:'♒',date:'1.20-2.18'},
|
||||
'双鱼座':{icon:'♓',date:'2.19-3.20'}
|
||||
};
|
||||
|
||||
var barColors={all:'#e0e0e0',love:'#ff6b9d',work:'#4fc3f7',money:'#ffd54f',health:'#81c784'};
|
||||
|
||||
function getUserZodiac(){ return window.userZodiac||'射手座'; }
|
||||
|
||||
function setEl(id,html){
|
||||
@@ -233,14 +463,52 @@ function setText(id,txt){
|
||||
if(e) e.textContent=txt;
|
||||
}
|
||||
|
||||
function buildBar(label,val,color){
|
||||
var v=parseInt(val)||0;
|
||||
return '<div class="zodiac-bar">'+
|
||||
'<span class="zodiac-bar-label">'+label+'</span>'+
|
||||
'<div class="zodiac-bar-track"><div class="zodiac-bar-fill" style="width:'+v+'%;background:'+color+'"></div></div>'+
|
||||
'<span class="zodiac-bar-val">'+val+'%</span></div>';
|
||||
}
|
||||
|
||||
function buildZodiacHTML(name){
|
||||
var z=zodiacData[name]||{icon:'✨',date:''};
|
||||
var html='<div class="zodiac-title">'+z.icon+' '+name+'运势</div>';
|
||||
html+='<div class="zodiac-date">'+z.date+'</div>';
|
||||
if(horoscopeInfo&&horoscopeInfo.zodiac===name){
|
||||
html+=buildBar('综合',horoscopeInfo.all,barColors.all);
|
||||
html+=buildBar('爱情',horoscopeInfo.love,barColors.love);
|
||||
html+=buildBar('工作',horoscopeInfo.work,barColors.work);
|
||||
html+=buildBar('财运',horoscopeInfo.money,barColors.money);
|
||||
html+=buildBar('健康',horoscopeInfo.health,barColors.health);
|
||||
html+='<div class="zodiac-tags">';
|
||||
if(horoscopeInfo.luckyColor) html+='<span class="zodiac-tag">🎨 '+horoscopeInfo.luckyColor+'</span>';
|
||||
if(horoscopeInfo.luckyNum) html+='<span class="zodiac-tag">🔢 '+horoscopeInfo.luckyNum+'</span>';
|
||||
if(horoscopeInfo.noble) html+='<span class="zodiac-tag">⭐ '+horoscopeInfo.noble+'</span>';
|
||||
html+='</div>';
|
||||
if(horoscopeInfo.summary) html+='<div class="zodiac-summary">'+horoscopeInfo.summary+'</div>';
|
||||
} else {
|
||||
html+='<div style="opacity:0.4;font-size:12px;margin-top:8px">运势加载中...</div>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function syncZodiacHeight(){
|
||||
var zw=document.getElementById('card-zodiac');
|
||||
var ww=document.getElementById('card-weather');
|
||||
if(!zw||!ww||!document.body.classList.contains('layout-multi')) return;
|
||||
var wh=ww.getBoundingClientRect().height;
|
||||
zw.style.minHeight=wh+'px';
|
||||
}
|
||||
|
||||
function updateZodiacDisplay(){
|
||||
var name=getUserZodiac();
|
||||
if(name===lastZodiac) return;
|
||||
lastZodiac=name;
|
||||
var z=zodiacData[name]||{icon:'✨',date:'',fortune:'运势平稳,保持平常心。'};
|
||||
var html=z.icon+' '+name+'运势 <span style="opacity:0.4;font-size:12px">'+z.date+'</span><br><span style="opacity:0.6;font-size:12px">'+z.fortune+'</span>';
|
||||
var html=buildZodiacHTML(name);
|
||||
setEl('zodiac',html);
|
||||
setEl('zodiac2',html);
|
||||
syncZodiacHeight();
|
||||
}
|
||||
|
||||
var holidays=[
|
||||
@@ -266,12 +534,29 @@ function getNextHoliday(now){
|
||||
var target=new Date(y,h.m-1,h.d);
|
||||
var diff=Math.ceil((target-now)/(1000*60*60*24));
|
||||
if(diff>0&&diff<=60) results.push({diff:diff,name:h.name});
|
||||
if(diff<0){target=new Date(y+1,h.m-1,h.d);diff=Math.ceil((target-now)/(1000*60*60*24));if(diff>0&&diff<=60)results.push({diff:diff,name:h.name});}
|
||||
if(diff<0){
|
||||
target=new Date(y+1,h.m-1,h.d);
|
||||
diff=Math.ceil((target-now)/(1000*60*60*24));
|
||||
if(diff>0&&diff<=60) results.push({diff:diff,name:h.name});
|
||||
}
|
||||
}
|
||||
results.sort(function(a,b){return a.diff-b.diff;});
|
||||
return results.length>0?'距'+results[0].name+'还有'+results[0].diff+'天':'';
|
||||
}
|
||||
|
||||
window.setCardVisible=function(card,visible){
|
||||
if(visible){document.body.classList.remove('hide-'+card);}
|
||||
else{document.body.classList.add('hide-'+card);}
|
||||
};
|
||||
|
||||
window.setWallpaperVisible=function(visible){
|
||||
var bg=document.getElementById('bg-layer');
|
||||
if(bg){bg.style.display=visible?'':'none';}
|
||||
};
|
||||
|
||||
window._showSeconds={{SHOW_SECONDS}};
|
||||
window.setShowSeconds=function(v){window._showSeconds=v; updateTime();};
|
||||
|
||||
function updateTime(){
|
||||
var now=new Date();
|
||||
var hh=String(now.getHours()).padStart(2,'0');
|
||||
@@ -280,10 +565,21 @@ function updateTime(){
|
||||
var month=now.getMonth()+1;
|
||||
var day=now.getDate();
|
||||
var week=['周日','周一','周二','周三','周四','周五','周六'][now.getDay()];
|
||||
var timeStr=hh+':'+mm+':'+ss;
|
||||
var timeStr=window._showSeconds?(hh+':'+mm+':'+ss):(hh+':'+mm);
|
||||
var dateStr=month+'月'+day+'日 '+week;
|
||||
if(timeStr!==lastTimeStr){
|
||||
setText('time',timeStr); setText('time2',timeStr); lastTimeStr=timeStr;
|
||||
if(mm==='00'&&ss==='00'){
|
||||
['time','time2'].forEach(function(id){
|
||||
var el=document.getElementById(id);
|
||||
if(el){
|
||||
el.classList.remove('hourly-glow');
|
||||
void el.offsetWidth;
|
||||
el.classList.add('hourly-glow');
|
||||
el.addEventListener('animationend',function(){ el.classList.remove('hourly-glow'); },{once:true});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if(dateStr!==lastDateStr){
|
||||
var holiday=getNextHoliday(now);
|
||||
@@ -293,6 +589,50 @@ function updateTime(){
|
||||
updateZodiacDisplay();
|
||||
}
|
||||
|
||||
window.updateHoroscopeFromGo=function(data){
|
||||
console.log('[horoscope] received:', typeof data, JSON.stringify(data).substring(0,100));
|
||||
if(typeof data==='string') data=JSON.parse(data);
|
||||
horoscopeInfo=data;
|
||||
lastZodiac='';
|
||||
window.userZodiac=data.zodiac;
|
||||
updateZodiacDisplay();
|
||||
syncZodiacHeight();
|
||||
};
|
||||
|
||||
window.updateAINewsFromGo=function(items){
|
||||
if(typeof items==='string') items=JSON.parse(items);
|
||||
if(!items||!items.length) return;
|
||||
var html='';
|
||||
var count=Math.min(items.length,5);
|
||||
for(var i=0;i<count;i++){
|
||||
var n=items[i];
|
||||
var time=n.ctime||'';
|
||||
if(time.length>10) time=time.substring(5,10);
|
||||
html+='<div class="ainews-item">';
|
||||
if(n.picUrl){
|
||||
html+='<img class="ainews-img" src="'+n.picUrl+'" loading="lazy" onerror="this.style.display=\'none\'">';
|
||||
}
|
||||
html+='<div class="ainews-body">';
|
||||
html+='<div class="ainews-title-row"><span class="ainews-title">'+n.title+'</span><span class="ainews-source">'+n.source+' · '+time+'</span></div>';
|
||||
if(n.description) html+='<div class="ainews-desc">'+n.description+'</div>';
|
||||
html+='</div></div>';
|
||||
}
|
||||
setEl('ainews',html);
|
||||
setEl('ainews2',html);
|
||||
};
|
||||
|
||||
window.updateKnowledgeFromGo=function(data){
|
||||
if(typeof data==='string') data=JSON.parse(data);
|
||||
var ids=['knowledge','knowledge2'];
|
||||
var tagIds=['knowledgeTag','knowledgeTag2'];
|
||||
ids.forEach(function(id,i){
|
||||
var el=document.getElementById(id);
|
||||
if(el) el.textContent=data.content||'';
|
||||
var tag=document.getElementById(tagIds[i]);
|
||||
if(tag) tag.textContent=data.keyword?'#'+data.keyword:'';
|
||||
});
|
||||
};
|
||||
|
||||
window.updateWeatherFromGo=function(data){
|
||||
if(typeof data==='string') data=JSON.parse(data);
|
||||
|
||||
@@ -318,6 +658,7 @@ window.updateWeatherFromGo=function(data){
|
||||
|
||||
renderWeather('currentWeather','hourlyForecast','dailyForecast');
|
||||
renderWeather('currentWeather2','hourlyForecast2','dailyForecast2');
|
||||
syncZodiacHeight();
|
||||
};
|
||||
|
||||
updateTime();
|
||||
|
||||
699
web/settings.html
Normal file
699
web/settings.html
Normal file
@@ -0,0 +1,699 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f0f1a;
|
||||
--card-bg: rgba(255,255,255,0.04);
|
||||
--card-border: rgba(255,255,255,0.06);
|
||||
--card-divider: rgba(255,255,255,0.04);
|
||||
--text: #e0e0e0;
|
||||
--text-strong: #fff;
|
||||
--text-weak: rgba(255,255,255,0.25);
|
||||
--text-muted: rgba(255,255,255,0.5);
|
||||
--input-bg: rgba(255,255,255,0.08);
|
||||
--input-border: rgba(255,255,255,0.1);
|
||||
--input-border-focus: #4f8cff;
|
||||
--accent: #4f8cff;
|
||||
--toggle-track: rgba(255,255,255,0.1);
|
||||
--toggle-thumb: rgba(255,255,255,0.5);
|
||||
--option-bg: #1a1a2e;
|
||||
--footer-color: rgba(255,255,255,0.3);
|
||||
}
|
||||
.light {
|
||||
--bg: #f5f5f5;
|
||||
--card-bg: rgba(0,0,0,0.03);
|
||||
--card-border: rgba(0,0,0,0.08);
|
||||
--card-divider: rgba(0,0,0,0.05);
|
||||
--text: #333;
|
||||
--text-strong: #111;
|
||||
--text-weak: rgba(0,0,0,0.35);
|
||||
--text-muted: rgba(0,0,0,0.55);
|
||||
--input-bg: rgba(0,0,0,0.04);
|
||||
--input-border: rgba(0,0,0,0.12);
|
||||
--input-border-focus: #2b6cb0;
|
||||
--accent: #2b6cb0;
|
||||
--toggle-track: rgba(0,0,0,0.12);
|
||||
--toggle-thumb: rgba(0,0,0,0.45);
|
||||
--option-bg: #fff;
|
||||
--footer-color: rgba(0,0,0,0.35);
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 16px 18px;
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-y: auto;
|
||||
}
|
||||
::-webkit-scrollbar { width: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 3px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
||||
.light ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.12); }
|
||||
.light ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.22); }
|
||||
.header { margin-bottom: 14px; }
|
||||
.header h1 { font-size: 16px; font-weight: 600; color: var(--text-strong); }
|
||||
.header p { font-size: 11px; color: var(--text-weak); margin-top: 2px; }
|
||||
.section { margin-bottom: 12px; }
|
||||
.section-label {
|
||||
font-size: 10px; font-weight: 600; color: var(--text-weak);
|
||||
text-transform: uppercase; letter-spacing: 1.5px;
|
||||
margin-bottom: 4px; padding-left: 2px;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.item {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
.item + .item { border-top: 1px solid var(--card-divider); }
|
||||
.item-label { font-size: 12px; font-weight: 500; color: var(--text-muted); }
|
||||
.item-desc { font-size: 10px; color: var(--text-weak); margin-top: 1px; }
|
||||
.item-sub { padding-left: 32px; }
|
||||
|
||||
/* Toggle */
|
||||
.switch { position: relative; width: 36px; height: 20px; flex-shrink: 0; }
|
||||
.switch input { display: none; }
|
||||
.switch .track {
|
||||
position: absolute; inset: 0;
|
||||
background: var(--toggle-track);
|
||||
border-radius: 10px; cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.switch .thumb {
|
||||
position: absolute; width: 14px; height: 14px; top: 3px; left: 3px;
|
||||
background: var(--toggle-thumb);
|
||||
border-radius: 50%; transition: all 0.2s; pointer-events: none;
|
||||
}
|
||||
.switch input:checked + .track { background: var(--accent); }
|
||||
.switch input:checked + .track .thumb { transform: translateX(16px); background: var(--text-strong); }
|
||||
|
||||
/* Select */
|
||||
select {
|
||||
background: var(--input-bg); border: 1px solid var(--input-border);
|
||||
border-radius: 6px; color: var(--text); font-size: 11px; padding: 3px 6px;
|
||||
font-family: inherit; cursor: pointer; outline: none;
|
||||
min-width: 80px; max-width: 160px;
|
||||
}
|
||||
select:focus { border-color: var(--input-border-focus); }
|
||||
select option { background: var(--option-bg); color: var(--text); }
|
||||
|
||||
/* Button */
|
||||
.btn {
|
||||
background: var(--input-bg); border: 1px solid var(--input-border);
|
||||
border-radius: 6px; color: var(--text); font-size: 11px; padding: 4px 10px;
|
||||
font-family: inherit; cursor: pointer; transition: background 0.15s; white-space: nowrap;
|
||||
}
|
||||
.btn:hover { background: var(--card-border); }
|
||||
.btn:active { background: var(--input-bg); }
|
||||
.btn.active { background: var(--accent); border-color: var(--accent); color: var(--text-strong); }
|
||||
.btn-sm { padding: 3px 8px; font-size: 10px; }
|
||||
.btn-group { display: flex; gap: 4px; }
|
||||
.radio-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.bing-nav { display: flex; gap: 4px; align-items: center; }
|
||||
|
||||
.wp-type-section { display: none; }
|
||||
.wp-type-section.visible { display: block; }
|
||||
|
||||
input[type="text"] {
|
||||
background: var(--input-bg); border: 1px solid var(--input-border);
|
||||
border-radius: 6px; color: var(--text); font-size: 11px; padding: 4px 8px;
|
||||
font-family: inherit; outline: none; width: 140px;
|
||||
}
|
||||
input[type="text"]:focus { border-color: var(--input-border-focus); }
|
||||
|
||||
/* City Picker */
|
||||
.city-picker {
|
||||
position: relative;
|
||||
background: var(--input-bg); border: 1px solid var(--input-border);
|
||||
border-radius: 6px; padding: 4px 24px 4px 8px; cursor: pointer;
|
||||
font-size: 11px; min-width: 140px; max-width: 180px;
|
||||
}
|
||||
.city-picker:focus, .city-picker.open { border-color: var(--input-border-focus); }
|
||||
.city-picker-text { color: var(--text); }
|
||||
.city-picker-arrow {
|
||||
position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
|
||||
color: var(--text-weak); font-size: 10px; pointer-events: none;
|
||||
}
|
||||
.city-panel {
|
||||
display: none; position: absolute; right: -1px; bottom: calc(100% + 4px);
|
||||
background: var(--option-bg); border: 1px solid var(--input-border);
|
||||
border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.25);
|
||||
z-index: 1000; width: 260px; height: 200px; overflow: hidden;
|
||||
}
|
||||
.city-picker.open .city-panel { display: flex; }
|
||||
.city-col {
|
||||
flex: 1; overflow-y: auto; border-right: 1px solid var(--card-divider);
|
||||
}
|
||||
.city-col:last-child { border-right: none; }
|
||||
.city-col div {
|
||||
padding: 5px 10px; font-size: 11px; color: var(--text); cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.city-col div:hover { background: var(--input-bg); }
|
||||
.city-col div.active { color: var(--accent); font-weight: 600; }
|
||||
|
||||
/* Saved colors */
|
||||
.color-swatch {
|
||||
width: 28px; height: 28px; border-radius: 6px; cursor: pointer;
|
||||
border: 2px solid transparent; transition: border-color 0.15s; position: relative;
|
||||
}
|
||||
.color-swatch:hover { border-color: var(--accent); }
|
||||
.color-swatch .del {
|
||||
display: none; position: absolute; top: -6px; right: -6px;
|
||||
width: 14px; height: 14px; border-radius: 50%;
|
||||
background: #e53e3e; color: #fff; font-size: 9px; line-height: 14px;
|
||||
text-align: center; cursor: pointer;
|
||||
}
|
||||
.color-swatch:hover .del { display: block; }
|
||||
|
||||
.footer {
|
||||
text-align: center; font-size: 11px; color: var(--footer-color); margin-top: 12px;
|
||||
padding: 6px 0; letter-spacing: 0.5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>桌面设置</h1>
|
||||
<p>壁纸 · 布局 · 信息显示</p>
|
||||
</div>
|
||||
|
||||
<!-- 显示控制 -->
|
||||
<div class="section">
|
||||
<div class="section-label">显示控制</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div><div class="item-label">显示壁纸</div></div>
|
||||
<label class="switch"><input type="checkbox" id="wallpaper" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">时间日期</div></div>
|
||||
<label class="switch"><input type="checkbox" id="time" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
<div class="item item-sub">
|
||||
<div><div class="item-label">显示秒</div></div>
|
||||
<label class="switch"><input type="checkbox" id="showSeconds" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">天气信息</div></div>
|
||||
<label class="switch"><input type="checkbox" id="weather" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">星座运势</div></div>
|
||||
<label class="switch"><input type="checkbox" id="zodiacCard" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">知识卡片</div></div>
|
||||
<label class="switch"><input type="checkbox" id="knowledgeCard" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">AI 资讯</div></div>
|
||||
<label class="switch"><input type="checkbox" id="ainewsCard" checked><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 壁纸类型 -->
|
||||
<div class="section">
|
||||
<div class="section-label">壁纸类型</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div class="item-label">类型选择</div>
|
||||
<div class="radio-tabs" id="wpTypeTabs">
|
||||
<button class="btn" data-type="theme">主题</button>
|
||||
<button class="btn" data-type="image">本地图片</button>
|
||||
<button class="btn" data-type="bing">Bing</button>
|
||||
<button class="btn" data-type="color">纯色/渐变</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主题选择 -->
|
||||
<div class="section wp-type-section" id="sec-theme">
|
||||
<div class="section-label">壁纸主题</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div class="item-label">选择主题</div>
|
||||
<select id="themeSelect"></select>
|
||||
</div>
|
||||
<div class="item" id="textInputRow" style="display:none">
|
||||
<div class="item-label">自定义文字</div>
|
||||
<input type="text" id="wallpaperText" placeholder="输入显示文字">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本地图片 -->
|
||||
<div class="section wp-type-section" id="sec-image">
|
||||
<div class="section-label">本地图片</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div class="item-desc" id="imagePathDisplay">未选择图片</div>
|
||||
<button class="btn" id="btnPickImage">选择图片</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bing -->
|
||||
<div class="section wp-type-section" id="sec-bing">
|
||||
<div class="section-label">Bing 每日壁纸</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex:1;min-width:0">
|
||||
<img id="bingPreview" style="width:64px;height:40px;object-fit:cover;border-radius:4px;flex-shrink:0;display:none">
|
||||
<div style="min-width:0;overflow:hidden">
|
||||
<div class="item-label" id="bingCopyright" style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Bing 每日壁纸</div>
|
||||
<div class="item-desc" id="bingIdx"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bing-nav" style="flex-shrink:0">
|
||||
<button class="btn btn-sm" id="btnBingPrev">◀</button>
|
||||
<button class="btn btn-sm" id="btnBingNext">▶</button>
|
||||
<button class="btn btn-sm" id="btnBingFav">☆</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">定时切换</div><div class="item-desc">每小时自动切换壁纸</div></div>
|
||||
<label class="switch"><input type="checkbox" id="bingAutoRefresh"><span class="track"><span class="thumb"></span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="bingFavSection" style="display:none;margin-top:6px">
|
||||
<div class="section-label">收藏列表</div>
|
||||
<div class="card" id="bingFavList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 纯色/渐变 -->
|
||||
<div class="section wp-type-section" id="sec-color">
|
||||
<div class="section-label">纯色 / 渐变</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div class="item-label">选择颜色</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn" id="btnSolidColor">纯色</button>
|
||||
<button class="btn" id="btnGradientColor">渐变</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" id="currentColorRow" style="display:none">
|
||||
<div class="item-label">当前颜色</div>
|
||||
<div style="display:flex;align-items:center;gap:6px">
|
||||
<span id="currentColorPreview" style="display:inline-block;width:24px;height:16px;border-radius:3px;border:1px solid var(--input-border)"></span>
|
||||
<button class="btn btn-sm" id="btnSaveColor">收藏</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="savedColorsSection" style="display:none;margin-top:6px">
|
||||
<div class="card" id="savedColorsGrid" style="padding:8px 12px;display:flex;flex-wrap:wrap;gap:6px">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 布局 -->
|
||||
<div class="section">
|
||||
<div class="section-label">布局</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div class="item-label">信息布局</div>
|
||||
<select id="layout">
|
||||
<option value="single">合并卡片</option>
|
||||
<option value="multi">独立卡片</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 星座 + 城市 -->
|
||||
<div class="section">
|
||||
<div class="section-label">个性化</div>
|
||||
<div class="card">
|
||||
<div class="item">
|
||||
<div class="item-label">我的星座</div>
|
||||
<select id="zodiacSelect">
|
||||
<option>白羊座</option><option>金牛座</option><option>双子座</option>
|
||||
<option>巨蟹座</option><option>狮子座</option><option>处女座</option>
|
||||
<option>天秤座</option><option>天蝎座</option><option>射手座</option>
|
||||
<option>摩羯座</option><option>水瓶座</option><option>双鱼座</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">知识关键字</div><div class="item-desc">AI 将根据关键字生成知识小卡片</div></div>
|
||||
<input type="text" id="knowledgeKeyword" placeholder="如: 历史、科学、冷知识">
|
||||
</div>
|
||||
<div class="item">
|
||||
<div><div class="item-label">知识提示词</div><div class="item-desc">自定义生成风格,不会显示在桌面</div></div>
|
||||
<input type="text" id="knowledgePrompt" placeholder="如: 用幽默口吻、面向程序员">
|
||||
</div>
|
||||
<div class="item" style="position:relative">
|
||||
<div class="item-label">天气城市</div>
|
||||
<div class="city-picker" id="cityPicker" tabindex="0">
|
||||
<span class="city-picker-text" id="cityPickerText">未选择</span>
|
||||
<span class="city-picker-arrow">▾</span>
|
||||
<div class="city-panel" id="cityPanel">
|
||||
<div class="city-col" id="provCol"></div>
|
||||
<div class="city-col" id="cityCol"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">u-desktop v1.0</div>
|
||||
|
||||
<script>
|
||||
var toggleKeys = ['wallpaper', 'time', 'showSeconds', 'weather', 'zodiacCard', 'knowledgeCard', 'ainewsCard'];
|
||||
var initDone = false;
|
||||
function sendToggle() {
|
||||
if (!initDone) return;
|
||||
var data = {};
|
||||
toggleKeys.forEach(function(k) { data[k] = document.getElementById(k).checked; });
|
||||
if (window.saveToggles) window.saveToggles(JSON.stringify(data));
|
||||
}
|
||||
toggleKeys.forEach(function(k) {
|
||||
document.getElementById(k).addEventListener('change', sendToggle);
|
||||
});
|
||||
document.getElementById('layout').addEventListener('change', function() {
|
||||
if (window.saveLayout) window.saveLayout(this.value);
|
||||
});
|
||||
document.getElementById('zodiacSelect').addEventListener('change', function() {
|
||||
if (window.saveZodiac) window.saveZodiac(this.value);
|
||||
});
|
||||
|
||||
var kwTimer = null;
|
||||
document.getElementById('knowledgeKeyword').addEventListener('input', function() {
|
||||
clearTimeout(kwTimer);
|
||||
var val = this.value;
|
||||
kwTimer = setTimeout(function() {
|
||||
if (window.saveKnowledgeKeyword) window.saveKnowledgeKeyword(val);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
var kpTimer = null;
|
||||
document.getElementById('knowledgePrompt').addEventListener('input', function() {
|
||||
clearTimeout(kpTimer);
|
||||
var val = this.value;
|
||||
kpTimer = setTimeout(function() {
|
||||
if (window.saveKnowledgePrompt) window.saveKnowledgePrompt(val);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// City picker
|
||||
var cityMap = {};
|
||||
var selectedCityId = '';
|
||||
var selectedCityName = '';
|
||||
var picker = document.getElementById('cityPicker');
|
||||
var pickerText = document.getElementById('cityPickerText');
|
||||
var provCol = document.getElementById('provCol');
|
||||
var cityCol = document.getElementById('cityCol');
|
||||
var activeProv = '';
|
||||
|
||||
function renderProvinces(provinces) {
|
||||
provCol.innerHTML = '';
|
||||
provinces.forEach(function(p) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = p;
|
||||
if (p === activeProv) div.className = 'active';
|
||||
div.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
activeProv = p;
|
||||
renderProvinces(provinces);
|
||||
renderCities(p);
|
||||
});
|
||||
provCol.appendChild(div);
|
||||
});
|
||||
}
|
||||
function renderCities(prov) {
|
||||
cityCol.innerHTML = '';
|
||||
var list = cityMap[prov] || [];
|
||||
list.forEach(function(c) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = c.name;
|
||||
if (c.id === selectedCityId) div.className = 'active';
|
||||
div.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
selectedCityId = c.id;
|
||||
selectedCityName = c.name;
|
||||
pickerText.textContent = activeProv + ' · ' + c.name;
|
||||
picker.classList.remove('open');
|
||||
renderCities(prov);
|
||||
if (window.saveCity) window.saveCity(c.id);
|
||||
});
|
||||
cityCol.appendChild(div);
|
||||
});
|
||||
}
|
||||
picker.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
this.classList.toggle('open');
|
||||
this.focus();
|
||||
});
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!picker.contains(e.target)) picker.classList.remove('open');
|
||||
});
|
||||
|
||||
document.getElementById('themeSelect').addEventListener('change', function() {
|
||||
if (window.saveWallpaperType) window.saveWallpaperType('theme', this.value);
|
||||
document.getElementById('textInputRow').style.display = this.value === 'text' ? 'flex' : 'none';
|
||||
});
|
||||
|
||||
var textTimer = null;
|
||||
document.getElementById('wallpaperText').addEventListener('input', function() {
|
||||
clearTimeout(textTimer);
|
||||
var val = this.value;
|
||||
textTimer = setTimeout(function() {
|
||||
if (window.saveWallpaperText) window.saveWallpaperText(val);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
var wpTypeTabs = document.querySelectorAll('#wpTypeTabs .btn');
|
||||
var currentWpType = '';
|
||||
function setWpType(type) {
|
||||
wpTypeTabs.forEach(function(b) { b.classList.toggle('active', b.dataset.type === type); });
|
||||
document.querySelectorAll('.wp-type-section').forEach(function(s) { s.classList.remove('visible'); });
|
||||
var sec = document.getElementById('sec-' + type);
|
||||
if (sec) sec.classList.add('visible');
|
||||
if (type === 'bing' && currentWpType !== 'bing') {
|
||||
if (window.enableBing) window.enableBing();
|
||||
}
|
||||
currentWpType = type;
|
||||
}
|
||||
wpTypeTabs.forEach(function(b) {
|
||||
b.addEventListener('click', function() { setWpType(b.dataset.type); });
|
||||
});
|
||||
|
||||
document.getElementById('btnPickImage').addEventListener('click', function() {
|
||||
if (!window.pickLocalImage) return;
|
||||
window.pickLocalImage().then(function(path) {
|
||||
if (path) document.getElementById('imagePathDisplay').textContent = path;
|
||||
});
|
||||
});
|
||||
function updateBingUI(stateJson) {
|
||||
if (!stateJson) return;
|
||||
var s = JSON.parse(stateJson);
|
||||
document.getElementById('btnBingFav').textContent = s.label;
|
||||
document.getElementById('bingCopyright').textContent = s.copyright || 'Bing 每日壁纸';
|
||||
document.getElementById('bingIdx').textContent = (s.total > 0) ? ((s.idx + 1) + ' / ' + s.total) : '';
|
||||
// load preview thumbnail
|
||||
if (s.filename && window.bingThumbDataURI) {
|
||||
var preview = document.getElementById('bingPreview');
|
||||
window.bingThumbDataURI(s.filename).then(function(uri) {
|
||||
if (uri) { preview.src = uri; preview.style.display = 'block'; }
|
||||
});
|
||||
}
|
||||
}
|
||||
function loadBingFavorites() {
|
||||
if (!window.getBingFavorites) return;
|
||||
window.getBingFavorites().then(function(json) {
|
||||
var favs = JSON.parse(json);
|
||||
var sec = document.getElementById('bingFavSection');
|
||||
var list = document.getElementById('bingFavList');
|
||||
list.innerHTML = '';
|
||||
if (!favs || favs.length === 0) { sec.style.display = 'none'; return; }
|
||||
sec.style.display = 'block';
|
||||
list.style.cssText = 'padding:8px 12px;display:flex;flex-wrap:wrap;gap:6px';
|
||||
favs.forEach(function(f) {
|
||||
var img = document.createElement('img');
|
||||
img.style.cssText = 'width:64px;height:40px;object-fit:cover;border-radius:4px;cursor:pointer;border:2px solid transparent;transition:border-color 0.15s';
|
||||
img.title = f.copyright + ' (' + f.date + ')';
|
||||
img.addEventListener('click', function() {
|
||||
if (window.bingSetByIdx) window.bingSetByIdx(f.idx).then(function(s) { updateBingUI(s); });
|
||||
});
|
||||
img.addEventListener('mouseenter', function() { this.style.borderColor = 'var(--accent)'; });
|
||||
img.addEventListener('mouseleave', function() { this.style.borderColor = 'transparent'; });
|
||||
list.appendChild(img);
|
||||
if (window.bingThumbDataURI) {
|
||||
window.bingThumbDataURI(f.filename).then(function(uri) {
|
||||
if (uri) img.src = uri;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
document.getElementById('btnBingPrev').addEventListener('click', function() {
|
||||
if (window.bingNext) window.bingNext().then(function(s) { updateBingUI(s); });
|
||||
});
|
||||
document.getElementById('btnBingNext').addEventListener('click', function() {
|
||||
if (window.bingPrev) window.bingPrev().then(function(s) { updateBingUI(s); });
|
||||
});
|
||||
document.getElementById('btnBingFav').addEventListener('click', function() {
|
||||
if (window.bingToggleFavorite) {
|
||||
window.bingToggleFavorite().then(function(s) { updateBingUI(s); loadBingFavorites(); });
|
||||
}
|
||||
});
|
||||
document.getElementById('bingAutoRefresh').addEventListener('change', function() {
|
||||
if (window.saveBingAutoRefresh) window.saveBingAutoRefresh(this.checked);
|
||||
});
|
||||
document.getElementById('btnSolidColor').addEventListener('click', function() {
|
||||
if (window.pickSolidColor) {
|
||||
window.pickSolidColor().then(function(c) {
|
||||
if (c) { currentColor1 = c; currentColor2 = ''; currentGradient = false; updateColorPreview(); }
|
||||
});
|
||||
}
|
||||
});
|
||||
document.getElementById('btnGradientColor').addEventListener('click', function() {
|
||||
if (window.pickGradientColor) {
|
||||
window.pickGradientColor().then(function(res) {
|
||||
if (res) {
|
||||
var parts = res.split(',');
|
||||
currentColor1 = parts[0]; currentColor2 = parts[1] || ''; currentGradient = true;
|
||||
updateColorPreview();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Color favorites
|
||||
var currentColor1 = '', currentColor2 = '', currentGradient = false;
|
||||
var savedColors = [];
|
||||
|
||||
function updateColorPreview() {
|
||||
var row = document.getElementById('currentColorRow');
|
||||
var preview = document.getElementById('currentColorPreview');
|
||||
if (!currentColor1) { row.style.display = 'none'; return; }
|
||||
row.style.display = 'flex';
|
||||
if (currentGradient && currentColor2) {
|
||||
preview.style.background = 'linear-gradient(135deg,' + currentColor1 + ',' + currentColor2 + ')';
|
||||
} else {
|
||||
preview.style.background = currentColor1;
|
||||
}
|
||||
}
|
||||
|
||||
function renderSavedColors() {
|
||||
var sec = document.getElementById('savedColorsSection');
|
||||
var grid = document.getElementById('savedColorsGrid');
|
||||
grid.innerHTML = '';
|
||||
if (!savedColors || savedColors.length === 0) { sec.style.display = 'none'; return; }
|
||||
sec.style.display = 'block';
|
||||
savedColors.forEach(function(sc, idx) {
|
||||
var swatch = document.createElement('div');
|
||||
swatch.className = 'color-swatch';
|
||||
if (sc.gradient && sc.color2) {
|
||||
swatch.style.background = 'linear-gradient(135deg,' + sc.color1 + ',' + sc.color2 + ')';
|
||||
} else {
|
||||
swatch.style.background = sc.color1;
|
||||
}
|
||||
var del = document.createElement('span');
|
||||
del.className = 'del'; del.textContent = 'x';
|
||||
del.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (window.removeSavedColor) window.removeSavedColor(idx).then(function() {
|
||||
savedColors.splice(idx, 1);
|
||||
renderSavedColors();
|
||||
});
|
||||
});
|
||||
swatch.appendChild(del);
|
||||
swatch.addEventListener('click', function() {
|
||||
if (window.applySavedColor) window.applySavedColor(idx);
|
||||
currentColor1 = sc.color1; currentColor2 = sc.color2; currentGradient = sc.gradient;
|
||||
updateColorPreview();
|
||||
});
|
||||
grid.appendChild(swatch);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('btnSaveColor').addEventListener('click', function() {
|
||||
if (!currentColor1) return;
|
||||
if (window.addSavedColor) window.addSavedColor(currentColor1, currentColor2, currentGradient).then(function() {
|
||||
savedColors.push({color1: currentColor1, color2: currentColor2, gradient: currentGradient});
|
||||
renderSavedColors();
|
||||
});
|
||||
});
|
||||
|
||||
if (window.loadAllSettings) {
|
||||
window.loadAllSettings().then(function(raw) {
|
||||
var s = JSON.parse(raw);
|
||||
|
||||
// Apply system theme
|
||||
if (s.lightTheme) {
|
||||
document.documentElement.className = 'light';
|
||||
}
|
||||
|
||||
// Province-city cascade
|
||||
cityMap = s.citiesByProv || {};
|
||||
selectedCityId = s.city || '';
|
||||
activeProv = '';
|
||||
if (s.provinces && s.provinces.length) {
|
||||
for (var p in cityMap) {
|
||||
for (var ci = 0; ci < cityMap[p].length; ci++) {
|
||||
if (cityMap[p][ci].id === selectedCityId) { activeProv = p; selectedCityName = cityMap[p][ci].name; break; }
|
||||
}
|
||||
if (activeProv) break;
|
||||
}
|
||||
renderProvinces(s.provinces);
|
||||
if (activeProv) renderCities(activeProv);
|
||||
if (activeProv && selectedCityName) {
|
||||
pickerText.textContent = activeProv + ' · ' + selectedCityName;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.themes && s.themes.length) {
|
||||
var themeSel = document.getElementById('themeSelect');
|
||||
s.themes.forEach(function(t) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = t.value;
|
||||
opt.textContent = t.label;
|
||||
themeSel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
toggleKeys.forEach(function(k) {
|
||||
if (s[k] !== undefined) document.getElementById(k).checked = s[k];
|
||||
});
|
||||
if (s.layout) document.getElementById('layout').value = s.layout;
|
||||
if (s.zodiac) document.getElementById('zodiacSelect').value = s.zodiac;
|
||||
if (s.theme) document.getElementById('themeSelect').value = s.theme;
|
||||
if (s.wallpaperText) document.getElementById('wallpaperText').value = s.wallpaperText;
|
||||
if (s.imagePath) document.getElementById('imagePathDisplay').textContent = s.imagePath;
|
||||
if (s.knowledgeKeyword) document.getElementById('knowledgeKeyword').value = s.knowledgeKeyword;
|
||||
if (s.knowledgePrompt) document.getElementById('knowledgePrompt').value = s.knowledgePrompt;
|
||||
if (s.theme === 'text') document.getElementById('textInputRow').style.display = 'flex';
|
||||
// Color state
|
||||
if (s.color1) { currentColor1 = s.color1; currentColor2 = s.color2 || ''; currentGradient = s.colorGradient || false; }
|
||||
if (s.wallpaperType === 'color') updateColorPreview();
|
||||
savedColors = s.savedColors || [];
|
||||
renderSavedColors();
|
||||
setWpType(s.wallpaperType || 'theme');
|
||||
// Bing state
|
||||
if (s.wallpaperType === 'bing' && window.getBingInfo) {
|
||||
window.getBingInfo().then(function(si) { updateBingUI(si); });
|
||||
}
|
||||
if (s.wallpaperType === 'bing') loadBingFavorites();
|
||||
if (s.bingAutoRefresh !== undefined) document.getElementById('bingAutoRefresh').checked = s.bingAutoRefresh;
|
||||
// resize window to fit content
|
||||
setTimeout(function() {
|
||||
var el = document.documentElement;
|
||||
var cw = el.scrollWidth;
|
||||
var ch = el.scrollHeight + 8;
|
||||
if (window.resizeToFit) window.resizeToFit(cw, ch);
|
||||
}, 100);
|
||||
initDone = true;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
239
web/themes/fractal_src.html
Normal file
239
web/themes/fractal_src.html
Normal file
@@ -0,0 +1,239 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dynamic Wallpaper</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; }
|
||||
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
|
||||
canvas { display: block; width: 100vw; height: 100vh; }
|
||||
#info {
|
||||
position: fixed; top: 20px; right: 20px;
|
||||
color: rgba(255,255,255,0.6); font: 14px monospace;
|
||||
pointer-events: none; user-select: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<div id="info"></div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('c');
|
||||
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
||||
if (!gl) { document.body.innerHTML = '<h1 style="color:#fff;text-align:center;margin-top:40vh">WebGL 不可用</h1>'; }
|
||||
|
||||
let paused = false;
|
||||
let fullscreen = false;
|
||||
let mouseX = 0, mouseY = 0;
|
||||
let lastMove = 0;
|
||||
|
||||
// --- 状态控制 (Go Bridge) ---
|
||||
window.setPaused = function(v) { paused = v; };
|
||||
window.setFullscreen = function(v) { fullscreen = v; };
|
||||
|
||||
// --- 鼠标 ---
|
||||
document.addEventListener('mousemove', e => {
|
||||
mouseX = e.clientX / window.innerWidth;
|
||||
mouseY = 1.0 - e.clientY / window.innerHeight;
|
||||
lastMove = performance.now();
|
||||
});
|
||||
document.addEventListener('click', e => {
|
||||
// 点击涟漪效果 — 传给 shader
|
||||
clickX = e.clientX / window.innerWidth;
|
||||
clickY = 1.0 - e.clientY / window.innerHeight;
|
||||
clickTime = performance.now();
|
||||
});
|
||||
|
||||
let clickX = 0, clickY = 0, clickTime = 0;
|
||||
|
||||
// --- Shader ---
|
||||
const vertSrc = `
|
||||
attribute vec2 a_pos;
|
||||
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
|
||||
`;
|
||||
|
||||
// 极光 + 流体噪声 shader
|
||||
const fragSrc = `
|
||||
precision highp float;
|
||||
uniform float u_time;
|
||||
uniform vec2 u_resolution;
|
||||
uniform vec2 u_mouse;
|
||||
uniform float u_click;
|
||||
uniform vec2 u_clickPos;
|
||||
|
||||
// simplex-like noise
|
||||
vec3 mod289(vec3 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
|
||||
vec2 mod289(vec2 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
|
||||
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
|
||||
|
||||
float snoise(vec2 v) {
|
||||
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
|
||||
-0.577350269189626, 0.024390243902439);
|
||||
vec2 i = floor(v + dot(v, C.yy));
|
||||
vec2 x0 = v - i + dot(i, C.xx);
|
||||
vec2 i1;
|
||||
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||
vec4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
i = mod289(i);
|
||||
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
|
||||
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
||||
m = m*m; m = m*m;
|
||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||
vec3 h = abs(x) - 0.5;
|
||||
vec3 ox = floor(x + 0.5);
|
||||
vec3 a0 = x - ox;
|
||||
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
|
||||
vec3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / u_resolution;
|
||||
vec2 p = uv * 2.0 - 1.0;
|
||||
p.x *= u_resolution.x / u_resolution.y;
|
||||
|
||||
float t = u_time * 0.3;
|
||||
|
||||
// 鼠标影响
|
||||
vec2 mp = u_mouse * 2.0 - 1.0;
|
||||
mp.x *= u_resolution.x / u_resolution.y;
|
||||
float mouseDist = length(p - mp);
|
||||
float mouseInfluence = smoothstep(0.8, 0.0, mouseDist) * 0.3;
|
||||
|
||||
// 点击涟漪
|
||||
float ripple = 0.0;
|
||||
if (u_click > 0.0) {
|
||||
vec2 cp = u_clickPos * 2.0 - 1.0;
|
||||
cp.x *= u_resolution.x / u_resolution.y;
|
||||
float d = length(p - cp);
|
||||
ripple = sin(d * 30.0 - u_click * 8.0) * exp(-d * 3.0) * exp(-u_click * 2.0) * 0.15;
|
||||
}
|
||||
|
||||
// 多层噪声
|
||||
float n1 = snoise(p * 1.5 + vec2(t * 0.5, t * 0.3)) * 0.5 + 0.5;
|
||||
float n2 = snoise(p * 3.0 + vec2(-t * 0.7, t * 0.5)) * 0.5 + 0.5;
|
||||
float n3 = snoise(p * 0.8 + vec2(t * 0.2, -t * 0.4) + mouseInfluence) * 0.5 + 0.5;
|
||||
|
||||
// 极光色彩
|
||||
vec3 c1 = vec3(0.05, 0.2, 0.4); // 深蓝
|
||||
vec3 c2 = vec3(0.0, 0.8, 0.5); // 翠绿
|
||||
vec3 c3 = vec3(0.3, 0.1, 0.6); // 紫色
|
||||
vec3 c4 = vec3(0.1, 0.5, 0.9); // 天蓝
|
||||
|
||||
// 极光带
|
||||
float aurora = smoothstep(0.3, 0.7, n3) * smoothstep(0.8, 0.4, n1);
|
||||
vec3 auroraColor = mix(c2, c4, n2) * aurora * 1.2;
|
||||
|
||||
// 底层渐变
|
||||
vec3 bg = mix(c1, c3, uv.y * 0.5 + n1 * 0.3);
|
||||
bg += auroraColor;
|
||||
|
||||
// 星星效果
|
||||
float stars = pow(snoise(p * 20.0), 12.0) * 0.8;
|
||||
bg += vec3(stars);
|
||||
|
||||
// 涟漪叠加
|
||||
bg += vec3(ripple * 2.0, ripple * 3.0, ripple * 4.0);
|
||||
|
||||
// 鼠标光晕
|
||||
bg += vec3(0.1, 0.3, 0.5) * mouseInfluence;
|
||||
|
||||
// 轻微暗角
|
||||
float vignette = 1.0 - smoothstep(0.5, 1.5, length(p * 0.7));
|
||||
bg *= vignette * 0.9 + 0.1;
|
||||
|
||||
gl_FragColor = vec4(bg, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
// --- 编译 Shader ---
|
||||
function createShader(type, src) {
|
||||
const s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
||||
console.error('Shader error:', gl.getShaderInfoLog(s));
|
||||
return null;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
const vs = createShader(gl.VERTEX_SHADER, vertSrc);
|
||||
const fs = createShader(gl.FRAGMENT_SHADER, fragSrc);
|
||||
const prog = gl.createProgram();
|
||||
gl.attachShader(prog, vs);
|
||||
gl.attachShader(prog, fs);
|
||||
gl.linkProgram(prog);
|
||||
gl.useProgram(prog);
|
||||
|
||||
// 全屏四边形
|
||||
const buf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
|
||||
const aPos = gl.getAttribLocation(prog, 'a_pos');
|
||||
gl.enableVertexAttribArray(aPos);
|
||||
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Uniforms
|
||||
const uTime = gl.getUniformLocation(prog, 'u_time');
|
||||
const uRes = gl.getUniformLocation(prog, 'u_resolution');
|
||||
const uMouse = gl.getUniformLocation(prog, 'u_mouse');
|
||||
const uClick = gl.getUniformLocation(prog, 'u_click');
|
||||
const uClickPos = gl.getUniformLocation(prog, 'u_clickPos');
|
||||
|
||||
// --- 渲染循环 ---
|
||||
let lastFrame = 0;
|
||||
let targetFPS = 30;
|
||||
let currentFPS = 30;
|
||||
|
||||
function resize() {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = window.innerWidth * dpr;
|
||||
canvas.height = window.innerHeight * dpr;
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
function render(now) {
|
||||
requestAnimationFrame(render);
|
||||
if (paused || fullscreen) return;
|
||||
|
||||
// 帧率控制
|
||||
const interval = 1000 / targetFPS;
|
||||
if (now - lastFrame < interval) return;
|
||||
lastFrame = now;
|
||||
|
||||
// 自适应帧率:鼠标静止 5s 后降帧
|
||||
const idleTime = now - lastMove;
|
||||
if (idleTime > 5000) {
|
||||
targetFPS = 10;
|
||||
} else {
|
||||
targetFPS = 30;
|
||||
}
|
||||
|
||||
const t = now / 1000.0;
|
||||
const clickElapsed = clickTime > 0 ? (now - clickTime) / 1000.0 : 0.0;
|
||||
|
||||
gl.uniform1f(uTime, t);
|
||||
gl.uniform2f(uRes, canvas.width, canvas.height);
|
||||
gl.uniform2f(uMouse, mouseX, mouseY);
|
||||
gl.uniform1f(uClick, clickElapsed > 3.0 ? 0.0 : clickElapsed);
|
||||
gl.uniform2f(uClickPos, clickX, clickY);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
requestAnimationFrame(render);
|
||||
|
||||
// 通知 Go 层壁纸就绪
|
||||
if (window.wallpaperReady) {
|
||||
wallpaperReady().then(() => console.log('wallpaper embedded'));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user