feat: apply Liquid Glass design to all miniapp components

Unify cards, steps, skills, stats, command tiles, send bar, and
buttons with frosted glass materials, translucent backgrounds,
spring animations, and consistent glass CSS variables for
light/dark themes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-22 08:12:21 +09:00
parent df47980070
commit 374f86cbd4

View file

@ -17,6 +17,14 @@
--done: #34c759;
--current: var(--btn);
--pending-phase: var(--hint);
/* Liquid Glass */
--glass-bg: rgba(255, 255, 255, 0.55);
--glass-border: rgba(255, 255, 255, 0.7);
--glass-shadow: rgba(0, 0, 0, 0.06);
--glass-divider: rgba(0, 0, 0, 0.06);
--tab-bar-bg: rgba(255, 255, 255, 0.72);
--tab-track-bg: rgba(0, 0, 0, 0.06);
--tab-pill-bg: rgba(255, 255, 255, 0.9);
}
@media (prefers-color-scheme: dark) {
@ -28,6 +36,14 @@
--btn: var(--tg-theme-button-color, #5ac8fa);
--btn-text: var(--tg-theme-button-text-color, #ffffff);
--secondary-bg: var(--tg-theme-secondary-bg-color, #2c2c2e);
/* Liquid Glass — dark */
--glass-bg: rgba(255, 255, 255, 0.08);
--glass-border: rgba(255, 255, 255, 0.12);
--glass-shadow: rgba(0, 0, 0, 0.2);
--glass-divider: rgba(255, 255, 255, 0.08);
--tab-bar-bg: rgba(28, 28, 30, 0.72);
--tab-track-bg: rgba(255, 255, 255, 0.1);
--tab-pill-bg: rgba(255, 255, 255, 0.16);
}
}
@ -43,15 +59,40 @@
.tabs {
display: flex;
background: var(--secondary-bg);
position: sticky;
top: 0;
z-index: 10;
padding: 8px 12px;
background: var(--tab-bar-bg);
-webkit-backdrop-filter: saturate(180%) blur(20px);
backdrop-filter: saturate(180%) blur(20px);
}
.tabs-inner {
display: flex;
position: relative;
width: 100%;
background: var(--tab-track-bg);
border-radius: 10px;
padding: 2px;
}
.tab-indicator {
position: absolute;
top: 2px;
bottom: 2px;
left: 2px;
width: calc(25% - 2px);
border-radius: 8px;
background: var(--tab-pill-bg);
box-shadow: 0 0.5px 2px rgba(0,0,0,0.12), 0 0.5px 1px rgba(0,0,0,0.08);
transition: transform 0.38s cubic-bezier(0.25, 1, 0.5, 1);
z-index: 0;
}
.tab {
flex: 1;
padding: 12px 4px;
padding: 7px 4px;
text-align: center;
font-size: 13px;
font-weight: 500;
@ -59,36 +100,43 @@
border: none;
background: none;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s;
transition: color 0.25s ease;
position: relative;
z-index: 1;
-webkit-tap-highlight-color: transparent;
}
.tab.active {
color: var(--btn);
border-bottom-color: var(--btn);
color: var(--text);
font-weight: 600;
}
.panel { display: none; padding: 16px; }
.panel.active { display: block; }
.card {
background: var(--secondary-bg);
border-radius: 12px;
padding: 14px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 16px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 1px 3px var(--glass-shadow);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
.card-title {
font-size: 13px;
font-size: 12px;
color: var(--hint);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
letter-spacing: 0.6px;
font-weight: 600;
}
.card-value {
font-size: 20px;
font-weight: 600;
font-size: 22px;
font-weight: 700;
}
/* Plan - phase/step list */
@ -99,15 +147,15 @@
.phase-header {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 0 6px;
gap: 10px;
padding: 10px 0 8px;
font-weight: 600;
font-size: 14px;
font-size: 15px;
}
.phase-indicator {
width: 22px;
height: 22px;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
@ -120,39 +168,45 @@
.phase-indicator.done { background: var(--done); }
.phase-indicator.current { background: var(--current); }
.phase-indicator.pending { background: var(--pending-phase); }
.phase-indicator.pending { background: var(--glass-divider); color: var(--hint); }
.phase-title { flex: 1; }
.phase-progress {
font-size: 12px;
color: var(--hint);
font-weight: 400;
font-weight: 500;
}
.step {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 12px 10px 30px;
margin-bottom: 4px;
border-radius: 8px;
padding: 12px 14px 12px 34px;
margin-bottom: 6px;
border-radius: 12px;
cursor: pointer;
transition: all 0.15s;
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s;
-webkit-tap-highlight-color: transparent;
}
.step:active { transform: scale(0.97); }
.step:not(.step-done) { background: var(--secondary-bg); }
.step:not(.step-done) {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
box-shadow: 0 0.5px 2px var(--glass-shadow);
}
.step-check {
width: 20px;
height: 20px;
border-radius: 6px;
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid var(--hint);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
margin-top: 1px;
margin-top: 0;
transition: all 0.2s;
}
.step-check.done {
@ -183,24 +237,29 @@
/* Skills */
.skill-item {
background: linear-gradient(to bottom, var(--bg), var(--secondary-bg));
border-radius: 12px;
background: var(--glass-bg);
border-radius: 16px;
padding: 14px 14px 14px 16px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.12s;
border: 1px solid var(--hint);
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s, border-color 0.2s;
border: 1px solid var(--glass-border);
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.08), 0 1px 0 rgba(255,255,255,0.06) inset;
box-shadow: 0 1px 3px var(--glass-shadow);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
-webkit-tap-highlight-color: transparent;
}
.skill-item:active {
transform: scale(0.98) translateY(1px);
box-shadow: 0 0 2px rgba(0,0,0,0.06);
transform: scale(0.98);
}
.skill-item.selected {
border-color: var(--btn);
box-shadow: 0 2px 8px var(--glass-shadow);
}
.skill-item.selected { border-color: var(--btn); box-shadow: 0 2px 6px rgba(0,0,0,0.12); }
.skill-body { flex: 1; min-width: 0; }
@ -219,18 +278,19 @@
.skill-source {
display: inline-block;
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
background: var(--btn);
color: var(--btn-text);
padding: 3px 10px;
border-radius: 20px;
background: var(--tab-track-bg);
color: var(--hint);
margin-top: 6px;
font-weight: 500;
}
.skill-arrow {
color: var(--hint);
font-size: 22px;
flex-shrink: 0;
transition: color 0.15s;
transition: color 0.2s;
}
.skill-item.selected .skill-arrow { color: var(--btn); }
@ -238,13 +298,13 @@
.stat-row {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid var(--secondary-bg);
padding: 11px 0;
border-bottom: 1px solid var(--glass-divider);
}
.stat-row:last-child { border-bottom: none; }
.stat-label { color: var(--hint); }
.stat-value { font-weight: 600; }
.stat-label { color: var(--hint); font-size: 14px; }
.stat-value { font-weight: 600; font-size: 14px; }
/* Send bar */
.send-bar {
@ -252,22 +312,27 @@
bottom: 0;
left: 0;
right: 0;
background: var(--bg);
background: var(--tab-bar-bg);
-webkit-backdrop-filter: saturate(180%) blur(20px);
backdrop-filter: saturate(180%) blur(20px);
padding: 12px 16px;
display: flex;
gap: 8px;
border-top: 1px solid var(--secondary-bg);
border-top: 1px solid var(--glass-divider);
}
.send-input {
flex: 1;
padding: 10px 14px;
padding: 10px 16px;
border-radius: 20px;
border: 1px solid var(--hint);
background: var(--bg);
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--text);
font-size: 14px;
outline: none;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
transition: border-color 0.2s;
}
.send-input:focus { border-color: var(--btn); }
@ -281,23 +346,26 @@
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), background 0.15s;
-webkit-tap-highlight-color: transparent;
}
.send-btn:active { opacity: 0.8; }
.send-btn:active { transform: scale(0.95); }
.send-btn:disabled { opacity: 0.5; }
.send-btn.sent { background: var(--done); transition: background 0.15s; }
.send-btn.sent { background: var(--done); }
.empty-state {
text-align: center;
color: var(--hint);
padding: 40px 20px;
font-size: 14px;
padding: 48px 20px;
font-size: 15px;
}
.loading {
text-align: center;
color: var(--hint);
padding: 40px 20px;
padding: 48px 20px;
font-size: 15px;
}
.cmd-tiles {
@ -308,60 +376,65 @@
.cmd-tile {
padding: 16px 14px;
border-radius: 12px;
border: 1px solid var(--hint);
background: linear-gradient(to bottom, var(--bg), var(--secondary-bg));
border-radius: 14px;
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--text);
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.12s;
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), background 0.15s;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 1px 0 rgba(255,255,255,0.06) inset;
box-shadow: 0 1px 3px var(--glass-shadow);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
-webkit-tap-highlight-color: transparent;
}
.cmd-tile:active {
transform: scale(0.96) translateY(1px);
box-shadow: 0 0 2px rgba(0,0,0,0.08);
background: var(--secondary-bg);
transform: scale(0.96);
}
.cmd-tile.sent {
background: var(--btn);
color: var(--btn-text);
border-color: var(--btn);
box-shadow: 0 2px 4px rgba(0,0,0,0.15);
transition: all 0.15s;
}
.refresh-btn {
display: block;
width: 100%;
padding: 12px 16px;
border-radius: 12px;
border: 1px solid var(--hint);
background: linear-gradient(to bottom, var(--bg), var(--secondary-bg));
border-radius: 14px;
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--text);
font-size: 14px;
font-weight: 500;
cursor: pointer;
margin-top: 8px;
transition: all 0.12s;
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
margin-top: 12px;
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1);
box-shadow: 0 1px 3px var(--glass-shadow);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
-webkit-tap-highlight-color: transparent;
}
.refresh-btn:active {
transform: scale(0.98) translateY(1px);
box-shadow: 0 0 2px rgba(0,0,0,0.06);
transform: scale(0.98);
}
</style>
</head>
<body>
<div class="tabs">
<button class="tab active" data-panel="plan">Plan</button>
<button class="tab" data-panel="skills">Skills</button>
<button class="tab" data-panel="session">Session</button>
<button class="tab" data-panel="config">Config</button>
<div class="tabs-inner">
<div class="tab-indicator"></div>
<button class="tab active" data-panel="plan">Plan</button>
<button class="tab" data-panel="skills">Skills</button>
<button class="tab" data-panel="session">Session</button>
<button class="tab" data-panel="config">Config</button>
</div>
</div>
<div id="plan" class="panel active">
@ -412,12 +485,20 @@ let initData = tg.initData || '';
let selectedSkill = null;
// Tab switching — always re-fetch data on tab switch
document.querySelectorAll('.tab').forEach(tab => {
const tabs = document.querySelectorAll('.tab');
const tabIndicator = document.querySelector('.tab-indicator');
function moveIndicator(index) {
tabIndicator.style.transform = 'translateX(' + (index * 100) + '%)';
}
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tabs.forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.panel).classList.add('active');
moveIndicator(index);
document.getElementById('send-bar').style.display =
tab.dataset.panel === 'skills' && selectedSkill ? 'flex' : 'none';