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 <noreply@anthropic.com>
This commit is contained in:
parent
39aaacc1a1
commit
1c804f23ff
1 changed files with 58 additions and 12 deletions
|
|
@ -17,6 +17,10 @@
|
||||||
--done: #34c759;
|
--done: #34c759;
|
||||||
--current: var(--btn);
|
--current: var(--btn);
|
||||||
--pending-phase: var(--hint);
|
--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) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
@ -28,6 +32,10 @@
|
||||||
--btn: var(--tg-theme-button-color, #5ac8fa);
|
--btn: var(--tg-theme-button-color, #5ac8fa);
|
||||||
--btn-text: var(--tg-theme-button-text-color, #ffffff);
|
--btn-text: var(--tg-theme-button-text-color, #ffffff);
|
||||||
--secondary-bg: var(--tg-theme-secondary-bg-color, #2c2c2e);
|
--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 {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: var(--secondary-bg);
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
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 {
|
.tab {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12px 4px;
|
padding: 7px 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
@ -59,13 +92,15 @@
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 2px solid transparent;
|
transition: color 0.25s ease;
|
||||||
transition: all 0.2s;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab.active {
|
.tab.active {
|
||||||
color: var(--btn);
|
color: var(--text);
|
||||||
border-bottom-color: var(--btn);
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel { display: none; padding: 16px; }
|
.panel { display: none; padding: 16px; }
|
||||||
|
|
@ -358,10 +393,13 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab active" data-panel="plan">Plan</button>
|
<div class="tabs-inner">
|
||||||
<button class="tab" data-panel="skills">Skills</button>
|
<div class="tab-indicator"></div>
|
||||||
<button class="tab" data-panel="session">Session</button>
|
<button class="tab active" data-panel="plan">Plan</button>
|
||||||
<button class="tab" data-panel="config">Config</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>
|
||||||
|
|
||||||
<div id="plan" class="panel active">
|
<div id="plan" class="panel active">
|
||||||
|
|
@ -412,12 +450,20 @@ let initData = tg.initData || '';
|
||||||
let selectedSkill = null;
|
let selectedSkill = null;
|
||||||
|
|
||||||
// Tab switching — always re-fetch data on tab switch
|
// 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', () => {
|
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'));
|
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
||||||
tab.classList.add('active');
|
tab.classList.add('active');
|
||||||
document.getElementById(tab.dataset.panel).classList.add('active');
|
document.getElementById(tab.dataset.panel).classList.add('active');
|
||||||
|
moveIndicator(index);
|
||||||
|
|
||||||
document.getElementById('send-bar').style.display =
|
document.getElementById('send-bar').style.display =
|
||||||
tab.dataset.panel === 'skills' && selectedSkill ? 'flex' : 'none';
|
tab.dataset.panel === 'skills' && selectedSkill ? 'flex' : 'none';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue