All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m58s
Co-authored-by: Cursor <cursoragent@cursor.com>
749 lines
17 KiB
Vue
749 lines
17 KiB
Vue
<template>
|
||
<div class="h5-dock-shell">
|
||
<nav ref="dockRef" class="h5-dock" aria-label="Customer center tabs">
|
||
<div class="h5-dock__glass" aria-hidden="true" />
|
||
<div
|
||
class="h5-dock__slider"
|
||
:class="{ 'is-ready': sliderReady }"
|
||
:style="sliderStyle"
|
||
aria-hidden="true"
|
||
/>
|
||
|
||
<router-link
|
||
v-for="item in tabs"
|
||
:key="item.path"
|
||
:to="item.path"
|
||
replace
|
||
class="h5-dock__item"
|
||
:class="{ 'is-active': isActive(item.path) }"
|
||
>
|
||
<span class="h5-dock__icon-wrap">
|
||
<component :is="item.icon" class="h5-dock__icon" />
|
||
<span v-if="item.badge && item.badge > 0" class="h5-dock__badge">
|
||
{{ item.badge > 99 ? '99+' : item.badge }}
|
||
</span>
|
||
</span>
|
||
<span class="h5-dock__label">{{ t(item.labelKey) }}</span>
|
||
</router-link>
|
||
</nav>
|
||
|
||
<div class="h5-dock-fab-slot" :class="{ 'is-hidden': hideCreate }">
|
||
<button
|
||
v-show="showFabHint && !hideCreate"
|
||
type="button"
|
||
class="h5-dock-fab-hint"
|
||
@click="dismissFabHint"
|
||
>
|
||
{{ t('h5.tab.createOrder') }}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="h5-dock-fab"
|
||
:tabindex="hideCreate ? -1 : 0"
|
||
:aria-hidden="hideCreate ? 'true' : 'false'"
|
||
:aria-label="t('h5.tab.createOrder')"
|
||
@click="onCreateOrder"
|
||
>
|
||
<span class="h5-dock-fab__glass" aria-hidden="true" />
|
||
<span class="h5-dock-fab__blob" aria-hidden="true" />
|
||
<icon-plus :size="26" class="h5-dock-fab__icon" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||
import { useRoute } from 'vue-router';
|
||
import { useI18n } from 'vue-i18n';
|
||
import { IconPlus } from '@arco-design/web-vue/es/icon';
|
||
import IconTabCalculator from '@/assets/icons/h5/tab-calculator.svg';
|
||
import IconTabPackage from '@/assets/icons/h5/tab-package.svg';
|
||
import IconTabService from '@/assets/icons/h5/tab-service.svg';
|
||
import IconTabMe from '@/assets/icons/h5/tab-me.svg';
|
||
import { isLogin } from '@/utils/auth';
|
||
|
||
defineOptions({ name: 'H5AppTabBar' });
|
||
|
||
const props = defineProps<{
|
||
orderBadge?: number;
|
||
/** 勾选批量时隐藏凸起下单按钮,避免挡住底部操作条 */
|
||
hideCreate?: boolean;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'create-order'): void;
|
||
}>();
|
||
|
||
const { t } = useI18n();
|
||
const route = useRoute();
|
||
const dockRef = ref<HTMLElement | null>(null);
|
||
const sliderReady = ref(false);
|
||
const sliderStyle = ref<Record<string, string>>({
|
||
left: '0px',
|
||
width: '0px',
|
||
opacity: '0',
|
||
});
|
||
const sliderPos = { x: 0, w: 0 };
|
||
const showFabHint = ref(false);
|
||
let settleTimer: ReturnType<typeof setTimeout> | null = null;
|
||
let hideCreateTimer: ReturnType<typeof setTimeout> | null = null;
|
||
let hintShowTimer: ReturnType<typeof setTimeout> | null = null;
|
||
let hintHideTimer: ReturnType<typeof setTimeout> | null = null;
|
||
let sliderAnimating = false;
|
||
|
||
const FAB_HINT_STORAGE_KEY = 'h5_create_order_fab_hint_seen';
|
||
|
||
const tabs = computed(() => [
|
||
{
|
||
path: '/h5/price',
|
||
labelKey: 'h5.tab.price',
|
||
icon: IconTabCalculator,
|
||
},
|
||
{
|
||
path: '/h5/assistant',
|
||
labelKey: 'h5.tab.assistant',
|
||
icon: IconTabService,
|
||
},
|
||
{
|
||
path: '/h5/orders',
|
||
labelKey: 'h5.tab.orders',
|
||
icon: IconTabPackage,
|
||
badge: props.orderBadge || 0,
|
||
},
|
||
{
|
||
path: '/h5/me',
|
||
labelKey: 'h5.tab.me',
|
||
icon: IconTabMe,
|
||
},
|
||
]);
|
||
|
||
function isActive(path: string) {
|
||
return route.path === path || route.path.startsWith(`${path}/`);
|
||
}
|
||
|
||
function hasSeenFabHint() {
|
||
try {
|
||
return localStorage.getItem(FAB_HINT_STORAGE_KEY) === '1';
|
||
} catch {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
function markFabHintSeen() {
|
||
try {
|
||
localStorage.setItem(FAB_HINT_STORAGE_KEY, '1');
|
||
} catch {
|
||
/* 隐私模式无法写入时,至少本页不再反复弹出 */
|
||
}
|
||
}
|
||
|
||
function clearHintTimers() {
|
||
if (hintShowTimer) {
|
||
clearTimeout(hintShowTimer);
|
||
hintShowTimer = null;
|
||
}
|
||
if (hintHideTimer) {
|
||
clearTimeout(hintHideTimer);
|
||
hintHideTimer = null;
|
||
}
|
||
}
|
||
|
||
function dismissFabHint() {
|
||
showFabHint.value = false;
|
||
clearHintTimers();
|
||
markFabHintSeen();
|
||
}
|
||
|
||
function scheduleFabHint() {
|
||
clearHintTimers();
|
||
// 访客点下单会先弹登录,不主动推「下单」气泡
|
||
if (props.hideCreate || hasSeenFabHint() || !isLogin()) {
|
||
showFabHint.value = false;
|
||
return;
|
||
}
|
||
hintShowTimer = setTimeout(() => {
|
||
hintShowTimer = null;
|
||
if (props.hideCreate || hasSeenFabHint() || !isLogin()) return;
|
||
showFabHint.value = true;
|
||
hintHideTimer = setTimeout(() => {
|
||
hintHideTimer = null;
|
||
dismissFabHint();
|
||
}, 4200);
|
||
}, 700);
|
||
}
|
||
|
||
function onCreateOrder() {
|
||
if (props.hideCreate) return;
|
||
dismissFabHint();
|
||
try {
|
||
navigator.vibrate?.(12);
|
||
} catch {
|
||
/* 部分 WebView 不支持震动 */
|
||
}
|
||
emit('create-order');
|
||
}
|
||
|
||
const SLIDER_INSET = 4;
|
||
const LIQUID_STRETCH_MS = 120;
|
||
|
||
function applySlider(x: number, w: number, opacity = '1') {
|
||
sliderPos.x = x;
|
||
sliderPos.w = w;
|
||
sliderStyle.value = {
|
||
left: `${x}px`,
|
||
width: `${w}px`,
|
||
opacity,
|
||
};
|
||
}
|
||
|
||
function readLiveSlider() {
|
||
const slider = dockRef.value?.querySelector<HTMLElement>('.h5-dock__slider');
|
||
if (!slider) return { ...sliderPos };
|
||
const cs = getComputedStyle(slider);
|
||
return {
|
||
x: Number.parseFloat(cs.left) || sliderPos.x,
|
||
w: Number.parseFloat(cs.width) || sliderPos.w,
|
||
};
|
||
}
|
||
|
||
function clearSettleTimer() {
|
||
if (settleTimer) {
|
||
clearTimeout(settleTimer);
|
||
settleTimer = null;
|
||
}
|
||
}
|
||
|
||
function updateSlider(animate: boolean) {
|
||
const dock = dockRef.value;
|
||
const active = dock?.querySelector<HTMLElement>('.h5-dock__item.is-active');
|
||
if (!dock || !active) {
|
||
sliderStyle.value = { ...sliderStyle.value, opacity: '0' };
|
||
sliderAnimating = false;
|
||
return;
|
||
}
|
||
|
||
const x = active.offsetLeft + SLIDER_INSET;
|
||
const w = Math.max(active.offsetWidth - SLIDER_INSET * 2, 0);
|
||
clearSettleTimer();
|
||
|
||
const reduceMotion =
|
||
typeof window !== 'undefined' &&
|
||
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
|
||
if (!animate || !sliderReady.value || sliderPos.w <= 0 || reduceMotion) {
|
||
sliderReady.value = false;
|
||
applySlider(x, w);
|
||
requestAnimationFrame(() => {
|
||
sliderReady.value = true;
|
||
});
|
||
sliderAnimating = false;
|
||
return;
|
||
}
|
||
|
||
const from = readLiveSlider();
|
||
if (Math.abs(from.x - x) < 1 && Math.abs(from.w - w) < 1) {
|
||
applySlider(x, w);
|
||
sliderAnimating = false;
|
||
return;
|
||
}
|
||
|
||
const coverLeft = Math.min(from.x, x);
|
||
const coverWidth = Math.max(from.x + from.w, x + w) - coverLeft;
|
||
sliderAnimating = true;
|
||
applySlider(coverLeft, coverWidth);
|
||
|
||
settleTimer = setTimeout(() => {
|
||
applySlider(x, w);
|
||
sliderAnimating = false;
|
||
settleTimer = null;
|
||
}, LIQUID_STRETCH_MS);
|
||
}
|
||
|
||
async function syncSlider(animate: boolean) {
|
||
await nextTick();
|
||
updateSlider(animate);
|
||
}
|
||
|
||
let resizeObserver: ResizeObserver | null = null;
|
||
|
||
function onViewportResize() {
|
||
if (sliderAnimating) return;
|
||
updateSlider(false);
|
||
}
|
||
|
||
onMounted(() => {
|
||
syncSlider(false);
|
||
scheduleFabHint();
|
||
if (typeof ResizeObserver !== 'undefined') {
|
||
resizeObserver = new ResizeObserver(onViewportResize);
|
||
if (dockRef.value) resizeObserver.observe(dockRef.value);
|
||
}
|
||
window.addEventListener('resize', onViewportResize);
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
clearSettleTimer();
|
||
clearHintTimers();
|
||
if (hideCreateTimer) {
|
||
clearTimeout(hideCreateTimer);
|
||
hideCreateTimer = null;
|
||
}
|
||
resizeObserver?.disconnect();
|
||
window.removeEventListener('resize', onViewportResize);
|
||
});
|
||
|
||
watch(
|
||
() => route.path,
|
||
() => {
|
||
syncSlider(true);
|
||
},
|
||
);
|
||
|
||
watch(
|
||
() => props.hideCreate,
|
||
(hidden) => {
|
||
if (hideCreateTimer) clearTimeout(hideCreateTimer);
|
||
hideCreateTimer = setTimeout(() => {
|
||
hideCreateTimer = null;
|
||
syncSlider(true);
|
||
}, 320);
|
||
if (hidden) {
|
||
showFabHint.value = false;
|
||
clearHintTimers();
|
||
} else {
|
||
scheduleFabHint();
|
||
}
|
||
},
|
||
);
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.h5-dock-shell {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
/* 16px 侧边 + 半高圆角 ≈ iPhone 屏幕连续圆角 */
|
||
padding: 0 var(--h5-dock-side-inset, 16px)
|
||
var(--h5-dock-bottom-inset, max(8px, calc(env(safe-area-inset-bottom, 0px) - 12px)));
|
||
pointer-events: none;
|
||
}
|
||
|
||
.h5-dock {
|
||
position: relative;
|
||
pointer-events: auto;
|
||
display: flex;
|
||
align-items: stretch;
|
||
justify-content: center;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
gap: 0;
|
||
height: var(--h5-dock-height, 68px);
|
||
min-height: var(--h5-dock-height, 68px);
|
||
padding: 5px;
|
||
border-radius: var(--h5-dock-radius, 34px);
|
||
corner-shape: squircle;
|
||
overflow: hidden;
|
||
isolation: isolate;
|
||
background: transparent;
|
||
border: none;
|
||
box-shadow:
|
||
0 12px 40px rgba(15, 23, 42, 0.14),
|
||
0 2px 8px rgba(15, 23, 42, 0.05);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.h5-dock__glass {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
border-radius: inherit;
|
||
corner-shape: inherit;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(255, 255, 255, 0.38) 0%,
|
||
rgba(255, 255, 255, 0.16) 100%
|
||
);
|
||
backdrop-filter: blur(32px) saturate(190%) brightness(1.08);
|
||
-webkit-backdrop-filter: blur(32px) saturate(190%) brightness(1.08);
|
||
|
||
/* 顶部入射高光 */
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
background: linear-gradient(
|
||
165deg,
|
||
rgba(255, 255, 255, 0.62) 0%,
|
||
rgba(255, 255, 255, 0.12) 32%,
|
||
rgba(255, 255, 255, 0) 58%
|
||
);
|
||
}
|
||
|
||
/* Liquid Glass 边缘:上沿更亮、下沿更淡 */
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
padding: 1px;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(255, 255, 255, 0.92) 0%,
|
||
rgba(255, 255, 255, 0.28) 42%,
|
||
rgba(255, 255, 255, 0.4) 100%
|
||
);
|
||
-webkit-mask:
|
||
linear-gradient(#000 0 0) content-box,
|
||
linear-gradient(#000 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask:
|
||
linear-gradient(#000 0 0) content-box,
|
||
linear-gradient(#000 0 0);
|
||
mask-composite: exclude;
|
||
}
|
||
}
|
||
|
||
.h5-dock__slider {
|
||
position: absolute;
|
||
top: 5px;
|
||
bottom: 5px;
|
||
border-radius: calc(var(--h5-dock-radius, 34px) - 5px);
|
||
corner-shape: squircle;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(255, 255, 255, 0.72) 0%,
|
||
rgba(255, 255, 255, 0.42) 100%
|
||
);
|
||
backdrop-filter: blur(10px) saturate(140%) brightness(1.12);
|
||
-webkit-backdrop-filter: blur(10px) saturate(140%) brightness(1.12);
|
||
box-shadow:
|
||
0 4px 16px rgba(15, 23, 42, 0.08),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
||
inset 0 -1px 0 rgba(255, 255, 255, 0.2);
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
opacity: 0;
|
||
|
||
&:not(.is-ready) {
|
||
transition: none;
|
||
}
|
||
|
||
/* 先拉长覆盖目标 Tab,再弹簧收回,接近 iOS 26 液态滑块 */
|
||
&.is-ready {
|
||
transition:
|
||
left 0.42s cubic-bezier(0.32, 0.72, 0, 1),
|
||
width 0.42s cubic-bezier(0.32, 0.72, 0, 1),
|
||
opacity 0.2s ease;
|
||
}
|
||
}
|
||
|
||
.h5-dock__item {
|
||
position: relative;
|
||
z-index: 2;
|
||
flex: 1 1 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 3px;
|
||
min-width: 0;
|
||
padding: 0 2px;
|
||
color: #86909c;
|
||
text-decoration: none;
|
||
background: transparent;
|
||
border: none;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
touch-action: manipulation;
|
||
transition: color 0.22s ease;
|
||
|
||
&.is-active {
|
||
color: var(--app-primary-color);
|
||
|
||
.h5-dock__icon {
|
||
animation: h5-dock-icon-pop 0.42s cubic-bezier(0.22, 1.35, 0.36, 1);
|
||
}
|
||
|
||
.h5-dock__label {
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.96);
|
||
}
|
||
}
|
||
|
||
.h5-dock__icon-wrap {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 36px;
|
||
height: 32px;
|
||
border-radius: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.h5-dock__icon {
|
||
font-size: 24px;
|
||
width: 1em;
|
||
height: 1em;
|
||
}
|
||
|
||
.h5-dock__badge {
|
||
position: absolute;
|
||
top: -4px;
|
||
right: -2px;
|
||
min-width: 16px;
|
||
height: 16px;
|
||
padding: 0 4px;
|
||
border-radius: 8px;
|
||
background: #f53f3f;
|
||
color: #fff;
|
||
font-size: 10px;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
box-sizing: border-box;
|
||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.92);
|
||
}
|
||
|
||
.h5-dock__label {
|
||
max-width: 100%;
|
||
font-size: 11px;
|
||
line-height: 1.15;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
text-align: center;
|
||
}
|
||
|
||
.h5-dock-fab-slot {
|
||
position: relative;
|
||
flex: 0 0 var(--h5-dock-fab-size, 56px);
|
||
width: var(--h5-dock-fab-size, 56px);
|
||
height: var(--h5-dock-fab-size, 56px);
|
||
margin-left: 12px;
|
||
overflow: visible;
|
||
pointer-events: none;
|
||
transition:
|
||
flex-basis 0.32s cubic-bezier(0.32, 0.72, 0, 1),
|
||
width 0.32s cubic-bezier(0.32, 0.72, 0, 1),
|
||
margin-left 0.32s cubic-bezier(0.32, 0.72, 0, 1),
|
||
opacity 0.22s ease;
|
||
|
||
&.is-hidden {
|
||
overflow: hidden;
|
||
flex-basis: 0;
|
||
width: 0;
|
||
margin-left: 0;
|
||
opacity: 0;
|
||
|
||
.h5-dock-fab,
|
||
.h5-dock-fab-hint {
|
||
pointer-events: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.h5-dock-fab-hint {
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: calc(100% + 10px);
|
||
z-index: 3;
|
||
transform: translateX(-50%);
|
||
pointer-events: auto;
|
||
padding: 6px 10px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: #1d2129;
|
||
color: #fff;
|
||
font-size: 12px;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 100%;
|
||
width: 0;
|
||
height: 0;
|
||
border: 6px solid transparent;
|
||
border-top-color: #1d2129;
|
||
transform: translateX(-50%);
|
||
}
|
||
}
|
||
|
||
/* 右侧独立圆形下单 FAB:液态玻璃 */
|
||
.h5-dock-fab {
|
||
position: relative;
|
||
pointer-events: auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: var(--h5-dock-fab-size, 56px);
|
||
height: var(--h5-dock-fab-size, 56px);
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
isolation: isolate;
|
||
color: var(--app-primary-color);
|
||
background: transparent;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
touch-action: manipulation;
|
||
transform-origin: center;
|
||
transition: transform 0.22s cubic-bezier(0.22, 1.2, 0.36, 1);
|
||
|
||
&:active {
|
||
transform: scale(0.9);
|
||
|
||
.h5-dock-fab__blob {
|
||
transform: translate(18%, 22%) scale(1.28);
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.h5-dock-fab__icon {
|
||
transform: scale(0.92);
|
||
}
|
||
}
|
||
}
|
||
|
||
.h5-dock-fab__glass {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
border-radius: inherit;
|
||
pointer-events: none;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(255, 255, 255, 0.38) 0%,
|
||
rgba(255, 255, 255, 0.16) 100%
|
||
);
|
||
backdrop-filter: blur(32px) saturate(190%) brightness(1.08);
|
||
-webkit-backdrop-filter: blur(32px) saturate(190%) brightness(1.08);
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
background: linear-gradient(
|
||
155deg,
|
||
rgba(255, 255, 255, 0.72) 0%,
|
||
rgba(255, 255, 255, 0.16) 36%,
|
||
rgba(255, 255, 255, 0) 62%
|
||
);
|
||
}
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
padding: 1px;
|
||
background: linear-gradient(
|
||
160deg,
|
||
rgba(255, 255, 255, 0.95) 0%,
|
||
rgba(255, 255, 255, 0.22) 42%,
|
||
rgba(255, 255, 255, 0.4) 100%
|
||
);
|
||
-webkit-mask:
|
||
linear-gradient(#000 0 0) content-box,
|
||
linear-gradient(#000 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask:
|
||
linear-gradient(#000 0 0) content-box,
|
||
linear-gradient(#000 0 0);
|
||
mask-composite: exclude;
|
||
}
|
||
}
|
||
|
||
.h5-dock-fab__blob {
|
||
position: absolute;
|
||
left: 8%;
|
||
top: 4%;
|
||
z-index: 1;
|
||
width: 62%;
|
||
height: 52%;
|
||
border-radius: 50%;
|
||
pointer-events: none;
|
||
background: radial-gradient(
|
||
circle at 40% 35%,
|
||
rgba(255, 255, 255, 0.78) 0%,
|
||
rgba(255, 255, 255, 0.18) 48%,
|
||
rgba(255, 255, 255, 0) 72%
|
||
);
|
||
filter: blur(1.2px);
|
||
opacity: 0.85;
|
||
transform: translate(0, 0) scale(1);
|
||
transition:
|
||
transform 0.42s cubic-bezier(0.32, 0.72, 0, 1),
|
||
opacity 0.22s ease;
|
||
}
|
||
|
||
.h5-dock-fab__icon {
|
||
position: relative;
|
||
z-index: 2;
|
||
color: var(--app-primary-color);
|
||
pointer-events: none;
|
||
transition: transform 0.22s cubic-bezier(0.22, 1.2, 0.36, 1);
|
||
}
|
||
|
||
@keyframes h5-dock-icon-pop {
|
||
0% {
|
||
transform: scale(0.88);
|
||
}
|
||
70% {
|
||
transform: scale(1.08);
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 380px) {
|
||
.h5-dock__icon {
|
||
font-size: 22px;
|
||
}
|
||
|
||
.h5-dock__label {
|
||
font-size: 10px;
|
||
}
|
||
|
||
.h5-dock-fab-slot,
|
||
.h5-dock-fab {
|
||
--h5-dock-fab-size: 52px;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.h5-dock__slider.is-ready {
|
||
transition: none;
|
||
}
|
||
|
||
.h5-dock__item.is-active .h5-dock__icon {
|
||
animation: none;
|
||
}
|
||
|
||
.h5-dock-fab-slot,
|
||
.h5-dock-fab {
|
||
transition: none;
|
||
}
|
||
}
|
||
</style>
|