新增: Bing壁纸历史导航+极光流体主题+重启菜单
This commit is contained in:
210
bing.go
210
bing.go
@@ -2,25 +2,71 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bingAPI = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"
|
const bingAPI = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=zh-CN"
|
||||||
|
|
||||||
type bingResponse struct {
|
type bingResponse struct {
|
||||||
Images []struct {
|
Images []struct {
|
||||||
|
StartDate string `json:"startdate"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
URLBase string `json:"urlbase"`
|
URLBase string `json:"urlbase"`
|
||||||
Copyright string `json:"copyright"`
|
Copyright string `json:"copyright"`
|
||||||
} `json:"images"`
|
} `json:"images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchBingWallpaper() {
|
type BingRecord struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
URLBase string `json:"urlbase"`
|
||||||
|
Copyright string `json:"copyright"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Favorited bool `json:"favorited"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BingHistory struct {
|
||||||
|
Records []BingRecord `json:"records"`
|
||||||
|
CurrentIdx int `json:"currentIdx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var bingMu sync.Mutex
|
||||||
|
|
||||||
|
func bingDir() string {
|
||||||
|
return filepath.Join(configDir(), "bing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func bingHistoryPath() string {
|
||||||
|
return filepath.Join(configDir(), "bing_history.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBingHistory() *BingHistory {
|
||||||
|
data, err := os.ReadFile(bingHistoryPath())
|
||||||
|
if err != nil {
|
||||||
|
return &BingHistory{}
|
||||||
|
}
|
||||||
|
var h BingHistory
|
||||||
|
if json.Unmarshal(data, &h) != nil {
|
||||||
|
return &BingHistory{}
|
||||||
|
}
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveBingHistory(h *BingHistory) error {
|
||||||
|
data, _ := json.MarshalIndent(h, "", " ")
|
||||||
|
return os.WriteFile(bingHistoryPath(), data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchBingHistory() {
|
||||||
|
bingMu.Lock()
|
||||||
|
defer bingMu.Unlock()
|
||||||
|
|
||||||
resp, err := httpClient.Get(bingAPI)
|
resp, err := httpClient.Get(bingAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Bing API 请求失败:", err)
|
log.Println("Bing API 请求失败:", err)
|
||||||
@@ -37,38 +83,160 @@ func fetchBingWallpaper() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imgURL := br.Images[0].URL
|
os.MkdirAll(bingDir(), 0755)
|
||||||
if !strings.HasPrefix(imgURL, "http") {
|
|
||||||
imgURL = "https://www.bing.com" + imgURL
|
existing := loadBingHistory()
|
||||||
|
existingMap := make(map[string]BingRecord)
|
||||||
|
for _, r := range existing.Records {
|
||||||
|
existingMap[r.Date] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
imgResp, err := httpClient.Get(imgURL)
|
for _, img := range br.Images {
|
||||||
if err != nil {
|
date := img.StartDate
|
||||||
log.Println("Bing 图片下载失败:", err)
|
if date == "" {
|
||||||
return
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := existingMap[date]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
imgURL := img.URL
|
||||||
|
if !strings.HasPrefix(imgURL, "http") {
|
||||||
|
imgURL = "https://www.bing.com" + imgURL
|
||||||
|
}
|
||||||
|
|
||||||
|
imgResp, err := httpClient.Get(imgURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Bing 图片下载失败 (%s): %v", date, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
imgData, _ := io.ReadAll(imgResp.Body)
|
||||||
|
imgResp.Body.Close()
|
||||||
|
if len(imgData) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := date + ".jpg"
|
||||||
|
localPath := filepath.Join(bingDir(), filename)
|
||||||
|
if err := os.WriteFile(localPath, imgData, 0644); err != nil {
|
||||||
|
log.Printf("Bing 图片保存失败 (%s): %v", date, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("Bing 壁纸已下载: %s (%d bytes)", filename, len(imgData))
|
||||||
|
|
||||||
|
existingMap[date] = BingRecord{
|
||||||
|
Date: date,
|
||||||
|
URLBase: img.URLBase,
|
||||||
|
Copyright: img.Copyright,
|
||||||
|
Filename: filename,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer imgResp.Body.Close()
|
|
||||||
imgData, err := io.ReadAll(imgResp.Body)
|
var records []BingRecord
|
||||||
if err != nil {
|
for _, img := range br.Images {
|
||||||
|
if r, ok := existingMap[img.StartDate]; ok {
|
||||||
|
records = append(records, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(records) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bingPath := filepath.Join(configDir(), "bing_wallpaper.jpg")
|
history := &BingHistory{
|
||||||
if err := os.WriteFile(bingPath, imgData, 0644); err != nil {
|
Records: records,
|
||||||
log.Println("Bing 壁纸缓存失败:", err)
|
CurrentIdx: 0,
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Printf("Bing 壁纸已缓存: %s (%d bytes)", bingPath, len(imgData))
|
if existing.CurrentIdx < len(records) {
|
||||||
|
history.CurrentIdx = existing.CurrentIdx
|
||||||
|
}
|
||||||
|
saveBingHistory(history)
|
||||||
|
|
||||||
reloadWallpaper()
|
reloadWallpaper()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCurrentBingPath() string {
|
||||||
|
h := loadBingHistory()
|
||||||
|
if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(bingDir(), h.Records[h.CurrentIdx].Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentBingRecord() *BingRecord {
|
||||||
|
h := loadBingHistory()
|
||||||
|
if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &h.Records[h.CurrentIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func bingPrev() {
|
||||||
|
bingMu.Lock()
|
||||||
|
defer bingMu.Unlock()
|
||||||
|
|
||||||
|
h := loadBingHistory()
|
||||||
|
if len(h.Records) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.CurrentIdx < len(h.Records)-1 {
|
||||||
|
h.CurrentIdx++
|
||||||
|
saveBingHistory(h)
|
||||||
|
log.Printf("Bing 壁纸: 上一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date)
|
||||||
|
reloadWallpaper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bingNext() {
|
||||||
|
bingMu.Lock()
|
||||||
|
defer bingMu.Unlock()
|
||||||
|
|
||||||
|
h := loadBingHistory()
|
||||||
|
if h.CurrentIdx > 0 {
|
||||||
|
h.CurrentIdx--
|
||||||
|
saveBingHistory(h)
|
||||||
|
log.Printf("Bing 壁纸: 下一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date)
|
||||||
|
reloadWallpaper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bingToggleFavorite() string {
|
||||||
|
bingMu.Lock()
|
||||||
|
defer bingMu.Unlock()
|
||||||
|
|
||||||
|
h := loadBingHistory()
|
||||||
|
if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
r := &h.Records[h.CurrentIdx]
|
||||||
|
r.Favorited = !r.Favorited
|
||||||
|
saveBingHistory(h)
|
||||||
|
state := "收藏"
|
||||||
|
if r.Favorited {
|
||||||
|
state = "取消收藏"
|
||||||
|
}
|
||||||
|
log.Printf("Bing 壁纸: %s (date=%s)", state, r.Date)
|
||||||
|
if r.Favorited {
|
||||||
|
return "☆ 取消收藏"
|
||||||
|
}
|
||||||
|
return "★ 收藏当前壁纸"
|
||||||
|
}
|
||||||
|
|
||||||
|
func bingCopyrightInfo() string {
|
||||||
|
r := getCurrentBingRecord()
|
||||||
|
if r != nil {
|
||||||
|
return fmt.Sprintf("%s (%s)", r.Copyright, r.Date)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func bingWallpaperLoop() {
|
func bingWallpaperLoop() {
|
||||||
cfg := loadConfig()
|
cfg := loadConfig()
|
||||||
if cfg.WallpaperType == WPBing {
|
if cfg.WallpaperType == WPBing {
|
||||||
bingPath := filepath.Join(configDir(), "bing_wallpaper.jpg")
|
h := loadBingHistory()
|
||||||
if _, err := os.Stat(bingPath); err != nil {
|
if len(h.Records) == 0 {
|
||||||
fetchBingWallpaper()
|
fetchBingHistory()
|
||||||
|
} else {
|
||||||
|
reloadWallpaper()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +244,7 @@ func bingWallpaperLoop() {
|
|||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
cfg := loadConfig()
|
cfg := loadConfig()
|
||||||
if cfg.WallpaperType == WPBing {
|
if cfg.WallpaperType == WPBing {
|
||||||
fetchBingWallpaper()
|
fetchBingHistory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const (
|
|||||||
ThemeStar ThemeName = "starfield"
|
ThemeStar ThemeName = "starfield"
|
||||||
ThemeGradient ThemeName = "gradient"
|
ThemeGradient ThemeName = "gradient"
|
||||||
ThemeParticle ThemeName = "particles"
|
ThemeParticle ThemeName = "particles"
|
||||||
|
ThemeFractal ThemeName = "fractal"
|
||||||
|
ThemeText ThemeName = "text"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -38,6 +40,7 @@ type Config struct {
|
|||||||
Color1 string `json:"color1"`
|
Color1 string `json:"color1"`
|
||||||
Color2 string `json:"color2"`
|
Color2 string `json:"color2"`
|
||||||
ColorGradient bool `json:"colorGradient"`
|
ColorGradient bool `json:"colorGradient"`
|
||||||
|
WallpaperText string `json:"wallpaperText"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultZodiac = "射手座"
|
const defaultZodiac = "射手座"
|
||||||
|
|||||||
53
systray.go
53
systray.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -26,6 +27,8 @@ var themeNames = []struct {
|
|||||||
{ThemeStar, "星空"},
|
{ThemeStar, "星空"},
|
||||||
{ThemeGradient, "渐变"},
|
{ThemeGradient, "渐变"},
|
||||||
{ThemeParticle, "粒子"},
|
{ThemeParticle, "粒子"},
|
||||||
|
{ThemeFractal, "极光流体"},
|
||||||
|
{ThemeText, "文字"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func onSystrayReady() {
|
func onSystrayReady() {
|
||||||
@@ -47,7 +50,11 @@ func onSystrayReady() {
|
|||||||
themeItems = append(themeItems, item)
|
themeItems = append(themeItems, item)
|
||||||
}
|
}
|
||||||
mLocalImage := systray.AddMenuItem("本地图片", "选择本地图片作为壁纸")
|
mLocalImage := systray.AddMenuItem("本地图片", "选择本地图片作为壁纸")
|
||||||
mBingDaily := systray.AddMenuItem("Bing 每日壁纸", "使用 Bing 每日壁纸")
|
mBingMenu := systray.AddMenuItem("Bing 每日壁纸", "")
|
||||||
|
mBingEnable := mBingMenu.AddSubMenuItem("启用 Bing 壁纸", "")
|
||||||
|
mBingPrev := mBingMenu.AddSubMenuItem("◀ 上一个", "")
|
||||||
|
mBingNext := mBingMenu.AddSubMenuItem("下一个 ▶", "")
|
||||||
|
mBingFav := mBingMenu.AddSubMenuItem("★ 收藏当前壁纸", "")
|
||||||
mSolidColor := systray.AddMenuItem("纯色壁纸", "选择纯色壁纸")
|
mSolidColor := systray.AddMenuItem("纯色壁纸", "选择纯色壁纸")
|
||||||
mGradientColor := systray.AddMenuItem("渐变壁纸", "选择渐变壁纸")
|
mGradientColor := systray.AddMenuItem("渐变壁纸", "选择渐变壁纸")
|
||||||
|
|
||||||
@@ -82,6 +89,7 @@ func onSystrayReady() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
|
mRestart := systray.AddMenuItem("重启", "重启程序")
|
||||||
mQuit := systray.AddMenuItem("退出", "退出程序")
|
mQuit := systray.AddMenuItem("退出", "退出程序")
|
||||||
|
|
||||||
// 主题切换监听
|
// 主题切换监听
|
||||||
@@ -123,10 +131,10 @@ func onSystrayReady() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Bing 每日
|
// Bing 启用
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-mBingDaily.ClickedCh
|
<-mBingEnable.ClickedCh
|
||||||
cfg := loadConfig()
|
cfg := loadConfig()
|
||||||
cfg.WallpaperType = WPBing
|
cfg.WallpaperType = WPBing
|
||||||
saveConfig(cfg)
|
saveConfig(cfg)
|
||||||
@@ -134,7 +142,34 @@ func onSystrayReady() {
|
|||||||
it.Uncheck()
|
it.Uncheck()
|
||||||
}
|
}
|
||||||
log.Println("切换 Bing 壁纸")
|
log.Println("切换 Bing 壁纸")
|
||||||
go fetchBingWallpaper()
|
go fetchBingHistory()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Bing 上一个
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-mBingPrev.ClickedCh
|
||||||
|
bingPrev()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Bing 下一个
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-mBingNext.ClickedCh
|
||||||
|
bingNext()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Bing 收藏
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-mBingFav.ClickedCh
|
||||||
|
title := bingToggleFavorite()
|
||||||
|
if title != "" {
|
||||||
|
mBingFav.SetTitle(title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -243,6 +278,16 @@ func onSystrayReady() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 重启
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-mRestart.ClickedCh
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
exec.Command(exe).Start()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go startWebView()
|
go startWebView()
|
||||||
go weatherLoop()
|
go weatherLoop()
|
||||||
go bingWallpaperLoop()
|
go bingWallpaperLoop()
|
||||||
|
|||||||
33
wallpaper.go
33
wallpaper.go
@@ -26,11 +26,19 @@ var themeGradient string
|
|||||||
//go:embed web/themes/particles.html
|
//go:embed web/themes/particles.html
|
||||||
var themeParticles string
|
var themeParticles string
|
||||||
|
|
||||||
|
//go:embed web/themes/fractal.html
|
||||||
|
var themeFractal string
|
||||||
|
|
||||||
|
//go:embed web/themes/text.html
|
||||||
|
var themeText string
|
||||||
|
|
||||||
var themeMap = map[ThemeName]string{
|
var themeMap = map[ThemeName]string{
|
||||||
ThemeAurora: themeAurora,
|
ThemeAurora: themeAurora,
|
||||||
ThemeStar: themeStarfield,
|
ThemeStar: themeStarfield,
|
||||||
ThemeGradient: themeGradient,
|
ThemeGradient: themeGradient,
|
||||||
ThemeParticle: themeParticles,
|
ThemeParticle: themeParticles,
|
||||||
|
ThemeFractal: themeFractal,
|
||||||
|
ThemeText: themeText,
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildWallpaperHTML(cfg *Config) string {
|
func buildWallpaperHTML(cfg *Config) string {
|
||||||
@@ -51,11 +59,12 @@ func buildWallpaperHTML(cfg *Config) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case WPBing:
|
case WPBing:
|
||||||
bingPath := filepath.Join(configDir(), "bing_wallpaper.jpg")
|
if p := getCurrentBingPath(); p != "" {
|
||||||
if _, err := os.Stat(bingPath); err == nil {
|
if _, err := os.Stat(p); err == nil {
|
||||||
src := imageToDataURI(bingPath)
|
src := imageToDataURI(p)
|
||||||
if src != "" {
|
if src != "" {
|
||||||
bg = fmt.Sprintf(`<img src="%s" style="position:fixed;top:0;left:0;width:100%%;height:100%%;object-fit:cover;z-index:1;">`, src)
|
bg = fmt.Sprintf(`<img src="%s" style="position:fixed;top:0;left:0;width:100%%;height:100%%;object-fit:cover;z-index:1;">`, src)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case WPColor:
|
case WPColor:
|
||||||
@@ -69,7 +78,16 @@ func buildWallpaperHTML(cfg *Config) string {
|
|||||||
if bg == "" {
|
if bg == "" {
|
||||||
bg = themeAurora
|
bg = themeAurora
|
||||||
}
|
}
|
||||||
return strings.Replace(overlayHTML, "{{BACKGROUND}}", bg, 1)
|
html := strings.Replace(overlayHTML, "{{BACKGROUND}}", bg, 1)
|
||||||
|
|
||||||
|
// 注入自定义文字
|
||||||
|
if cfg.WallpaperType == WPTheme && cfg.Theme == ThemeText && cfg.WallpaperText != "" {
|
||||||
|
escaped := strings.ReplaceAll(cfg.WallpaperText, `\`, `\\`)
|
||||||
|
escaped = strings.ReplaceAll(escaped, `"`, `\"`)
|
||||||
|
html = strings.Replace(html, "</script>", `window.wallpaperText = "`+escaped+`";</script>`, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageToDataURI(path string) string {
|
func imageToDataURI(path string) string {
|
||||||
@@ -107,6 +125,9 @@ func reloadWallpaper() {
|
|||||||
go func() {
|
go func() {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
evalJS(fmt.Sprintf(`window.userZodiac = %q;`, cfg.Zodiac))
|
evalJS(fmt.Sprintf(`window.userZodiac = %q;`, cfg.Zodiac))
|
||||||
|
if cfg.Theme == ThemeText && cfg.WallpaperText != "" {
|
||||||
|
evalJS(fmt.Sprintf(`window.wallpaperText = %q; var el=document.getElementById("wallpaper-text"); if(el){el.textContent=%q;}`, cfg.WallpaperText, cfg.WallpaperText))
|
||||||
|
}
|
||||||
city := getCurrentCity()
|
city := getCurrentCity()
|
||||||
go fetchAndPushWeather(city)
|
go fetchAndPushWeather(city)
|
||||||
}()
|
}()
|
||||||
|
|||||||
176
web/overlay.html
176
web/overlay.html
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- v3 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
@@ -13,37 +14,120 @@ html, body {
|
|||||||
|
|
||||||
#info {
|
#info {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 40px;
|
||||||
right: 80px;
|
right: 40px;
|
||||||
transform: translateY(-50%);
|
background: rgba(0, 0, 0, 0.25);
|
||||||
background: rgba(0, 0, 0, 0.4);
|
backdrop-filter: blur(24px);
|
||||||
backdrop-filter: blur(20px);
|
-webkit-backdrop-filter: blur(24px);
|
||||||
border-radius: 16px;
|
border-radius: 20px;
|
||||||
padding: 24px 32px;
|
padding: 28px 32px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-family: "Microsoft YaHei", sans-serif;
|
font-family: "Microsoft YaHei", sans-serif;
|
||||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 0 0 1px rgba(255,255,255,0.08);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
min-width: 300px;
|
min-width: 320px;
|
||||||
max-height: 90vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time { font-size: 64px; font-weight: 300; color: #fff; text-shadow: 0 2px 12px rgba(0,0,0,0.8); letter-spacing: -1px; }
|
.time {
|
||||||
.date { font-size: 15px; color: rgba(255,255,255,0.9); margin-top: 6px; text-shadow: 0 1px 6px rgba(0,0,0,0.8); }
|
font-size: 72px;
|
||||||
.weather-section { margin-top: 20px; }
|
font-weight: 200;
|
||||||
.current-weather { font-size: 17px; color: rgba(255,255,255,0.95); text-shadow: 0 1px 4px rgba(0,0,0,0.8); margin-bottom: 12px; }
|
color: #fff;
|
||||||
.forecast-title { font-size: 13px; color: rgba(255,255,255,0.7); margin-bottom: 8px; }
|
text-shadow: 0 2px 20px rgba(0,0,0,0.5);
|
||||||
.weather-forecast { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px; justify-content: flex-end; }
|
letter-spacing: -2px;
|
||||||
.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); }
|
line-height: 1;
|
||||||
.forecast-time { margin-bottom: 4px; opacity: 0.8; }
|
}
|
||||||
.forecast-temp { font-weight: 500; }
|
.date {
|
||||||
.daily-forecast { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px; justify-content: flex-end; }
|
font-size: 14px;
|
||||||
.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); }
|
font-weight: 400;
|
||||||
.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; text-shadow: 0 1px 4px rgba(0,0,0,0.8); }
|
color: rgba(255,255,255,0.7);
|
||||||
.weather-forecast::-webkit-scrollbar, #info::-webkit-scrollbar { display: none; }
|
margin-top: 8px;
|
||||||
|
text-shadow: 0 1px 6px rgba(0,0,0,0.5);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent);
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-section {}
|
||||||
|
.current-weather {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(255,255,255,0.95);
|
||||||
|
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.forecast-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(255,255,255,0.45);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.weather-forecast {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.forecast-item {
|
||||||
|
background: rgba(255,255,255,0.06);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 52px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255,255,255,0.8);
|
||||||
|
border: 1px solid rgba(255,255,255,0.04);
|
||||||
|
}
|
||||||
|
.forecast-time { margin-bottom: 4px; opacity: 0.6; font-size: 10px; }
|
||||||
|
.forecast-temp { font-weight: 500; margin-top: 2px; }
|
||||||
|
|
||||||
|
.daily-forecast {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.daily-item {
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 48px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255,255,255,0.8);
|
||||||
|
border: 1px solid rgba(255,255,255,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zodiac {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(255,255,255,0.9);
|
||||||
|
line-height: 1.6;
|
||||||
|
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-forecast::-webkit-scrollbar,
|
||||||
|
.daily-forecast::-webkit-scrollbar,
|
||||||
|
#info::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
#author {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 60px;
|
||||||
|
right: 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgba(255,255,255,0.5);
|
||||||
|
font-family: "Microsoft YaHei", sans-serif;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -52,16 +136,20 @@ html, body {
|
|||||||
<div id="info">
|
<div id="info">
|
||||||
<div class="time" id="time">00:00</div>
|
<div class="time" id="time">00:00</div>
|
||||||
<div class="date" id="date">1月1日 周一</div>
|
<div class="date" id="date">1月1日 周一</div>
|
||||||
|
<div class="divider"></div>
|
||||||
<div class="weather-section">
|
<div class="weather-section">
|
||||||
<div class="current-weather" id="currentWeather">🌤️ 加载中...</div>
|
<div class="current-weather" id="currentWeather">加载中...</div>
|
||||||
<div class="forecast-title">未来24小时</div>
|
<div class="forecast-title">24小时预报</div>
|
||||||
<div class="weather-forecast" id="hourlyForecast"></div>
|
<div class="weather-forecast" id="hourlyForecast"></div>
|
||||||
<div class="forecast-title" style="margin-top: 12px;">未来7天</div>
|
<div class="forecast-title" style="margin-top: 12px;">7日预报</div>
|
||||||
<div class="daily-forecast" id="dailyForecast"></div>
|
<div class="daily-forecast" id="dailyForecast"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="zodiac" id="zodiac">✨ 射手座运势</div>
|
<div class="divider"></div>
|
||||||
|
<div class="zodiac" id="zodiac">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="author">绝尘</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let lastTimeStr = '';
|
let lastTimeStr = '';
|
||||||
let lastDateStr = '';
|
let lastDateStr = '';
|
||||||
@@ -77,25 +165,31 @@ window.updateWeatherFromGo = function(data) {
|
|||||||
fel.innerHTML = data.hourly.map(function(item) {
|
fel.innerHTML = data.hourly.map(function(item) {
|
||||||
return '<div class="forecast-item"><div class="forecast-time">' + item.time + '</div><div>' + item.icon + '</div><div class="forecast-temp">' + item.temp + '</div></div>';
|
return '<div class="forecast-item"><div class="forecast-time">' + item.time + '</div><div>' + item.icon + '</div><div class="forecast-temp">' + item.temp + '</div></div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
} else { fel.innerHTML = '<div style="font-size:11px;opacity:0.5">暂无数据</div>'; }
|
} else { fel.innerHTML = '<div style="font-size:11px;opacity:0.4">暂无数据</div>'; }
|
||||||
}
|
}
|
||||||
var del = document.getElementById('dailyForecast');
|
var del = document.getElementById('dailyForecast');
|
||||||
if (del) {
|
if (del) {
|
||||||
if (data.daily && data.daily.length > 0) {
|
if (data.daily && data.daily.length > 0) {
|
||||||
del.innerHTML = data.daily.map(function(item) {
|
del.innerHTML = data.daily.map(function(item) {
|
||||||
return '<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>';
|
return '<div class="daily-item"><div style="opacity:0.6;margin-bottom:3px;font-size:10px">' + item.date + '</div><div>' + item.icon + '</div><div class="forecast-temp">' + item.tempMin + '°~' + item.tempMax + '°</div></div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
} else { del.innerHTML = '<div style="font-size:11px;opacity:0.5">暂无数据</div>'; }
|
} else { del.innerHTML = '<div style="font-size:11px;opacity:0.4">暂无数据</div>'; }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var fortunes = {
|
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:'艺术灵感丰富,适合创作。'}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getUserZodiac() { return window.userZodiac || '射手座'; }
|
function getUserZodiac() { return window.userZodiac || '射手座'; }
|
||||||
@@ -106,8 +200,8 @@ function updateZodiacDisplay() {
|
|||||||
lastZodiac = name;
|
lastZodiac = name;
|
||||||
var el = document.getElementById('zodiac');
|
var el = document.getElementById('zodiac');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
var fortune = fortunes[name] || '运势平稳,保持平常心。';
|
var z = zodiacData[name] || {icon:'✨',date:'',fortune:'运势平稳,保持平常心。'};
|
||||||
el.innerHTML = '✨ ' + name + '运势<br><small style="opacity:0.7">' + fortune + '</small>';
|
el.innerHTML = 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>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTime() {
|
function updateTime() {
|
||||||
|
|||||||
161
web/themes/fractal.html
Normal file
161
web/themes/fractal.html
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<canvas id="c" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:1;"></canvas>
|
||||||
|
<script>
|
||||||
|
var canvas = document.getElementById('c');
|
||||||
|
var gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
||||||
|
|
||||||
|
var mouseX = 0, mouseY = 0, lastMove = 0;
|
||||||
|
var clickX = 0, clickY = 0, clickTime = 0;
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', function(e) {
|
||||||
|
mouseX = e.clientX / window.innerWidth;
|
||||||
|
mouseY = 1.0 - e.clientY / window.innerHeight;
|
||||||
|
lastMove = performance.now();
|
||||||
|
});
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
clickX = e.clientX / window.innerWidth;
|
||||||
|
clickY = 1.0 - e.clientY / window.innerHeight;
|
||||||
|
clickTime = performance.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
var vsSrc = 'attribute vec2 a_pos;void main(){gl_Position=vec4(a_pos,0.0,1.0);}';
|
||||||
|
var fsSrc = `
|
||||||
|
precision highp float;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform vec2 u_mouse;
|
||||||
|
uniform float u_click;
|
||||||
|
uniform vec2 u_clickPos;
|
||||||
|
|
||||||
|
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=(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);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function mkShader(type,src){
|
||||||
|
var s=gl.createShader(type);
|
||||||
|
gl.shaderSource(s,src);
|
||||||
|
gl.compileShader(s);
|
||||||
|
if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)){gl.deleteShader(s);return null;}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
var vs=mkShader(gl.VERTEX_SHADER,vsSrc);
|
||||||
|
var fs=mkShader(gl.FRAGMENT_SHADER,fsSrc);
|
||||||
|
var prog=gl.createProgram();
|
||||||
|
gl.attachShader(prog,vs);
|
||||||
|
gl.attachShader(prog,fs);
|
||||||
|
gl.linkProgram(prog);
|
||||||
|
gl.useProgram(prog);
|
||||||
|
|
||||||
|
var 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);
|
||||||
|
var aPos=gl.getAttribLocation(prog,'a_pos');
|
||||||
|
gl.enableVertexAttribArray(aPos);
|
||||||
|
gl.vertexAttribPointer(aPos,2,gl.FLOAT,false,0,0);
|
||||||
|
|
||||||
|
var uTime=gl.getUniformLocation(prog,'u_time');
|
||||||
|
var uRes=gl.getUniformLocation(prog,'u_resolution');
|
||||||
|
var uMouse=gl.getUniformLocation(prog,'u_mouse');
|
||||||
|
var uClick=gl.getUniformLocation(prog,'u_click');
|
||||||
|
var uClickPos=gl.getUniformLocation(prog,'u_clickPos');
|
||||||
|
|
||||||
|
function resize(){
|
||||||
|
var 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();
|
||||||
|
|
||||||
|
var lastFrame=0, targetFPS=30;
|
||||||
|
|
||||||
|
function render(now){
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
if(window._paused||window._fullscreen)return;
|
||||||
|
|
||||||
|
var interval=1000/targetFPS;
|
||||||
|
if(now-lastFrame<interval)return;
|
||||||
|
lastFrame=now;
|
||||||
|
|
||||||
|
if(now-lastMove>5000){targetFPS=10;}else{targetFPS=30;}
|
||||||
|
|
||||||
|
var t=now/1000.0;
|
||||||
|
var 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);
|
||||||
|
</script>
|
||||||
37
web/themes/text.html
Normal file
37
web/themes/text.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<style>
|
||||||
|
@keyframes textGlow {
|
||||||
|
0%, 100% { text-shadow: 0 0 40px rgba(120,140,255,0.3), 0 0 80px rgba(120,140,255,0.1); }
|
||||||
|
50% { text-shadow: 0 0 60px rgba(120,140,255,0.5), 0 0 120px rgba(120,140,255,0.2); }
|
||||||
|
}
|
||||||
|
@keyframes textFloat {
|
||||||
|
0%, 100% { transform: translate(-50%, -50%) translateY(0px); }
|
||||||
|
50% { transform: translate(-50%, -50%) translateY(-8px); }
|
||||||
|
}
|
||||||
|
#wallpaper-text {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 1;
|
||||||
|
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||||
|
font-size: 120px;
|
||||||
|
font-weight: 200;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
letter-spacing: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: textGlow 4s ease-in-out infinite, textFloat 6s ease-in-out infinite;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="wallpaper-text"></div>
|
||||||
|
<script>
|
||||||
|
var el = document.getElementById('wallpaper-text');
|
||||||
|
if (el) {
|
||||||
|
var text = 'DREAM';
|
||||||
|
if (window.wallpaperText) text = window.wallpaperText;
|
||||||
|
el.textContent = text;
|
||||||
|
var len = text.length;
|
||||||
|
var size = Math.min(120, Math.floor(window.innerWidth * 0.8 / len));
|
||||||
|
el.style.fontSize = size + 'px';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user