feat: Ambient風ヘッダーに刷新(時計・日付・天気・挨拶)
- 標準ヘッダーを廃止、時計ゾーンに置き換え - 大型時刻表示(68px / weight:300) - 日付・天気(open-meteo API)・挨拶を Ambient から移植 - 設定ボタンを時計ゾーン右上の丸ボタンに - 検索バーを top:0 で sticky 化、カテゴリタブを top:52px で sticky 化
This commit is contained in:
parent
d85b825bba
commit
22bb95a10a
182
index.html
182
index.html
|
|
@ -31,11 +31,67 @@
|
|||
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
||||
|
||||
<style>
|
||||
/* ── Clock zone ── */
|
||||
.veil-clock {
|
||||
padding: 28px 20px 16px;
|
||||
position: relative;
|
||||
}
|
||||
.veil-time {
|
||||
font-size: 68px;
|
||||
font-weight: 300;
|
||||
letter-spacing: -3px;
|
||||
line-height: 1;
|
||||
color: var(--text);
|
||||
}
|
||||
.veil-sub {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text2);
|
||||
margin-top: 8px;
|
||||
}
|
||||
.veil-sep { color: var(--text3); }
|
||||
.veil-weather {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.veil-weather-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
stroke: var(--text2);
|
||||
stroke-width: 1.75;
|
||||
}
|
||||
.veil-greeting {
|
||||
font-size: 13px;
|
||||
color: var(--text3);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.veil-settings-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 12px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--text2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s;
|
||||
}
|
||||
.veil-settings-btn:active { background: var(--surface2); }
|
||||
|
||||
/* ── Search ── */
|
||||
.search-wrap {
|
||||
padding: 8px 16px 4px;
|
||||
position: sticky;
|
||||
top: 52px;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
|
@ -71,6 +127,10 @@
|
|||
padding: 8px 16px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
position: sticky;
|
||||
top: 52px;
|
||||
z-index: 9;
|
||||
background: var(--bg);
|
||||
}
|
||||
.cat-scroll::-webkit-scrollbar { display: none; }
|
||||
.cat-btn {
|
||||
|
|
@ -372,18 +432,26 @@
|
|||
</aside>
|
||||
<div class="overlay" id="overlay" aria-hidden="true"></div>
|
||||
|
||||
<header class="header">
|
||||
<div class="header-brand">
|
||||
<div class="header-dot" aria-hidden="true"></div>
|
||||
<span class="header-title">Veil</span>
|
||||
</div>
|
||||
<button class="icon-btn" id="settingsBtn" aria-label="設定" aria-expanded="false">
|
||||
<i data-lucide="settings" style="width:18px;height:18px;stroke-width:1.5"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main id="main-content">
|
||||
|
||||
<!-- 時計・天気・挨拶ゾーン -->
|
||||
<div class="veil-clock">
|
||||
<div class="veil-time" id="veilTime">--:--</div>
|
||||
<div class="veil-sub">
|
||||
<span id="veilDate">---</span>
|
||||
<span class="veil-sep">·</span>
|
||||
<span class="veil-weather" id="veilWeather">
|
||||
<i data-lucide="cloud" class="veil-weather-icon" id="veilWeatherIcon"></i>
|
||||
<span id="veilTemp">--°</span>
|
||||
<span id="veilWeatherLabel"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="veil-greeting" id="veilGreeting"></div>
|
||||
<button class="veil-settings-btn" id="settingsBtn" aria-label="設定" aria-expanded="false">
|
||||
<i data-lucide="settings" style="width:16px;height:16px;stroke-width:1.5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="search-wrap">
|
||||
<div class="search-wrap-inner">
|
||||
<span class="search-icon">
|
||||
|
|
@ -816,12 +884,104 @@ function showToast(msg) {
|
|||
setTimeout(() => el.classList.remove('show'), 2200);
|
||||
}
|
||||
|
||||
// ── 時計 ────────────────────────────────────────────────────
|
||||
const DAYS_JP = ['日','月','火','水','木','金','土'];
|
||||
|
||||
const GREETING_POOL = {
|
||||
morning: ['おはようございます','いい朝ですね','さあ、始めましょうか','今日もよろしく'],
|
||||
afternoon: ['こんにちは','お昼どうでしたか','午後もがんばりましょう','ひと息ついて'],
|
||||
evening: ['こんばんは','お疲れさまです','今日もよく頑張りました','ゆっくり振り返りましょう'],
|
||||
night: ['お疲れさまでした','ゆっくり休んでくださいね','そろそろ休みましょうか'],
|
||||
latenight: ['夜更かしですね','深夜のひとときを','静かな夜ですね'],
|
||||
};
|
||||
|
||||
function getGreetingPool(hr) {
|
||||
if (hr >= 5 && hr <= 11) return GREETING_POOL.morning;
|
||||
if (hr >= 12 && hr <= 16) return GREETING_POOL.afternoon;
|
||||
if (hr >= 17 && hr <= 20) return GREETING_POOL.evening;
|
||||
if (hr >= 21 && hr <= 23) return GREETING_POOL.night;
|
||||
return GREETING_POOL.latenight;
|
||||
}
|
||||
|
||||
const _greetIdx = Math.floor(Math.random() * 4);
|
||||
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
const h = String(now.getHours()).padStart(2, '0');
|
||||
const m = String(now.getMinutes()).padStart(2, '0');
|
||||
document.getElementById('veilTime').textContent = `${h}:${m}`;
|
||||
|
||||
const mo = now.getMonth() + 1;
|
||||
const d = now.getDate();
|
||||
const day = DAYS_JP[now.getDay()];
|
||||
document.getElementById('veilDate').textContent = `${mo}月${d}日(${day})`;
|
||||
|
||||
const pool = getGreetingPool(now.getHours());
|
||||
document.getElementById('veilGreeting').textContent = pool[_greetIdx % pool.length];
|
||||
}
|
||||
|
||||
// ── 天気 ────────────────────────────────────────────────────
|
||||
const WMO_MAP = [
|
||||
[0, 'sun', '晴れ'],
|
||||
[3, 'cloud', '曇り'],
|
||||
[48, 'cloud', '霧'],
|
||||
[57, 'cloud-drizzle', '霧雨'],
|
||||
[67, 'cloud-rain', '雨'],
|
||||
[77, 'snowflake', '雪'],
|
||||
[82, 'cloud-rain', '大雨'],
|
||||
[86, 'snowflake', '大雪'],
|
||||
[99, 'cloud-lightning','雷雨'],
|
||||
];
|
||||
|
||||
function wmoToInfo(code) {
|
||||
for (let i = WMO_MAP.length - 1; i >= 0; i--) {
|
||||
if (code >= WMO_MAP[i][0]) return { icon: WMO_MAP[i][1], label: WMO_MAP[i][2] };
|
||||
}
|
||||
return { icon: 'cloud', label: '---' };
|
||||
}
|
||||
|
||||
async function fetchWeather() {
|
||||
try {
|
||||
let lat = 34.6937, lon = 135.5023;
|
||||
try {
|
||||
const pos = await new Promise((res, rej) =>
|
||||
navigator.geolocation.getCurrentPosition(res, rej, { timeout: 3000 })
|
||||
);
|
||||
lat = pos.coords.latitude;
|
||||
lon = pos.coords.longitude;
|
||||
} catch { /* デフォルト使用 */ }
|
||||
|
||||
const ac = new AbortController();
|
||||
setTimeout(() => ac.abort(), 6000);
|
||||
const r = await fetch(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${lat.toFixed(4)}&longitude=${lon.toFixed(4)}¤t=temperature_2m,weather_code&timezone=auto`,
|
||||
{ signal: ac.signal }
|
||||
);
|
||||
if (!r.ok) throw new Error();
|
||||
const data = await r.json();
|
||||
const temp = Math.round(data.current.temperature_2m);
|
||||
const info = wmoToInfo(data.current.weather_code);
|
||||
|
||||
document.getElementById('veilWeatherIcon').setAttribute('data-lucide', info.icon);
|
||||
document.getElementById('veilTemp').textContent = `${temp}°`;
|
||||
document.getElementById('veilWeatherLabel').textContent = info.label;
|
||||
lucide.createIcons();
|
||||
} catch {
|
||||
document.getElementById('veilWeather').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ── SW 登録 ─────────────────────────────────────────────────
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
||||
}
|
||||
|
||||
// ── 初期化 ──────────────────────────────────────────────────
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
fetchWeather();
|
||||
setInterval(fetchWeather, 30 * 60 * 1000);
|
||||
|
||||
renderCatTabs();
|
||||
renderApps();
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue