From 1c804f23ff64268b08563402439e60cfc34cbae6 Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Sun, 22 Feb 2026 08:01:36 +0900 Subject: [PATCH 1/3] feat: apply Apple Liquid Glass design to miniapp tab bar Replace flat tab bar with frosted glass segmented control featuring backdrop blur, sliding pill indicator with spring animation, and light/dark theme support. Co-Authored-By: Claude Opus 4.6 --- pkg/miniapp/static/index.html | 70 +++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/pkg/miniapp/static/index.html b/pkg/miniapp/static/index.html index e8e287534..e28b0fb75 100644 --- a/pkg/miniapp/static/index.html +++ b/pkg/miniapp/static/index.html @@ -17,6 +17,10 @@ --done: #34c759; --current: var(--btn); --pending-phase: var(--hint); + /* Liquid Glass */ + --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 +32,10 @@ --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 */ + --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 +51,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,13 +92,15 @@ 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; } @@ -358,10 +393,13 @@
- - - - +
+
+ + + + +
@@ -412,12 +450,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'; From 36b5d6cd1885c80ad417bf3166def5a3e5572d6c Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Sun, 22 Feb 2026 08:12:21 +0900 Subject: [PATCH 2/3] 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 --- pkg/miniapp/static/index.html | 241 +++++++++++++++++++++++----------- 1 file changed, 161 insertions(+), 80 deletions(-) diff --git a/pkg/miniapp/static/index.html b/pkg/miniapp/static/index.html index e8e287534..e3a3a4218 100644 --- a/pkg/miniapp/static/index.html +++ b/pkg/miniapp/static/index.html @@ -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); }
- - - - +
+
+ + + + +
@@ -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'; From c6f74c7273461e8ac29f1ca27f8b7a2d09a20160 Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Sun, 22 Feb 2026 08:20:14 +0900 Subject: [PATCH 3/3] fix: increase border contrast on interactive elements for tap affordance Add --glass-border-interactive with higher opacity for tappable elements (tiles, skills, steps, inputs, refresh) while keeping subtle --glass-border for passive containers like cards. Co-Authored-By: Claude Opus 4.6 --- pkg/miniapp/static/index.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/miniapp/static/index.html b/pkg/miniapp/static/index.html index e3a3a4218..90c174a92 100644 --- a/pkg/miniapp/static/index.html +++ b/pkg/miniapp/static/index.html @@ -19,7 +19,8 @@ --pending-phase: var(--hint); /* Liquid Glass */ --glass-bg: rgba(255, 255, 255, 0.55); - --glass-border: rgba(255, 255, 255, 0.7); + --glass-border: rgba(0, 0, 0, 0.08); + --glass-border-interactive: rgba(0, 0, 0, 0.15); --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); @@ -38,7 +39,8 @@ --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-border: rgba(255, 255, 255, 0.1); + --glass-border-interactive: rgba(255, 255, 255, 0.22); --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); @@ -192,7 +194,7 @@ .step:active { transform: scale(0.97); } .step:not(.step-done) { background: var(--glass-bg); - border: 1px solid var(--glass-border); + border: 1px solid var(--glass-border-interactive); box-shadow: 0 0.5px 2px var(--glass-shadow); } @@ -243,7 +245,7 @@ margin-bottom: 10px; cursor: pointer; 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); + border: 1px solid var(--glass-border-interactive); display: flex; align-items: center; gap: 12px; @@ -325,7 +327,7 @@ flex: 1; padding: 10px 16px; border-radius: 20px; - border: 1px solid var(--glass-border); + border: 1px solid var(--glass-border-interactive); background: var(--glass-bg); color: var(--text); font-size: 14px; @@ -377,7 +379,7 @@ .cmd-tile { padding: 16px 14px; border-radius: 14px; - border: 1px solid var(--glass-border); + border: 1px solid var(--glass-border-interactive); background: var(--glass-bg); color: var(--text); font-size: 15px; @@ -406,7 +408,7 @@ width: 100%; padding: 12px 16px; border-radius: 14px; - border: 1px solid var(--glass-border); + border: 1px solid var(--glass-border-interactive); background: var(--glass-bg); color: var(--text); font-size: 14px;