feat(float): rebuild right floating buttons 1:1 with reference theme
- Storage: aksrt_floating_buttons now uses reference float_btn shape (fixed 8 sortable types, more-buttons group); single source of defaults/sanitize in AKSRT\Settings\FloatButtons - Admin SPA: CSF-parity page split into components/floating/* - Frontend: aksrt_float_right() on wp_footer mirrors zib_float_right() (wp_is_mobile, PC tooltip, toggle-theme, ontop/scrollTopTo, qrcode, wechat/qq, pay_vip, add, more) - CSS: floating-buttons.css rewritten from reference rules; legacy float rules removed from main/part/shell/overlay-primitives - Remove dead code: reorder route/API, get_floating_buttons_items, floatingSchema, floatActionsId, float-theme-toggle bindings, common/floating.css, aksrt-runtime-win.php, .aksrt-float-btn
This commit is contained in:
@@ -11,7 +11,6 @@ export const globalApiMethods = {
|
||||
|
||||
getFloatingButtons() { return this.request('/settings/floating-buttons') },
|
||||
saveFloatingButtons(data) { return this.request('/settings/floating-buttons', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
reorderFloatingButtons(ids) { return this.request('/settings/floating-buttons/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) },
|
||||
|
||||
getFriendLinks() { return this.request('/friend-links') },
|
||||
addFriendLink(data) { return this.request('/friend-links', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<n-form label-placement="left" label-width="110" class="aksrt-form float-common-fields">
|
||||
<n-form-item label="PC端显示" :show-feedback="false">
|
||||
<n-switch :value="!!modelValue.pc_s" @update:value="update('pc_s', $event)" />
|
||||
</n-form-item>
|
||||
<n-form-item label="移动端显示" :show-feedback="false">
|
||||
<n-switch :value="!!modelValue.m_s" @update:value="update('m_s', $event)" />
|
||||
</n-form-item>
|
||||
<n-form-item label="颜色" :show-feedback="false">
|
||||
<div class="float-common-fields__colors">
|
||||
<div class="float-common-fields__color">
|
||||
<span>自定义图标颜色</span>
|
||||
<n-color-picker
|
||||
:value="modelValue.color?.color || null"
|
||||
:show-alpha="true"
|
||||
:modes="['hex', 'rgb']"
|
||||
clearable
|
||||
@update:value="updateColor('color', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="float-common-fields__color">
|
||||
<span>自定义背景颜色</span>
|
||||
<n-color-picker
|
||||
:value="modelValue.color?.bg || null"
|
||||
:show-alpha="true"
|
||||
:modes="['hex', 'rgb']"
|
||||
clearable
|
||||
@update:value="updateColor('bg', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<div class="float-common-fields__desc">自定义颜色请注意日间、夜间模式的适配</div>
|
||||
</n-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
function update(key, value) {
|
||||
emit('update:modelValue', { ...props.modelValue, [key]: value })
|
||||
}
|
||||
|
||||
function updateColor(key, value) {
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
color: { ...(props.modelValue.color || { color: '', bg: '' }), [key]: value || '' },
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.float-common-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.float-common-fields__colors {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.float-common-fields__color {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 180px;
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-3);
|
||||
}
|
||||
|
||||
.float-common-fields__desc {
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-3);
|
||||
padding-left: 110px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="float-btn-panel">
|
||||
<div class="float-btn-panel__head" @click="expanded = !expanded">
|
||||
<button type="button" class="float-btn-panel__handle" aria-label="拖拽排序" @click.stop>
|
||||
<i class="fa fas fa-arrows-alt-v" />
|
||||
</button>
|
||||
<strong class="float-btn-panel__title">{{ meta.title }}</strong>
|
||||
<template v-if="modelValue.type !== 'more'">
|
||||
<n-tag v-if="modelValue.pc_s" size="small" type="info" :bordered="false">PC</n-tag>
|
||||
<n-tag v-if="modelValue.m_s" size="small" type="warning" :bordered="false">移动</n-tag>
|
||||
<n-tag v-if="!modelValue.pc_s && !modelValue.m_s" size="small" :bordered="false">未显示</n-tag>
|
||||
</template>
|
||||
<n-tag v-else size="small" :bordered="false">{{ modelValue.buttons.length }} 个</n-tag>
|
||||
<i class="fa fas float-btn-panel__arrow" :class="expanded ? 'fa-angle-up' : 'fa-angle-down'" />
|
||||
</div>
|
||||
|
||||
<div v-if="meta.warn" class="float-btn-panel__warn">
|
||||
<i class="fa fas fa-info-circle" /> {{ meta.warn }}
|
||||
</div>
|
||||
|
||||
<div v-show="expanded" class="float-btn-panel__body">
|
||||
<div class="float-btn-panel__subtitle">{{ meta.panel }}</div>
|
||||
|
||||
<FloatMoreButtonsEditor
|
||||
v-if="modelValue.type === 'more'"
|
||||
:model-value="modelValue.buttons"
|
||||
@update:model-value="patch({ buttons: $event })"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<FloatBtnCommonFields :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" />
|
||||
|
||||
<n-form label-placement="left" label-width="110" class="aksrt-form float-btn-panel__extra">
|
||||
<n-form-item v-if="modelValue.type === 'qrcode'" label="一句话简介">
|
||||
<n-input :value="modelValue.desc" @update:value="patch({ desc: $event })" />
|
||||
</n-form-item>
|
||||
|
||||
<template v-if="modelValue.type === 'add'">
|
||||
<n-form-item label="选择按钮">
|
||||
<div class="float-btn-panel__block">
|
||||
<n-select
|
||||
:value="modelValue.btns"
|
||||
:options="ADD_BTN_OPTIONS"
|
||||
multiple
|
||||
placeholder="请选择按钮"
|
||||
@update:value="patch({ btns: $event })"
|
||||
/>
|
||||
<div class="float-btn-panel__desc">选择并排序按钮,未开启的功能不会显示</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="按钮图标">
|
||||
<FaIconPicker v-model="addIcon" placeholder="选择图标" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<n-form-item v-if="modelValue.type === 'service_qq'" label="QQ号码">
|
||||
<n-input :value="modelValue.qq" placeholder="仅填写数字" @update:value="patch({ qq: $event })" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item v-if="modelValue.type === 'service_wechat'" label="微信二维码">
|
||||
<AksMediaField
|
||||
:model-value="modelValue.wechat_img"
|
||||
placeholder="选择二维码图片"
|
||||
@update:model-value="patch({ wechat_img: $event })"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||
import AksMediaField from '@/components/form/AksMediaField.vue'
|
||||
import FloatBtnCommonFields from './FloatBtnCommonFields.vue'
|
||||
import FloatMoreButtonsEditor from './FloatMoreButtonsEditor.vue'
|
||||
import { ADD_BTN_OPTIONS, FLOAT_BTN_META } from './floating-defaults.js'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const expanded = ref(false)
|
||||
const meta = computed(() => FLOAT_BTN_META[props.modelValue.type] || { title: props.modelValue.type, panel: '' })
|
||||
|
||||
const addIcon = computed({
|
||||
get: () => props.modelValue.icon || '',
|
||||
set: (value) => patch({ icon: value || '' }),
|
||||
})
|
||||
|
||||
function patch(partial) {
|
||||
emit('update:modelValue', { ...props.modelValue, ...partial })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.float-btn-panel {
|
||||
border: 1px solid var(--n-border-color, rgba(0, 0, 0, 0.09));
|
||||
border-radius: 8px;
|
||||
background: var(--n-card-color, #fff);
|
||||
}
|
||||
|
||||
.float-btn-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.float-btn-panel__handle {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--n-text-color-3, #999);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.float-btn-panel__handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.float-btn-panel__title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.float-btn-panel__arrow {
|
||||
margin-left: auto;
|
||||
color: var(--n-text-color-3, #999);
|
||||
}
|
||||
|
||||
.float-btn-panel__warn {
|
||||
padding: 0 14px 10px 48px;
|
||||
font-size: 12px;
|
||||
color: #f0a020;
|
||||
}
|
||||
|
||||
.float-btn-panel__body {
|
||||
padding: 12px 16px 16px;
|
||||
border-top: 1px solid var(--n-border-color, rgba(0, 0, 0, 0.09));
|
||||
}
|
||||
|
||||
.float-btn-panel__subtitle {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.float-btn-panel__extra {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.float-btn-panel__block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.float-btn-panel__desc {
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-3, #999);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div class="float-more-editor">
|
||||
<n-collapse :default-expanded-names="[]" accordion>
|
||||
<n-collapse-item
|
||||
v-for="(btn, index) in modelValue"
|
||||
:key="index"
|
||||
:name="index"
|
||||
>
|
||||
<template #header>
|
||||
<span class="float-more-editor__head">
|
||||
<IconValue v-if="btn.icon" :value="btn.icon" />
|
||||
<span>{{ btn.desc || `按钮 ${index + 1}` }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
type="error"
|
||||
:disabled="modelValue.length <= 1"
|
||||
@click.stop="remove(index)"
|
||||
>
|
||||
<template #icon><i class="fa fas fa-trash-alt" /></template>
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<n-form label-placement="left" label-width="110" class="aksrt-form">
|
||||
<n-form-item label="按钮简介">
|
||||
<n-input :value="btn.desc" @update:value="patch(index, { desc: $event })" />
|
||||
</n-form-item>
|
||||
<n-form-item label="按钮图标">
|
||||
<FaIconPicker :model-value="btn.icon" placeholder="选择图标" @update:model-value="patch(index, { icon: $event || '' })" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="!btn.hover" label="按钮链接">
|
||||
<div class="float-more-editor__link">
|
||||
<n-input
|
||||
:value="btn.link.url"
|
||||
placeholder="https://"
|
||||
@update:value="patch(index, { link: { ...btn.link, url: $event } })"
|
||||
/>
|
||||
<n-checkbox
|
||||
:checked="btn.link.target === '_blank'"
|
||||
@update:checked="patch(index, { link: { ...btn.link, target: $event ? '_blank' : '' } })"
|
||||
>
|
||||
新窗口打开
|
||||
</n-checkbox>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="悬浮内容">
|
||||
<div class="float-more-editor__block">
|
||||
<div class="float-more-editor__subtitle">鼠标移到此处时显示内容</div>
|
||||
<n-input
|
||||
type="textarea"
|
||||
:value="btn.hover"
|
||||
:autosize="{ minRows: 2, maxRows: 8 }"
|
||||
@update:value="patch(index, { hover: $event })"
|
||||
/>
|
||||
<div class="float-more-editor__desc">设置此处后,按钮链接将失效<br>支持HTML代码,请注意代码规范及标签闭合</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="btn.hover" label="悬浮内容宽度">
|
||||
<div class="float-more-editor__block">
|
||||
<n-input-number
|
||||
:value="btn.hover_width"
|
||||
:min="50"
|
||||
:max="500"
|
||||
:step="10"
|
||||
style="width: 180px;"
|
||||
@update:value="patch(index, { hover_width: $event ?? 200 })"
|
||||
>
|
||||
<template #suffix>PX</template>
|
||||
</n-input-number>
|
||||
<div class="float-more-editor__desc">移动端宽度最大240px</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<FloatBtnCommonFields :model-value="btn" @update:model-value="replace(index, $event)" />
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
|
||||
<div class="float-more-editor__footer">
|
||||
<n-button size="small" type="primary" @click="add">
|
||||
<template #icon><i class="fa fas fa-plus" /></template>
|
||||
添加按钮
|
||||
</n-button>
|
||||
<span class="float-more-editor__desc">此处内容至少保留一个,如一个都无需显示请关闭PC端和移动端显示即可</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||
import IconValue from '@/components/form/IconValue.vue'
|
||||
import FloatBtnCommonFields from './FloatBtnCommonFields.vue'
|
||||
import { defaultMoreButton } from './floating-defaults.js'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Array, required: true },
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
function replace(index, value) {
|
||||
const next = props.modelValue.slice()
|
||||
next.splice(index, 1, value)
|
||||
emit('update:modelValue', next)
|
||||
}
|
||||
|
||||
function patch(index, partial) {
|
||||
replace(index, { ...props.modelValue[index], ...partial })
|
||||
}
|
||||
|
||||
function add() {
|
||||
emit('update:modelValue', [...props.modelValue, defaultMoreButton()])
|
||||
}
|
||||
|
||||
function remove(index) {
|
||||
if (props.modelValue.length <= 1) return
|
||||
const next = props.modelValue.slice()
|
||||
next.splice(index, 1)
|
||||
emit('update:modelValue', next)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.float-more-editor__head {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.float-more-editor__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.float-more-editor__block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.float-more-editor__subtitle,
|
||||
.float-more-editor__desc {
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-3);
|
||||
}
|
||||
|
||||
.float-more-editor__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 右侧悬浮按钮:类型元数据与默认值(与 src/Settings/FloatButtons.php 保持一致)。
|
||||
*/
|
||||
|
||||
export const emptyColor = () => ({ color: '', bg: '' })
|
||||
|
||||
/** 类型元数据:面板标题、折叠标题、黄色提示。 */
|
||||
export const FLOAT_BTN_META = {
|
||||
back_top: { title: '返回顶部', panel: '返回顶部', warn: '此按钮只能放置在最顶部或者最底部!' },
|
||||
qrcode: { title: '当前页面二维码', panel: '当前页面二维码' },
|
||||
add: { title: 'Add发布内容', panel: '发布投稿', warn: '此按钮尽量放在上方,否则会显示遮挡' },
|
||||
theme_mode: { title: '日夜主题切换', panel: '日夜主题切换' },
|
||||
pay_vip: { title: 'VIP购买', panel: 'VIP购买' },
|
||||
service_qq: { title: 'QQ客服', panel: 'QQ客服' },
|
||||
service_wechat: { title: '微信客服', panel: '微信客服' },
|
||||
more: { title: '更多按钮', panel: '添加更多自定义按钮' },
|
||||
}
|
||||
|
||||
/** 发布按钮可选项。 */
|
||||
export const ADD_BTN_OPTIONS = [{ label: '文章投稿', value: 'post' }]
|
||||
|
||||
export function defaultMoreButton() {
|
||||
return {
|
||||
desc: '',
|
||||
icon: 'fa fas fa-heart',
|
||||
link: { url: '', target: '' },
|
||||
hover: '',
|
||||
hover_width: 200,
|
||||
pc_s: false,
|
||||
m_s: false,
|
||||
color: { color: '', bg: 'rgba(255, 111, 6, 0.2)' },
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultItems() {
|
||||
return [
|
||||
{ type: 'more', buttons: [defaultMoreButton()] },
|
||||
{ type: 'pay_vip', pc_s: true, m_s: true, color: { color: '#f2c97d', bg: 'rgba(62,62,67,0.9)' } },
|
||||
{ type: 'add', pc_s: true, m_s: false, color: emptyColor(), btns: ['post'], icon: 'fa fas fa-pencil-alt' },
|
||||
{ type: 'service_qq', pc_s: false, m_s: false, color: emptyColor(), qq: '' },
|
||||
{ type: 'service_wechat', pc_s: true, m_s: true, color: emptyColor(), wechat_img: '' },
|
||||
{ type: 'theme_mode', pc_s: false, m_s: false, color: emptyColor() },
|
||||
{ type: 'qrcode', pc_s: true, m_s: false, color: emptyColor(), desc: '在手机上浏览此页面' },
|
||||
{ type: 'back_top', pc_s: true, m_s: true, color: emptyColor() },
|
||||
]
|
||||
}
|
||||
|
||||
export function defaultSettings() {
|
||||
return {
|
||||
style: 'round',
|
||||
position: 'bottom',
|
||||
filter_blur: ['m_s'],
|
||||
scroll_hide: false,
|
||||
scroll_down_hide: true,
|
||||
items: defaultItems(),
|
||||
}
|
||||
}
|
||||
|
||||
/** 规范化颜色组:确保 color/bg 都为字符串。 */
|
||||
export function normalizeColor(color) {
|
||||
const c = color && typeof color === 'object' ? color : {}
|
||||
return { color: c.color || '', bg: c.bg || '' }
|
||||
}
|
||||
|
||||
/** 用默认值补齐服务端返回的按钮(按服务端顺序)。 */
|
||||
export function normalizeItems(items) {
|
||||
const defaults = Object.fromEntries(defaultItems().map((item) => [item.type, item]))
|
||||
const source = Array.isArray(items) && items.length ? items : defaultItems()
|
||||
const out = []
|
||||
const seen = new Set()
|
||||
for (const raw of source) {
|
||||
const base = defaults[raw?.type]
|
||||
if (!base || seen.has(raw.type)) continue
|
||||
seen.add(raw.type)
|
||||
if (raw.type === 'more') {
|
||||
const buttons = (Array.isArray(raw.buttons) && raw.buttons.length ? raw.buttons : base.buttons).map((btn) => ({
|
||||
...defaultMoreButton(),
|
||||
...btn,
|
||||
link: { url: btn?.link?.url || '', target: btn?.link?.target || '' },
|
||||
color: normalizeColor(btn?.color),
|
||||
}))
|
||||
out.push({ type: 'more', buttons })
|
||||
continue
|
||||
}
|
||||
out.push({ ...base, ...raw, color: normalizeColor(raw.color) })
|
||||
}
|
||||
for (const item of defaultItems()) {
|
||||
if (!seen.has(item.type)) out.push(item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
export const floatingSchema = {
|
||||
key: 'global.floating',
|
||||
title: '右侧悬浮按钮',
|
||||
icon: 'fa fas fa-ellipsis-v',
|
||||
description: '页面右侧浮动按钮的显示与配置。',
|
||||
defaults: {},
|
||||
groups: [],
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { layoutSchema } from './layout.js'
|
||||
import { searchSchema } from './search.js'
|
||||
// Email schema is served by the PHP template catalog to keep defaults and fields canonical.
|
||||
import { tabbarSchema } from './tabbar.js'
|
||||
import { floatingSchema } from './floating.js'
|
||||
import { i18nSchema } from './i18n.js'
|
||||
import { customCodeSchema } from './custom-code.js'
|
||||
import { seoSchema } from './seo.js'
|
||||
@@ -23,7 +22,6 @@ export const globalSchemas = {
|
||||
search: searchSchema,
|
||||
email: null,
|
||||
tabbar: tabbarSchema,
|
||||
floating: floatingSchema,
|
||||
i18n: i18nSchema,
|
||||
'custom-code': customCodeSchema,
|
||||
seo: seoSchema,
|
||||
|
||||
+112
-472
@@ -3426,16 +3426,6 @@ html.dark .footer-tabbar--glass {
|
||||
box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Glass on floating buttons (lighter blur: saturate(2) blur(10px))
|
||||
* ============================================================ */
|
||||
.float-right--glass .float-btn {
|
||||
-webkit-backdrop-filter: saturate(2) blur(10px);
|
||||
backdrop-filter: saturate(2) blur(10px);
|
||||
background: var(--float-btn-bg);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Scroll shadow on glass header (legacy: .body-scroll .header)
|
||||
* ============================================================ */
|
||||
@@ -4094,101 +4084,6 @@ body.site-layout-3 .sidebar,
|
||||
.hide-on-pc { display: none !important; }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 浮动右侧按钮组(模仿 legacy float-right)
|
||||
* ============================================================ */
|
||||
/* 对齐 legacy: bottom 60px, z-index 1030 */
|
||||
.float-right-btns {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 60px;
|
||||
z-index: 1030;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* 对齐 legacy: 40px, 1.4em icon */
|
||||
.float-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
min-height: 40px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--float-btn-bg);
|
||||
color: var(--color-ink-3);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
|
||||
font-size: 1.4em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.float-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--color-primary);
|
||||
background: var(--surface-page);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.float-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 回到顶部按钮 — 滚动后显示 */
|
||||
.float-btn--top {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.float-btn--top.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 主题切换按钮 */
|
||||
#float-theme-toggle .float-btn__icon-moon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#float-theme-toggle .float-btn__icon-sun {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#float-theme-toggle[aria-pressed="true"] .float-btn__icon-moon,
|
||||
.theme-dark #float-theme-toggle .float-btn__icon-moon,
|
||||
html.dark #float-theme-toggle .float-btn__icon-moon,
|
||||
body.dark-theme #float-theme-toggle .float-btn__icon-moon {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#float-theme-toggle[aria-pressed="true"] .float-btn__icon-sun,
|
||||
.theme-dark #float-theme-toggle .float-btn__icon-sun,
|
||||
html.dark #float-theme-toggle .float-btn__icon-sun,
|
||||
body.dark-theme #float-theme-toggle .float-btn__icon-sun {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 移动端隐藏浮动按钮(用 tabbar 代替) */
|
||||
@media (max-width: 767px) {
|
||||
.float-right-btns {
|
||||
right: 12px;
|
||||
bottom: 80px;
|
||||
}
|
||||
|
||||
.float-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 移动端底部 Tab 栏(模仿 legacy footer-tabbar)
|
||||
* ============================================================ */
|
||||
@@ -4679,12 +4574,6 @@ html.dark .site-footer {
|
||||
background: var(--surface-1);
|
||||
}
|
||||
|
||||
.theme-dark .float-btn,
|
||||
html.dark .float-btn {
|
||||
background: var(--float-btn-bg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.theme-dark .footer-tabbar,
|
||||
html.dark .footer-tabbar {
|
||||
background: var(--blur-bg);
|
||||
@@ -5064,421 +4953,172 @@ html.dark .footer-tabbar {
|
||||
}
|
||||
}
|
||||
|
||||
/* Float right buttons shell (kept from prior shell) */
|
||||
#float-right-btns.float-right,
|
||||
.float-right {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 60px;
|
||||
z-index: 1030;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* source: assets/css/frontend/floating-buttons.css */
|
||||
/* ============================================================
|
||||
* Floating Buttons
|
||||
* 右侧悬浮按钮(结构/样式对齐参考主题 .float-right)
|
||||
* 依赖 main.css 基础规则:.hover-show / .dropdown-menu(::before) /
|
||||
* .drop-newadd / .fa-toggle-theme / badge
|
||||
* ============================================================ */
|
||||
|
||||
.float-right {
|
||||
--float-btn-size: 42px;
|
||||
--float-btn-spacing: 8px;
|
||||
--float-btn-opacity: 1;
|
||||
--float-btn-color: var(--color-ink-3);
|
||||
--float-btn-bg: transparent;
|
||||
--float-btn-active-color: var(--color-primary);
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
bottom: 96px;
|
||||
z-index: 900;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--float-btn-spacing, 8px);
|
||||
opacity: var(--float-btn-opacity, 1);
|
||||
transition: transform var(--dur, 0.25s) var(--ease, ease), opacity var(--dur, 0.25s) var(--ease, ease);
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
z-index: 1030;
|
||||
text-align: center;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.float-right.no-shadow .float-btn {
|
||||
box-shadow: none;
|
||||
body:has(.modal.in) .float-right,
|
||||
.modal-open .float-right,
|
||||
.imgbox-show .float-right,
|
||||
.scroll-down .scroll-down-hide.float-right,
|
||||
.scroll-ing .scrolling-hide.float-right {
|
||||
transform: translateX(200%);
|
||||
}
|
||||
|
||||
.float-right.no-animation .float-btn {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.float-right.no-animation .float-btn:hover {
|
||||
transform: none;
|
||||
body:has(.modal.in) .float-right.position-center,
|
||||
.modal-open .float-right.position-center,
|
||||
.imgbox-show .float-right.position-center,
|
||||
.scroll-down .scroll-down-hide.float-right.position-center,
|
||||
.scroll-ing .scrolling-hide.float-right.position-center {
|
||||
transform: translate(200%, -50%);
|
||||
}
|
||||
|
||||
.float-right.position-center {
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.float-right.square {
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-lg, 8px);
|
||||
box-shadow: var(--shadow-md, 0 10px 30px rgba(17, 24, 39, 0.12));
|
||||
}
|
||||
|
||||
.float-right .float-btn {
|
||||
width: var(--float-btn-size);
|
||||
min-height: var(--float-btn-size);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 7px 0px;
|
||||
font-size: 1.4em;
|
||||
--this-color: var(--muted-2-color);
|
||||
--this-bg: var(--float-btn-bg);
|
||||
background: var(--this-bg);
|
||||
position: relative;
|
||||
color: var(--this-color, var(--float-btn-color));
|
||||
background: var(--this-bg, var(--float-btn-bg));
|
||||
border: 1px solid var(--color-line-soft, #f0f1f3);
|
||||
text-decoration: none;
|
||||
box-shadow: var(--shadow, 0 8px 24px rgba(17, 24, 39, 0.08));
|
||||
transition: transform var(--dur-fast, 0.15s) var(--ease, ease), box-shadow var(--dur-fast, 0.15s) var(--ease, ease), color var(--dur-fast, 0.15s) var(--ease, ease), background var(--dur-fast, 0.15s) var(--ease, ease);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
overflow: visible;
|
||||
color: var(--this-color) !important;
|
||||
}
|
||||
|
||||
.float-right .float-btn text {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.float-right badge {
|
||||
transform: translate(-11px, -5px) scale(0.85);
|
||||
margin-right: -50px;
|
||||
right: auto;
|
||||
left: 35px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.filter .float-btn {
|
||||
-webkit-backdrop-filter: saturate(2) blur(10px);
|
||||
backdrop-filter: saturate(2) blur(10px);
|
||||
}
|
||||
|
||||
.float-right.square .float-btn + .float-btn {
|
||||
border-top: 1px solid var(--main-shadow);
|
||||
}
|
||||
|
||||
.float-right.round .float-btn {
|
||||
border-radius: 12px;
|
||||
margin-top: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.float-right.square .float-btn {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.float-right.square .float-btn:first-child {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.float-right .float-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
color: var(--float-btn-active-color, var(--color-primary));
|
||||
text-decoration: none;
|
||||
box-shadow: var(--shadow-md, 0 10px 30px rgba(17, 24, 39, 0.12));
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.float-right .float-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.float-right .float-btn::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.float-right .float-btn.is-rippling::after {
|
||||
animation: float-btn-ripple 0.55s ease-out;
|
||||
}
|
||||
|
||||
.float-right .float-btn i {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.float-right .float-btn.ontop {
|
||||
.float-btn.ontop {
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
transform: translateY(10px);
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.float-right .float-btn.ontop.is-visible {
|
||||
.ontop.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translateY(0);
|
||||
visibility: unset;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.float-right.has-blur-pc .float-btn {
|
||||
backdrop-filter: saturate(1.5) blur(10px);
|
||||
-webkit-backdrop-filter: saturate(1.5) blur(10px);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con {
|
||||
position: absolute;
|
||||
display: block;
|
||||
right: calc(100% + 12px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
min-width: 180px;
|
||||
max-width: 280px;
|
||||
padding: 12px;
|
||||
border-radius: var(--radius-lg, 8px);
|
||||
background: var(--surface-1, #fff);
|
||||
border: 1px solid var(--color-line);
|
||||
box-shadow: var(--shadow-lg, 0 18px 40px rgba(17, 24, 39, 0.18));
|
||||
color: var(--color-ink-2);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--dur-fast, 0.15s) var(--ease, ease), transform var(--dur-fast, 0.15s) var(--ease, ease), visibility var(--dur-fast, 0.15s) var(--ease, ease);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: inherit;
|
||||
border-top: 1px solid var(--color-line);
|
||||
border-right: 1px solid var(--color-line);
|
||||
transform: translateY(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 160px;
|
||||
height: auto;
|
||||
margin: 0 auto 10px;
|
||||
border-radius: var(--radius, 10px);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink-3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.float-btn-qrcode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
margin: 0 auto 10px;
|
||||
background: #fff;
|
||||
border-radius: var(--radius, 10px);
|
||||
}
|
||||
|
||||
.float-right .service-wechat .hover-show-con,
|
||||
.float-right .service-qq .hover-show-con,
|
||||
.float-right .qrcode-btn .hover-show-con {
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Page QR hover card: keep title/caption centered under the code */
|
||||
.float-right .qrcode-btn .hover-show-con {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.float-right .qrcode-btn .hover-show-con p {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.float-right .qrcode-btn .hover-show-con .float-btn-qrcode__title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 2px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
|
||||
.float-right .service-wechat .hover-show-con img,
|
||||
.float-right .service-qq .hover-show-con img {
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 10px 24px rgba(17, 24, 39, 0.12);
|
||||
}
|
||||
|
||||
.float-right .service-wechat .hover-show-con p,
|
||||
.float-right .service-qq .hover-show-con p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.float-right .newadd-btns .hover-show-con {
|
||||
min-width: 196px;
|
||||
.float-btn .hover-show-con {
|
||||
right: 35px;
|
||||
left: auto;
|
||||
width: 200px;
|
||||
margin-right: 15px;
|
||||
top: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: var(--main-color);
|
||||
padding: 10px;
|
||||
margin-top: 0;
|
||||
border-radius: var(--main-radius);
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.float-add-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
.float-btn .dropdown-menu::before {
|
||||
left: auto;
|
||||
top: 15px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.float-add-menu__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
color: var(--color-ink-2);
|
||||
text-decoration: none;
|
||||
transition: background var(--dur-fast, 0.15s) var(--ease, ease), color var(--dur-fast, 0.15s) var(--ease, ease), transform var(--dur-fast, 0.15s) var(--ease, ease);
|
||||
.float-btn.service-wechat .hover-show-con {
|
||||
width: 120px;
|
||||
top: -40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.float-add-menu__item i {
|
||||
width: 18px;
|
||||
.float-btn.service-wechat .dropdown-menu::before {
|
||||
top: 53px;
|
||||
}
|
||||
|
||||
.float-btn.qrcode-btn .hover-show-con {
|
||||
width: 150px;
|
||||
top: -60px;
|
||||
padding: 16px 5px 12px;
|
||||
text-align: center;
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
}
|
||||
|
||||
.float-add-menu__item:hover {
|
||||
background: var(--color-primary-soft, rgba(240, 68, 148, 0.1));
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
transform: translateX(-2px);
|
||||
.float-btn.qrcode-btn .qrcode {
|
||||
background: #fff;
|
||||
display: inline-flex;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.float-btn-qrcode canvas,
|
||||
.float-btn-qrcode img,
|
||||
.float-btn-qrcode svg {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
.float-btn.qrcode-btn .dropdown-menu::before {
|
||||
top: 73px;
|
||||
}
|
||||
|
||||
.float-right .float-btn:hover .hover-show-con,
|
||||
.float-right .float-btn:focus-visible .hover-show-con {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translateY(-50%) translateX(-4px);
|
||||
.float-btn.newadd-btns .drop-newadd {
|
||||
width: 137px;
|
||||
top: -74px;
|
||||
}
|
||||
|
||||
.float-right.scrolling-hide.is-hidden,
|
||||
.float-right.scroll-down-hide.is-hidden {
|
||||
transform: translateX(calc(100% + 24px));
|
||||
}
|
||||
|
||||
.float-right.position-center.scrolling-hide.is-hidden,
|
||||
.float-right.position-center.scroll-down-hide.is-hidden {
|
||||
transform: translateX(calc(100% + 24px)) translateY(-50%);
|
||||
}
|
||||
|
||||
.float-right__html {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.float-right__html a {
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
}
|
||||
|
||||
.float-right__html a:hover {
|
||||
color: var(--color-primary-hover, #7099CC);
|
||||
}
|
||||
|
||||
@keyframes float-btn-ripple {
|
||||
0% {
|
||||
opacity: 0.22;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(14);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme float glyph: light → moon, dark → sun.
|
||||
* Match nav toggle + theme.js contract. Class-driven only.
|
||||
*/
|
||||
.float-right .float-btn__icon-sun {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.float-right .float-btn__icon-moon {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
html.dark .float-right .float-btn__icon-moon,
|
||||
body.theme-dark .float-right .float-btn__icon-moon,
|
||||
body.dark-theme .float-right .float-btn__icon-moon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html.dark .float-right .float-btn__icon-sun,
|
||||
body.theme-dark .float-right .float-btn__icon-sun,
|
||||
body.dark-theme .float-right .float-btn__icon-sun {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* VIP float rail entry */
|
||||
.float-right .float-btn.pay-vip {
|
||||
color: var(--this-color, #f5a623);
|
||||
}
|
||||
|
||||
.float-right .float-btn.pay-vip:hover {
|
||||
color: var(--this-color, #f5a623);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.float-right .float-btn.signin-loader {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Dark: solid surface + brighter icon so transparent btn doesn't vanish */
|
||||
html.dark .float-right,
|
||||
body.theme-dark .float-right,
|
||||
body.dark-theme .float-right {
|
||||
--float-btn-color: var(--color-ink-2, #e5eef7);
|
||||
--float-btn-bg: var(--surface-1, #323335);
|
||||
}
|
||||
|
||||
html.dark .float-right .float-btn,
|
||||
body.theme-dark .float-right .float-btn,
|
||||
body.dark-theme .float-right .float-btn {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||
color: var(--this-color, var(--float-btn-color));
|
||||
background: var(--this-bg, var(--float-btn-bg));
|
||||
}
|
||||
|
||||
html.dark .float-right .hover-show-con,
|
||||
body.theme-dark .float-right .hover-show-con,
|
||||
body.dark-theme .float-right .hover-show-con {
|
||||
background: var(--surface-2, #1f2430);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
.float-btn.newadd-btns .drop-newadd::before {
|
||||
left: auto;
|
||||
top: 88px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.float-right {
|
||||
right: 12px;
|
||||
bottom: 84px;
|
||||
--float-btn-size: 40px;
|
||||
}
|
||||
|
||||
.float-right.has-blur-mobile .float-btn {
|
||||
backdrop-filter: saturate(1.5) blur(10px);
|
||||
-webkit-backdrop-filter: saturate(1.5) blur(10px);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con {
|
||||
max-width: min(240px, calc(100vw - 84px));
|
||||
}
|
||||
|
||||
.float-right .float-btn.hide-on-mobile {
|
||||
display: none;
|
||||
.float-btn .hover-show-con {
|
||||
max-width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.float-right .float-btn.hide-on-pc {
|
||||
display: none;
|
||||
@media (max-width: 640px) {
|
||||
.float-right {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,454 +0,0 @@
|
||||
/* ============================================================
|
||||
AKSRT – Floating UI & Site Footer
|
||||
============================================================
|
||||
Covers:
|
||||
• .floating-toolbar – fixed action buttons (scroll-top,
|
||||
font-size, sidebar toggle)
|
||||
• .mobile-nav-overlay – full-screen drawer nav (mobile)
|
||||
• .site-footer – footer grid + bottom bar
|
||||
============================================================ */
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
FLOATING TOOLBAR
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.floating-toolbar {
|
||||
position: fixed;
|
||||
right: var(--space-5);
|
||||
bottom: var(--space-8);
|
||||
z-index: var(--z-floating);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* ── Individual button ────────────────────────────────────── */
|
||||
.floating-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--blur-bg);
|
||||
border: none;
|
||||
border-radius: var(--radius-full);
|
||||
box-shadow: var(--shadow-sm);
|
||||
backdrop-filter: saturate(2) blur(12px);
|
||||
-webkit-backdrop-filter: saturate(2) blur(12px);
|
||||
cursor: pointer;
|
||||
color: var(--color-ink-4);
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
color var(--transition-fast),
|
||||
box-shadow var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.floating-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.floating-btn:hover,
|
||||
.floating-btn:focus-visible {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.floating-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.floating-btn:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── Scroll-to-top: hide until page is scrolled ───────────── */
|
||||
.floating-btn--top {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity var(--transition-base),
|
||||
background var(--transition-fast),
|
||||
color var(--transition-fast),
|
||||
box-shadow var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.floating-btn--top.is-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* ── Mobile: smaller toolbar, stacked horizontally ───────── */
|
||||
@media (max-width: 767px) {
|
||||
.floating-toolbar {
|
||||
right: var(--space-4);
|
||||
bottom: var(--space-6);
|
||||
flex-direction: row; /* horizontal row on mobile */
|
||||
}
|
||||
|
||||
.floating-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.floating-btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
MOBILE NAV OVERLAY
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.mobile-nav-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-overlay);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition:
|
||||
opacity var(--transition-base),
|
||||
visibility var(--transition-base);
|
||||
/* backdrop blur on supporting browsers */
|
||||
backdrop-filter: blur(2px);
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.mobile-nav-overlay.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* ── Drawer panel ─────────────────────────────────────────── */
|
||||
.mobile-nav-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: min(320px, 85vw);
|
||||
height: 100%;
|
||||
background: var(--color-surface);
|
||||
box-shadow: var(--shadow-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-6);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--transition-base);
|
||||
}
|
||||
|
||||
.mobile-nav-overlay.is-open .mobile-nav-drawer {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* ── Close button ─────────────────────────────────────────── */
|
||||
.mobile-nav-close {
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: var(--surface-page);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--radius-full);
|
||||
cursor: pointer;
|
||||
color: var(--color-ink-4);
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mobile-nav-close:hover {
|
||||
background: var(--color-danger);
|
||||
color: #fff;
|
||||
border-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.mobile-nav-close svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* ── Brand ────────────────────────────────────────────────── */
|
||||
.mobile-nav-brand {
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.mobile-nav-brand a {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mobile-nav-brand a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Nav menu (WordPress-generated ul) ────────────────────── */
|
||||
.mobile-nav-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.mobile-nav-menu li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.mobile-nav-menu a {
|
||||
display: block;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-ink-4);
|
||||
border-radius: var(--radius-md);
|
||||
text-decoration: none;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.mobile-nav-menu a:hover,
|
||||
.mobile-nav-menu .current-menu-item > a,
|
||||
.mobile-nav-menu .current-page-item > a {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* sub-menu indented */
|
||||
.mobile-nav-menu .sub-menu {
|
||||
padding-left: var(--space-4);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
/* ── Search form ──────────────────────────────────────────── */
|
||||
.mobile-nav-search {
|
||||
margin-top: auto;
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.mobile-nav-search form {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.mobile-nav-search input[type="search"] {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
background: var(--surface-page);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.mobile-nav-search input[type="search"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.mobile-nav-search button[type="submit"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.mobile-nav-search button[type="submit"]:hover {
|
||||
background: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* ── Only mount overlay on mobile ─────────────────────────── */
|
||||
@media (min-width: 769px) {
|
||||
.mobile-nav-overlay {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
SITE FOOTER
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.site-footer {
|
||||
margin-top: var(--space-16);
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.site-footer__inner {
|
||||
padding-top: var(--space-12);
|
||||
padding-bottom: var(--space-10);
|
||||
}
|
||||
|
||||
/* ── Grid of columns ──────────────────────────────────────── */
|
||||
.site-footer__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: var(--space-8);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* Give the about column a bit more real estate when there's room */
|
||||
.site-footer__about {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.site-footer__about {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.site-footer__grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.site-footer__about {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Column headings ──────────────────────────────────────── */
|
||||
.site-footer__heading {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-ink-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* ── About description ────────────────────────────────────── */
|
||||
.site-footer__desc {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-ink-4);
|
||||
line-height: 1.65;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* ── Contact icon row ─────────────────────────────────────── */
|
||||
.site-footer__contacts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.site-footer__contacts a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
border: 1px solid var(--color-line);
|
||||
color: var(--color-ink-4);
|
||||
transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.site-footer__contacts a:hover {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.site-footer__contacts .dashicons {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* ── Link lists ───────────────────────────────────────────── */
|
||||
.site-footer__links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.site-footer__links li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.site-footer__links a {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-ink-4);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.site-footer__links a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Friend links: wrap horizontally ─────────────────────── */
|
||||
.site-footer__links--friends {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2) var(--space-4);
|
||||
}
|
||||
|
||||
/* ── Bottom copyright bar ─────────────────────────────────── */
|
||||
.site-footer__bottom {
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
background: var(--surface-page);
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.site-footer__bottom .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.site-footer__copy,
|
||||
.site-footer__icp {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
.site-footer__icp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.site-footer__icp a {
|
||||
color: var(--color-ink-4);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.site-footer__icp a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.site-footer__bottom .container {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -1,404 +1,167 @@
|
||||
/* ============================================================
|
||||
* Floating Buttons
|
||||
* 右侧悬浮按钮(结构/样式对齐参考主题 .float-right)
|
||||
* 依赖 main.css 基础规则:.hover-show / .dropdown-menu(::before) /
|
||||
* .drop-newadd / .fa-toggle-theme / badge
|
||||
* ============================================================ */
|
||||
|
||||
.float-right {
|
||||
--float-btn-size: 42px;
|
||||
--float-btn-spacing: 8px;
|
||||
--float-btn-opacity: 1;
|
||||
--float-btn-color: var(--color-ink-3);
|
||||
--float-btn-bg: transparent;
|
||||
--float-btn-active-color: var(--color-primary);
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
bottom: 96px;
|
||||
z-index: 900;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--float-btn-spacing, 8px);
|
||||
opacity: var(--float-btn-opacity, 1);
|
||||
transition: transform var(--dur, 0.25s) var(--ease, ease), opacity var(--dur, 0.25s) var(--ease, ease);
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
z-index: 1030;
|
||||
text-align: center;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.float-right.no-shadow .float-btn {
|
||||
box-shadow: none;
|
||||
body:has(.modal.in) .float-right,
|
||||
.modal-open .float-right,
|
||||
.imgbox-show .float-right,
|
||||
.scroll-down .scroll-down-hide.float-right,
|
||||
.scroll-ing .scrolling-hide.float-right {
|
||||
transform: translateX(200%);
|
||||
}
|
||||
|
||||
.float-right.no-animation .float-btn {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.float-right.no-animation .float-btn:hover {
|
||||
transform: none;
|
||||
body:has(.modal.in) .float-right.position-center,
|
||||
.modal-open .float-right.position-center,
|
||||
.imgbox-show .float-right.position-center,
|
||||
.scroll-down .scroll-down-hide.float-right.position-center,
|
||||
.scroll-ing .scrolling-hide.float-right.position-center {
|
||||
transform: translate(200%, -50%);
|
||||
}
|
||||
|
||||
.float-right.position-center {
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.float-right.square {
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-lg, 8px);
|
||||
box-shadow: var(--shadow-md, 0 10px 30px rgba(17, 24, 39, 0.12));
|
||||
}
|
||||
|
||||
.float-right .float-btn {
|
||||
width: var(--float-btn-size);
|
||||
min-height: var(--float-btn-size);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 7px 0px;
|
||||
font-size: 1.4em;
|
||||
--this-color: var(--muted-2-color);
|
||||
--this-bg: var(--float-btn-bg);
|
||||
background: var(--this-bg);
|
||||
position: relative;
|
||||
color: var(--this-color, var(--float-btn-color));
|
||||
background: var(--this-bg, var(--float-btn-bg));
|
||||
border: 1px solid var(--color-line-soft, #f0f1f3);
|
||||
text-decoration: none;
|
||||
box-shadow: var(--shadow, 0 8px 24px rgba(17, 24, 39, 0.08));
|
||||
transition: transform var(--dur-fast, 0.15s) var(--ease, ease), box-shadow var(--dur-fast, 0.15s) var(--ease, ease), color var(--dur-fast, 0.15s) var(--ease, ease), background var(--dur-fast, 0.15s) var(--ease, ease);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
overflow: visible;
|
||||
color: var(--this-color) !important;
|
||||
}
|
||||
|
||||
.float-right .float-btn text {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.float-right badge {
|
||||
transform: translate(-11px, -5px) scale(0.85);
|
||||
margin-right: -50px;
|
||||
right: auto;
|
||||
left: 35px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.filter .float-btn {
|
||||
-webkit-backdrop-filter: saturate(2) blur(10px);
|
||||
backdrop-filter: saturate(2) blur(10px);
|
||||
}
|
||||
|
||||
.float-right.square .float-btn + .float-btn {
|
||||
border-top: 1px solid var(--main-shadow);
|
||||
}
|
||||
|
||||
.float-right.round .float-btn {
|
||||
border-radius: 12px;
|
||||
margin-top: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.float-right.square .float-btn {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.float-right.square .float-btn:first-child {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.float-right .float-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
color: var(--float-btn-active-color, var(--color-primary));
|
||||
text-decoration: none;
|
||||
box-shadow: var(--shadow-md, 0 10px 30px rgba(17, 24, 39, 0.12));
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.float-right .float-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.float-right .float-btn::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.float-right .float-btn.is-rippling::after {
|
||||
animation: float-btn-ripple 0.55s ease-out;
|
||||
}
|
||||
|
||||
.float-right .float-btn i {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.float-right .float-btn.ontop {
|
||||
.float-btn.ontop {
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
transform: translateY(10px);
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.float-right .float-btn.ontop.is-visible {
|
||||
.ontop.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translateY(0);
|
||||
visibility: unset;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.float-right.has-blur-pc .float-btn {
|
||||
backdrop-filter: saturate(1.5) blur(10px);
|
||||
-webkit-backdrop-filter: saturate(1.5) blur(10px);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con {
|
||||
position: absolute;
|
||||
display: block;
|
||||
right: calc(100% + 12px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
min-width: 180px;
|
||||
max-width: 280px;
|
||||
padding: 12px;
|
||||
border-radius: var(--radius-lg, 8px);
|
||||
background: var(--surface-1, #fff);
|
||||
border: 1px solid var(--color-line);
|
||||
box-shadow: var(--shadow-lg, 0 18px 40px rgba(17, 24, 39, 0.18));
|
||||
color: var(--color-ink-2);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--dur-fast, 0.15s) var(--ease, ease), transform var(--dur-fast, 0.15s) var(--ease, ease), visibility var(--dur-fast, 0.15s) var(--ease, ease);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: inherit;
|
||||
border-top: 1px solid var(--color-line);
|
||||
border-right: 1px solid var(--color-line);
|
||||
transform: translateY(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 160px;
|
||||
height: auto;
|
||||
margin: 0 auto 10px;
|
||||
border-radius: var(--radius, 10px);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink-3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.float-btn-qrcode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
margin: 0 auto 10px;
|
||||
background: #fff;
|
||||
border-radius: var(--radius, 10px);
|
||||
}
|
||||
|
||||
.float-right .service-wechat .hover-show-con,
|
||||
.float-right .service-qq .hover-show-con,
|
||||
.float-right .qrcode-btn .hover-show-con {
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Page QR hover card: keep title/caption centered under the code */
|
||||
.float-right .qrcode-btn .hover-show-con {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.float-right .qrcode-btn .hover-show-con p {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.float-right .qrcode-btn .hover-show-con .float-btn-qrcode__title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 2px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
|
||||
.float-right .service-wechat .hover-show-con img,
|
||||
.float-right .service-qq .hover-show-con img {
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 10px 24px rgba(17, 24, 39, 0.12);
|
||||
}
|
||||
|
||||
.float-right .service-wechat .hover-show-con p,
|
||||
.float-right .service-qq .hover-show-con p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.float-right .newadd-btns .hover-show-con {
|
||||
min-width: 196px;
|
||||
.float-btn .hover-show-con {
|
||||
right: 35px;
|
||||
left: auto;
|
||||
width: 200px;
|
||||
margin-right: 15px;
|
||||
top: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: var(--main-color);
|
||||
padding: 10px;
|
||||
margin-top: 0;
|
||||
border-radius: var(--main-radius);
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.float-add-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
.float-btn .dropdown-menu::before {
|
||||
left: auto;
|
||||
top: 15px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.float-add-menu__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
color: var(--color-ink-2);
|
||||
text-decoration: none;
|
||||
transition: background var(--dur-fast, 0.15s) var(--ease, ease), color var(--dur-fast, 0.15s) var(--ease, ease), transform var(--dur-fast, 0.15s) var(--ease, ease);
|
||||
.float-btn.service-wechat .hover-show-con {
|
||||
width: 120px;
|
||||
top: -40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.float-add-menu__item i {
|
||||
width: 18px;
|
||||
.float-btn.service-wechat .dropdown-menu::before {
|
||||
top: 53px;
|
||||
}
|
||||
|
||||
.float-btn.qrcode-btn .hover-show-con {
|
||||
width: 150px;
|
||||
top: -60px;
|
||||
padding: 16px 5px 12px;
|
||||
text-align: center;
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
}
|
||||
|
||||
.float-add-menu__item:hover {
|
||||
background: var(--color-primary-soft, rgba(240, 68, 148, 0.1));
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
transform: translateX(-2px);
|
||||
.float-btn.qrcode-btn .qrcode {
|
||||
background: #fff;
|
||||
display: inline-flex;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.float-btn-qrcode canvas,
|
||||
.float-btn-qrcode img,
|
||||
.float-btn-qrcode svg {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
.float-btn.qrcode-btn .dropdown-menu::before {
|
||||
top: 73px;
|
||||
}
|
||||
|
||||
.float-right .float-btn:hover .hover-show-con,
|
||||
.float-right .float-btn:focus-visible .hover-show-con {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transform: translateY(-50%) translateX(-4px);
|
||||
.float-btn.newadd-btns .drop-newadd {
|
||||
width: 137px;
|
||||
top: -74px;
|
||||
}
|
||||
|
||||
.float-right.scrolling-hide.is-hidden,
|
||||
.float-right.scroll-down-hide.is-hidden {
|
||||
transform: translateX(calc(100% + 24px));
|
||||
}
|
||||
|
||||
.float-right.position-center.scrolling-hide.is-hidden,
|
||||
.float-right.position-center.scroll-down-hide.is-hidden {
|
||||
transform: translateX(calc(100% + 24px)) translateY(-50%);
|
||||
}
|
||||
|
||||
.float-right__html {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.float-right__html a {
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
}
|
||||
|
||||
.float-right__html a:hover {
|
||||
color: var(--color-primary-hover, #7099CC);
|
||||
}
|
||||
|
||||
@keyframes float-btn-ripple {
|
||||
0% {
|
||||
opacity: 0.22;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(14);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme float glyph: light → moon, dark → sun.
|
||||
* Match nav toggle + theme.js contract. Class-driven only.
|
||||
*/
|
||||
.float-right .float-btn__icon-sun {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.float-right .float-btn__icon-moon {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
html.dark .float-right .float-btn__icon-moon,
|
||||
body.theme-dark .float-right .float-btn__icon-moon,
|
||||
body.dark-theme .float-right .float-btn__icon-moon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html.dark .float-right .float-btn__icon-sun,
|
||||
body.theme-dark .float-right .float-btn__icon-sun,
|
||||
body.dark-theme .float-right .float-btn__icon-sun {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* VIP float rail entry */
|
||||
.float-right .float-btn.pay-vip {
|
||||
color: var(--this-color, #f5a623);
|
||||
}
|
||||
|
||||
.float-right .float-btn.pay-vip:hover {
|
||||
color: var(--this-color, #f5a623);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.float-right .float-btn.signin-loader {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Dark: solid surface + brighter icon so transparent btn doesn't vanish */
|
||||
html.dark .float-right,
|
||||
body.theme-dark .float-right,
|
||||
body.dark-theme .float-right {
|
||||
--float-btn-color: var(--color-ink-2, #e5eef7);
|
||||
--float-btn-bg: var(--surface-1, #323335);
|
||||
}
|
||||
|
||||
html.dark .float-right .float-btn,
|
||||
body.theme-dark .float-right .float-btn,
|
||||
body.dark-theme .float-right .float-btn {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||
color: var(--this-color, var(--float-btn-color));
|
||||
background: var(--this-bg, var(--float-btn-bg));
|
||||
}
|
||||
|
||||
html.dark .float-right .hover-show-con,
|
||||
body.theme-dark .float-right .hover-show-con,
|
||||
body.dark-theme .float-right .hover-show-con {
|
||||
background: var(--surface-2, #1f2430);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
.float-btn.newadd-btns .drop-newadd::before {
|
||||
left: auto;
|
||||
top: 88px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.float-btn .hover-show-con {
|
||||
max-width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.float-right {
|
||||
right: 12px;
|
||||
bottom: 84px;
|
||||
--float-btn-size: 40px;
|
||||
}
|
||||
|
||||
.float-right.has-blur-mobile .float-btn {
|
||||
backdrop-filter: saturate(1.5) blur(10px);
|
||||
-webkit-backdrop-filter: saturate(1.5) blur(10px);
|
||||
}
|
||||
|
||||
.float-right .hover-show-con {
|
||||
max-width: min(240px, calc(100vw - 84px));
|
||||
}
|
||||
|
||||
.float-right .float-btn.hide-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.float-right .float-btn.hide-on-pc {
|
||||
display: none;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,101 +18,6 @@
|
||||
.hide-on-pc { display: none !important; }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 浮动右侧按钮组(模仿 legacy float-right)
|
||||
* ============================================================ */
|
||||
/* 对齐 legacy: bottom 60px, z-index 1030 */
|
||||
.float-right-btns {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 60px;
|
||||
z-index: 1030;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* 对齐 legacy: 40px, 1.4em icon */
|
||||
.float-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
min-height: 40px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--float-btn-bg);
|
||||
color: var(--color-ink-3);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
|
||||
font-size: 1.4em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.float-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--color-primary);
|
||||
background: var(--surface-page);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.float-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 回到顶部按钮 — 滚动后显示 */
|
||||
.float-btn--top {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.float-btn--top.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 主题切换按钮 */
|
||||
#float-theme-toggle .float-btn__icon-moon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#float-theme-toggle .float-btn__icon-sun {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#float-theme-toggle[aria-pressed="true"] .float-btn__icon-moon,
|
||||
.theme-dark #float-theme-toggle .float-btn__icon-moon,
|
||||
html.dark #float-theme-toggle .float-btn__icon-moon,
|
||||
body.dark-theme #float-theme-toggle .float-btn__icon-moon {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#float-theme-toggle[aria-pressed="true"] .float-btn__icon-sun,
|
||||
.theme-dark #float-theme-toggle .float-btn__icon-sun,
|
||||
html.dark #float-theme-toggle .float-btn__icon-sun,
|
||||
body.dark-theme #float-theme-toggle .float-btn__icon-sun {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 移动端隐藏浮动按钮(用 tabbar 代替) */
|
||||
@media (max-width: 767px) {
|
||||
.float-right-btns {
|
||||
right: 12px;
|
||||
bottom: 80px;
|
||||
}
|
||||
|
||||
.float-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 移动端底部 Tab 栏(模仿 legacy footer-tabbar)
|
||||
* ============================================================ */
|
||||
@@ -603,12 +508,6 @@ html.dark .site-footer {
|
||||
background: var(--surface-1);
|
||||
}
|
||||
|
||||
.theme-dark .float-btn,
|
||||
html.dark .float-btn {
|
||||
background: var(--float-btn-bg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.theme-dark .footer-tabbar,
|
||||
html.dark .footer-tabbar {
|
||||
background: var(--blur-bg);
|
||||
|
||||
@@ -255,15 +255,3 @@
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Float right buttons shell (kept from prior shell) */
|
||||
#float-right-btns.float-right,
|
||||
.float-right {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 60px;
|
||||
z-index: 1030;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@@ -55,16 +55,6 @@ html.dark .footer-tabbar--glass {
|
||||
box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Glass on floating buttons (lighter blur: saturate(2) blur(10px))
|
||||
* ============================================================ */
|
||||
.float-right--glass .float-btn {
|
||||
-webkit-backdrop-filter: saturate(2) blur(10px);
|
||||
backdrop-filter: saturate(2) blur(10px);
|
||||
background: var(--float-btn-bg);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Scroll shadow on glass header (legacy: .body-scroll .header)
|
||||
* ============================================================ */
|
||||
|
||||
@@ -8117,206 +8117,10 @@ badge.top,
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
/*----右边浮动按钮-返回顶部---右边浮动按钮-返回顶部---右边浮动按钮-返回顶部---右边浮动按钮-返回顶部---*/
|
||||
.float-right {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
z-index: 1030;
|
||||
text-align: center;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
body:has(.modal.in) .float-right,
|
||||
.modal-open .float-right,
|
||||
.imgbox-show .float-right,
|
||||
.scroll-down .scroll-down-hide.float-right,
|
||||
.scroll-ing .scrolling-hide.float-right {
|
||||
transform: translateX(200%);
|
||||
}
|
||||
|
||||
body:has(.modal.in) .float-right.position-center,
|
||||
.modal-open .float-right.position-center,
|
||||
.imgbox-show .float-right.position-center,
|
||||
.scroll-down .scroll-down-hide.float-right.position-center,
|
||||
.scroll-ing .scrolling-hide.float-right.position-center {
|
||||
transform: translate(200%, -50%);
|
||||
}
|
||||
|
||||
.tabbar-item.active {
|
||||
color: var(--focus-color);
|
||||
}
|
||||
|
||||
.float-right.position-center {
|
||||
bottom: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.float-right.position-top {
|
||||
bottom: auto;
|
||||
top: 80px;
|
||||
}
|
||||
|
||||
.float-right .float-btn {
|
||||
width: 40px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 7px 0px;
|
||||
font-size: 1.4em;
|
||||
--this-color: var(--muted-2-color);
|
||||
--this-bg: var(--float-btn-bg);
|
||||
background: var(--this-bg);
|
||||
position: relative;
|
||||
color: var(--this-color) !important;
|
||||
}
|
||||
|
||||
.float-right .float-btn text {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.float-right badge {
|
||||
transform: translate(-11px, -5px) scale(0.85);
|
||||
margin-right: -50px;
|
||||
right: auto;
|
||||
left: 35px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.float-btn.wait-pay-btn .hover-show-con {
|
||||
animation: float-wait-pay 2s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
body.modal-open .float-btn.wait-pay-btn .hover-show-con {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
@keyframes float-wait-pay {
|
||||
0% {
|
||||
opacity: 0;
|
||||
visibility: unset;
|
||||
box-shadow: 0 0 10px 0 rgba(255, 73, 121, 0.1);
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 10px 0 rgba(255, 73, 121, 0.95);
|
||||
}
|
||||
30% {
|
||||
box-shadow: 0 0 10px 0 rgba(255, 73, 121, 0.1);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 10px 0 rgba(255, 73, 121, 0.95);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 10px 0 rgba(255, 73, 121, 0.1);
|
||||
}
|
||||
80% {
|
||||
visibility: unset;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.filter .float-btn {
|
||||
-webkit-backdrop-filter: saturate(2) blur(10px);
|
||||
backdrop-filter: saturate(2) blur(10px);
|
||||
}
|
||||
|
||||
.float-right.square .float-btn + .float-btn {
|
||||
border-top: 1px solid var(--main-shadow);
|
||||
}
|
||||
|
||||
.float-right.round .float-btn {
|
||||
margin-top: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.float-btn.ontop {
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
transform: translateY(10px);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ontop.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
visibility: unset;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.float-btn .hover-show-con {
|
||||
right: 35px;
|
||||
left: auto;
|
||||
width: 200px;
|
||||
margin-right: 15px;
|
||||
top: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: var(--main-color);
|
||||
padding: 10px;
|
||||
margin-top: 0;
|
||||
border-radius: var(--main-radius);
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.float-btn .dropdown-menu::before {
|
||||
left: auto;
|
||||
top: 15px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.float-btn.service-wechat .hover-show-con {
|
||||
width: 120px;
|
||||
top: -40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.float-btn.service-wechat .dropdown-menu::before {
|
||||
top: 53px;
|
||||
}
|
||||
|
||||
.float-btn.qrcode-btn .hover-show-con {
|
||||
width: 150px;
|
||||
top: -60px;
|
||||
padding: 16px 5px 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.float-btn.qrcode-btn .qrcode {
|
||||
background: #fff;
|
||||
display: inline-flex;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.float-btn.qrcode-btn .dropdown-menu::before {
|
||||
top: 73px;
|
||||
}
|
||||
|
||||
.float-btn.newadd-btns .drop-newadd {
|
||||
width: 137px;
|
||||
top: -74px;
|
||||
}
|
||||
|
||||
.float-btn.newadd-btns .drop-newadd::before {
|
||||
left: auto;
|
||||
top: 88px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.float-btn .order-thumb {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
/*----搜索页面-----搜索页面-----搜索页面-----搜索页面-----搜索页面-----搜索页面----*/
|
||||
.search-close-box {
|
||||
max-width: 660px;
|
||||
@@ -11688,10 +11492,6 @@ body.error404 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.float-btn .hover-show-con {
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
/***---链接列表页面模板---***/
|
||||
.link-nav-box > .aksrt-panel {
|
||||
background: transparent;
|
||||
@@ -11826,13 +11626,6 @@ body.error404 {
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
right: 10px;
|
||||
}
|
||||
.float-right.position-top {
|
||||
top: 56px;
|
||||
}
|
||||
|
||||
.em09-sm {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -159,12 +159,12 @@
|
||||
dispatchThemeChange(isDark);
|
||||
// Icon visibility is CSS-driven via html.dark / body.theme-dark.
|
||||
// Only sync a11y state here — do not set inline display (fights !important CSS).
|
||||
document.querySelectorAll("#theme-toggle, #theme-toggle-desktop, #theme-toggle-mobile, #dropdown-theme-toggle, #float-theme-toggle, .toggle-theme, .theme-toggle-btn, [data-action='toggle-theme']").forEach(function (btn) {
|
||||
document.querySelectorAll("#theme-toggle, #theme-toggle-desktop, #theme-toggle-mobile, #dropdown-theme-toggle, .toggle-theme, [data-action='toggle-theme']").forEach(function (btn) {
|
||||
if (!btn) return;
|
||||
btn.setAttribute("aria-pressed", isDark ? "true" : "false");
|
||||
btn.classList.toggle("is-dark", isDark);
|
||||
var moon = btn.querySelector(".theme-icon-moon, .float-btn__icon-moon");
|
||||
var sun = btn.querySelector(".theme-icon-sun, .float-btn__icon-sun");
|
||||
var moon = btn.querySelector(".theme-icon-moon");
|
||||
var sun = btn.querySelector(".theme-icon-sun");
|
||||
if (moon) {
|
||||
moon.removeAttribute("style");
|
||||
moon.setAttribute("aria-hidden", isDark ? "true" : "false");
|
||||
@@ -174,10 +174,6 @@
|
||||
sun.setAttribute("aria-hidden", isDark ? "false" : "true");
|
||||
}
|
||||
});
|
||||
var floatBtn = document.getElementById("float-theme-toggle");
|
||||
if (floatBtn) {
|
||||
floatBtn.setAttribute("aria-pressed", isDark ? "true" : "false");
|
||||
}
|
||||
var ddBtn = document.getElementById("dropdown-theme-toggle");
|
||||
if (ddBtn) ddBtn.setAttribute("aria-pressed", isDark ? "true" : "false");
|
||||
}
|
||||
@@ -260,7 +256,6 @@
|
||||
bindToggle("theme-toggle");
|
||||
bindToggle("theme-toggle-desktop");
|
||||
bindToggle("dropdown-theme-toggle");
|
||||
bindToggle("float-theme-toggle");
|
||||
bindToggle("theme-toggle-mobile");
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-159
@@ -195,171 +195,13 @@ $_footer_conter = wp_kses_post(trim((string) $footer_customize_code));
|
||||
</footer>
|
||||
|
||||
|
||||
<!-- 浮动右侧按钮组 -->
|
||||
<?php
|
||||
$floating_settings = $has_settings ? \AKSRT\Settings\Settings::get_floating_buttons_settings() : [];
|
||||
$floating_items = $has_settings ? \AKSRT\Settings\Settings::get_floating_buttons_items() : [];
|
||||
// 右侧悬浮按钮由 aksrt_float_right() 挂 wp_footer 输出(inc/helpers/aksrt-float-buttons.php)。
|
||||
$aksrt_hide_chrome = is_page_template('templates/pages/page-submit.php');
|
||||
$floating_enabled = !$aksrt_hide_chrome && !empty($floating_settings['enabled']) && !empty($floating_items);
|
||||
$tabbar_settings = $has_settings ? \AKSRT\Settings\Settings::get_footer_tabbar_settings() : [];
|
||||
$tabbar_items = $has_settings ? \AKSRT\Settings\Settings::get_footer_tabbar_items() : [];
|
||||
$tabbar_enabled = !$aksrt_hide_chrome && !empty($tabbar_settings['enabled']) && !empty($tabbar_items);
|
||||
$floating_classes = 'float-right';
|
||||
$floating_classes .= ' ' . (!empty($floating_settings['style']) ? sanitize_html_class($floating_settings['style']) : 'round');
|
||||
$floating_classes .= !empty($floating_settings['position']) && $floating_settings['position'] === 'center' ? ' position-center' : ' position-bottom';
|
||||
if (!empty($floating_settings['scroll_hide'])) {
|
||||
$floating_classes .= ' scrolling-hide';
|
||||
}
|
||||
if (!empty($floating_settings['scroll_down_hide'])) {
|
||||
$floating_classes .= ' scroll-down-hide';
|
||||
}
|
||||
if (empty($floating_settings['shadow'])) {
|
||||
$floating_classes .= ' no-shadow';
|
||||
}
|
||||
if (empty($floating_settings['animation'])) {
|
||||
$floating_classes .= ' no-animation';
|
||||
}
|
||||
$float_blur = is_array($floating_settings['filter_blur'] ?? null) ? $floating_settings['filter_blur'] : [];
|
||||
if (in_array('pc', $float_blur, true)) {
|
||||
$floating_classes .= ' has-blur-pc';
|
||||
}
|
||||
if (in_array('mobile', $float_blur, true)) {
|
||||
$floating_classes .= ' has-blur-mobile';
|
||||
}
|
||||
if ($floating_enabled) :
|
||||
?>
|
||||
<div id="float-right-btns" class="<?php echo esc_attr($floating_classes); ?>">
|
||||
<?php foreach ($floating_items as $item) :
|
||||
$item_type = $item['type'] ?? 'custom';
|
||||
$item_icon = $item['icon'] ?? 'fa fas fa-circle';
|
||||
$item_text = $item['text'] ?? '';
|
||||
$item_url = $item['link'] ?? '#';
|
||||
$item_target = !empty($item['new_window']) ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
$item_color = $item['color'] ?? '';
|
||||
$item_bg = $item['bg'] ?? '';
|
||||
$pc_show = $item['pc_s'] ?? true;
|
||||
$m_show = $item['m_s'] ?? true;
|
||||
|
||||
$item_class = 'float-btn';
|
||||
$inline_styles = [];
|
||||
if ($item_color !== '') {
|
||||
$inline_styles[] = '--this-color:' . esc_attr($item_color);
|
||||
}
|
||||
if ($item_bg !== '') {
|
||||
$inline_styles[] = '--this-bg:' . esc_attr($item_bg);
|
||||
}
|
||||
if (!$pc_show) {
|
||||
$item_class .= ' hide-on-pc';
|
||||
}
|
||||
if (!$m_show) {
|
||||
$item_class .= ' hide-on-mobile';
|
||||
}
|
||||
$style_attr = $inline_styles ? ' style="' . implode(';', $inline_styles) . '"' : '';
|
||||
|
||||
if ($item_type === 'back_top') :
|
||||
$item_class .= ' ontop';
|
||||
?>
|
||||
<a href="#" class="<?php echo esc_attr($item_class); ?>" id="float-back-to-top" role="button" aria-label="<?php echo esc_attr($item_text ?: __('返回顶部', 'aksrt')); ?>"<?php echo $style_attr; ?>>
|
||||
<?php echo aksrt_icon_markup((string) $item_icon); // pre-escaped ?>
|
||||
</a>
|
||||
<?php elseif ($item_type === 'theme_mode') :
|
||||
$item_class .= ' theme-toggle-btn';
|
||||
?>
|
||||
<a href="#" class="<?php echo esc_attr($item_class); ?>" role="button" data-action="toggle-theme" aria-label="<?php echo esc_attr($item_text ?: __('切换主题', 'aksrt')); ?>"<?php echo $style_attr; ?>>
|
||||
<i class="fa fas fa-sun float-btn__icon-sun" aria-hidden="true"></i>
|
||||
<i class="fa fas fa-moon float-btn__icon-moon" aria-hidden="true"></i>
|
||||
</a>
|
||||
<?php elseif ($item_type === 'pay_vip') :
|
||||
$vip_html = function_exists('aksrt_get_float_pay_vip_html') ? aksrt_get_float_pay_vip_html($item) : '';
|
||||
if ($vip_html !== '') {
|
||||
echo $vip_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
<?php elseif ($item_type === 'service_wechat') :
|
||||
$item_class .= ' service-wechat';
|
||||
$wechat_img = $item['wechat_img'] ?? ($item['qrcode_img'] ?? '');
|
||||
?>
|
||||
<a href="#" class="<?php echo esc_attr($item_class); ?>" role="button" aria-label="<?php echo esc_attr($item_text ?: __('微信', 'aksrt')); ?>"<?php echo $style_attr; ?>>
|
||||
<?php echo aksrt_icon_markup((string) $item_icon); // pre-escaped ?>
|
||||
<?php if ($wechat_img) : ?>
|
||||
<div class="hover-show-con">
|
||||
<img src="<?php echo esc_url($wechat_img); ?>" alt="<?php esc_attr_e('微信二维码', 'aksrt'); ?>">
|
||||
<?php if ($item_text !== '') : ?><p><?php echo esc_html($item_text); ?></p><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php elseif ($item_type === 'service_qq') :
|
||||
$item_class .= ' service-qq';
|
||||
$qq_number = $item['qq'] ?? '';
|
||||
$qq_img = $item['qrcode_img'] ?? '';
|
||||
?>
|
||||
<a href="<?php echo $qq_number ? esc_url('https://wpa.qq.com/msgrd?v=3&uin=' . $qq_number . '&site=qq&menu=yes') : '#'; ?>" class="<?php echo esc_attr($item_class); ?>" role="button"<?php echo $qq_number ? ' target="_blank" rel="noopener noreferrer"' : ''; ?> aria-label="<?php echo esc_attr($item_text ?: 'QQ'); ?>"<?php echo $style_attr; ?>>
|
||||
<?php echo aksrt_icon_markup((string) $item_icon); // pre-escaped ?>
|
||||
<?php if ($qq_img || $qq_number) : ?>
|
||||
<div class="hover-show-con">
|
||||
<?php if ($qq_img) : ?>
|
||||
<img src="<?php echo esc_url($qq_img); ?>" alt="QQ">
|
||||
<?php endif; ?>
|
||||
<?php if ($qq_number) : ?><p>QQ: <?php echo esc_html($qq_number); ?></p><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php elseif ($item_type === 'add') :
|
||||
$item_class .= ' newadd-btns';
|
||||
$add_items = is_array($item['add_items'] ?? null) ? $item['add_items'] : [];
|
||||
?>
|
||||
<a href="#" class="<?php echo esc_attr($item_class); ?>" role="button" aria-label="<?php echo esc_attr($item_text ?: __('发布', 'aksrt')); ?>"<?php echo $style_attr; ?>>
|
||||
<?php echo aksrt_icon_markup((string) $item_icon); // pre-escaped ?>
|
||||
<?php if (!empty($add_items)) : ?>
|
||||
<div class="hover-show-con">
|
||||
<div class="float-add-menu">
|
||||
<?php foreach ($add_items as $sub) :
|
||||
$sub_url = $sub['url'] ?? '#';
|
||||
$sub_icon = $sub['icon'] ?? 'fa fas fa-plus';
|
||||
$sub_text = $sub['text'] ?? '';
|
||||
?>
|
||||
<a href="<?php echo esc_url($sub_url); ?>" class="float-add-menu__item">
|
||||
<?php echo aksrt_icon_markup((string) $sub_icon); // pre-escaped ?>
|
||||
<span><?php echo esc_html($sub_text); ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php elseif ($item_type === 'qrcode') :
|
||||
$item_class .= ' qrcode-btn';
|
||||
?>
|
||||
<a href="#" class="<?php echo esc_attr($item_class); ?>" role="button" aria-label="<?php echo esc_attr($item_text ?: __('二维码', 'aksrt')); ?>"<?php echo $style_attr; ?>>
|
||||
<?php echo aksrt_icon_markup((string) $item_icon); // pre-escaped ?>
|
||||
<div class="hover-show-con">
|
||||
<div
|
||||
class="qrcode float-btn-qrcode"
|
||||
data-qrcode="<?php echo esc_attr(get_permalink() ?: home_url('/')); ?>"
|
||||
data-size="120"
|
||||
></div>
|
||||
<?php if ($item_text !== '') : ?><p class="float-btn-qrcode__title"><?php echo esc_html($item_text); ?></p><?php endif; ?>
|
||||
</div>
|
||||
</a>
|
||||
<?php else :
|
||||
$hover_html = $item['hover'] ?? '';
|
||||
$hover_width = (int) ($item['hover_width'] ?? 200);
|
||||
?>
|
||||
<a href="<?php echo esc_url($item_url ?: '#'); ?>" class="<?php echo esc_attr($item_class); ?>"<?php echo $item_target; echo $style_attr; ?>>
|
||||
<?php echo aksrt_icon_markup((string) $item_icon); // pre-escaped ?>
|
||||
<?php if ($hover_html !== '') : ?>
|
||||
<div class="hover-show-con"<?php echo $hover_width !== 200 ? ' style="min-width:' . (int) $hover_width . 'px"' : ''; ?>>
|
||||
<div class="float-right__html"><?php echo wp_kses_post($hover_html); ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach;
|
||||
// After loop: inject missing shop cart once. Never auto-inject pay_vip
|
||||
// (admin may already add one; previous in-loop inject created duplicates).
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($tabbar_enabled) :
|
||||
$tabbar_use_single_extend = function_exists('aksrt_should_use_footer_tabbar_single_extend')
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ require_once AKSRT_DIR . '/inc/helpers/header/aksrt-header-render.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/header/aksrt-header-navbar.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/header/aksrt-header-mobile.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/header/aksrt-header-menu-search.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-runtime-win.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-float-buttons.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-widgets-markup.php';
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
/**
|
||||
* 右侧悬浮按钮前台输出(结构对齐参考主题 zib_float_right())。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_float_btn_style_attr')) :
|
||||
/**
|
||||
* 自定义颜色 → 内联 CSS 变量。
|
||||
*
|
||||
* @param mixed $color
|
||||
*/
|
||||
function aksrt_float_btn_style_attr($color): string {
|
||||
$color = is_array($color) ? $color : [];
|
||||
$style = '';
|
||||
if (!empty($color['color'])) {
|
||||
$style .= '--this-color:' . $color['color'] . ';';
|
||||
}
|
||||
if (!empty($color['bg'])) {
|
||||
$style .= '--this-bg:' . $color['bg'] . ';';
|
||||
}
|
||||
return $style !== '' ? ' style="' . esc_attr($style) . '"' : '';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_float_btn_tooltip_attr')) :
|
||||
/**
|
||||
* PC 端 tooltip(参考主题仅 PC 输出 data-toggle)。
|
||||
*/
|
||||
function aksrt_float_btn_tooltip_attr(string $title, bool $is_mobile): string {
|
||||
$attr = !$is_mobile ? ' data-toggle="tooltip"' : '';
|
||||
return $attr . ' data-placement="left" title="' . esc_attr($title) . '"';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_float_pay_vip_html')) :
|
||||
/**
|
||||
* VIP 购买按钮:登录且未满级 → .pay-vip;访客 → .signin-loader。
|
||||
*/
|
||||
function aksrt_get_float_pay_vip_html(string $style, bool $is_mobile): string {
|
||||
if (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::close_sign()) {
|
||||
return '';
|
||||
}
|
||||
$vip = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
$vip1_s = !empty($vip['levels'][1]['enabled']);
|
||||
$vip2_s = !empty($vip['levels'][2]['enabled']);
|
||||
if (!$vip1_s && !$vip2_s) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$icon = '<svg class="icon" aria-hidden="true"><use xlink:href="#icon-vip_1"></use></svg>';
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if (!$user_id) {
|
||||
return '<a' . $style . ' class="float-btn signin-loader"' . aksrt_float_btn_tooltip_attr(__('开通会员', 'aksrt'), $is_mobile) . ' href="javascript:;">' . $icon . '</a>';
|
||||
}
|
||||
|
||||
$level = function_exists('aksrt_get_user_vip_level') ? (int) aksrt_get_user_vip_level($user_id) : 0;
|
||||
if ($level && !($level < 2 && $vip2_s)) {
|
||||
return '';
|
||||
}
|
||||
$title = 1 === $level ? __('升级会员', 'aksrt') : __('开通会员', 'aksrt');
|
||||
$open_level = $vip2_s ? 2 : 1;
|
||||
|
||||
return '<a' . $style . ' class="float-btn pay-vip" vip-level="' . esc_attr((string) $open_level) . '"' . aksrt_float_btn_tooltip_attr($title, $is_mobile) . ' href="javascript:;">' . $icon . '</a>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_float_add_html')) :
|
||||
/**
|
||||
* 发布按钮:单项直接链接;多项 hover 下拉 .drop-newadd。
|
||||
*
|
||||
* @param string[] $types
|
||||
*/
|
||||
function aksrt_get_float_add_html(array $types, string $icon, string $style): string {
|
||||
if (!$types || !function_exists('aksrt_get_new_add_actions')) {
|
||||
return '';
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::close_sign()) {
|
||||
return '';
|
||||
}
|
||||
$actions = aksrt_get_new_add_actions($types);
|
||||
if (!$actions) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$icon_html = $icon !== '' ? aksrt_icon_markup($icon) : '';
|
||||
if ($icon_html === '') {
|
||||
$icon_html = '<i class="fa fas fa-plus" aria-hidden="true"></i>';
|
||||
}
|
||||
|
||||
if (count($actions) === 1) {
|
||||
$a = $actions[0];
|
||||
return '<a' . $style . ' rel="nofollow" class="newadd-btns float-btn add-btn" title="' . esc_attr($a['label']) . '" href="' . esc_url($a['url']) . '">' . $icon_html . '</a>';
|
||||
}
|
||||
|
||||
$btns = '';
|
||||
foreach ($actions as $a) {
|
||||
$btns .= '<a rel="nofollow" class="btn-newadd" href="' . esc_url($a['url']) . '">'
|
||||
. '<icon class="jb-green">' . aksrt_icon_markup((string) $a['icon']) . '</icon>'
|
||||
. '<text>' . esc_html($a['label']) . '</text></a>';
|
||||
}
|
||||
|
||||
return '<span' . $style . ' class="newadd-btns hover-show float-btn add-btn">' . $icon_html
|
||||
. '<div class="hover-show-con dropdown-menu drop-newadd">' . $btns . '</div></span>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_float_more_html')) :
|
||||
/**
|
||||
* 更多(自定义)按钮组。
|
||||
*/
|
||||
function aksrt_get_float_more_html(array $buttons, bool $is_mobile): string {
|
||||
$html = '';
|
||||
foreach ($buttons as $opt) {
|
||||
if (!is_array($opt)) {
|
||||
continue;
|
||||
}
|
||||
if (!(($is_mobile && !empty($opt['m_s'])) || (!$is_mobile && !empty($opt['pc_s'])))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$style = aksrt_float_btn_style_attr($opt['color'] ?? []);
|
||||
$class = 'float-btn more-btn';
|
||||
$hover = '';
|
||||
$tag = 'a';
|
||||
$tooltip = true;
|
||||
$hover_html = (string) ($opt['hover'] ?? '');
|
||||
if ($hover_html !== '') {
|
||||
$tag = 'span';
|
||||
$tooltip = false;
|
||||
$class .= ' hover-show';
|
||||
$width = (int) ($opt['hover_width'] ?? 200);
|
||||
$hover_style = $width && 200 !== $width ? ' style="width:' . $width . 'px;"' : '';
|
||||
$hover = '<div' . $hover_style . ' class="hover-show-con dropdown-menu">' . wp_kses_post($hover_html) . '</div>';
|
||||
}
|
||||
|
||||
$icon = aksrt_icon_markup((string) ($opt['icon'] ?? ''));
|
||||
if ($icon === '') {
|
||||
$icon = '<i class="fa fas fa-heart" aria-hidden="true"></i>';
|
||||
}
|
||||
$url = (string) ($opt['link']['url'] ?? '');
|
||||
$href = ($tag === 'a' && $url !== '') ? esc_url($url) : 'javascript:;';
|
||||
$target = ($tag === 'a' && $url !== '' && ($opt['link']['target'] ?? '') === '_blank') ? ' target="_blank" rel="noopener"' : '';
|
||||
$desc = (string) ($opt['desc'] ?? '');
|
||||
$title = $desc !== '' ? ($tooltip ? aksrt_float_btn_tooltip_attr($desc, $is_mobile) : ' title="' . esc_attr($desc) . '"') : '';
|
||||
|
||||
if ('javascript:;' === $href) {
|
||||
$class .= ' nowave';
|
||||
}
|
||||
$href_attr = $tag === 'a' ? ' href="' . $href . '"' : '';
|
||||
|
||||
$html .= '<' . $tag . $style . ' class="' . esc_attr($class) . '"' . $target . $title . $href_attr . '>' . $icon . $hover . '</' . $tag . '>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_float_right')) :
|
||||
/**
|
||||
* 输出右侧悬浮按钮。
|
||||
*/
|
||||
function aksrt_float_right(): void {
|
||||
if (!class_exists('\AKSRT\Settings\Settings') || is_page_template('templates/pages/page-submit.php')) {
|
||||
return;
|
||||
}
|
||||
$settings = \AKSRT\Settings\Settings::get_floating_buttons_settings();
|
||||
$items = is_array($settings['items'] ?? null) ? $settings['items'] : [];
|
||||
if (!$items) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_mobile = wp_is_mobile();
|
||||
$btn = '';
|
||||
|
||||
foreach ($items as $opt) {
|
||||
$type = (string) ($opt['type'] ?? '');
|
||||
if ('more' !== $type && ((empty($opt['pc_s']) && !$is_mobile) || (empty($opt['m_s']) && $is_mobile))) {
|
||||
continue;
|
||||
}
|
||||
$style = aksrt_float_btn_style_attr($opt['color'] ?? []);
|
||||
|
||||
switch ($type) {
|
||||
case 'theme_mode':
|
||||
$btn .= '<a' . $style . ' class="float-btn toggle-theme hover-show"' . aksrt_float_btn_tooltip_attr(__('切换主题', 'aksrt'), $is_mobile) . ' href="javascript:;"><i class="fa fa-toggle-theme"></i></a>';
|
||||
break;
|
||||
|
||||
case 'back_top':
|
||||
$btn .= '<a' . $style . ' class="float-btn ontop fade"' . aksrt_float_btn_tooltip_attr(__('返回顶部', 'aksrt'), $is_mobile) . ' href="javascript:(scrollTopTo());"><i class="fa fas fa-angle-up em12"></i></a>';
|
||||
break;
|
||||
|
||||
case 'service_qq':
|
||||
$qq = (string) ($opt['qq'] ?? '');
|
||||
if ($qq !== '') {
|
||||
$href = 'https://wpa.qq.com/msgrd?v=3&uin=' . $qq . '&site=qq&menu=yes';
|
||||
$btn .= '<a' . $style . ' class="float-btn service-qq"' . aksrt_float_btn_tooltip_attr(__('QQ联系', 'aksrt'), $is_mobile) . ' target="_blank" rel="noopener" href="' . esc_url($href) . '"><i class="fa fab fa-qq"></i></a>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'pay_vip':
|
||||
$btn .= aksrt_get_float_pay_vip_html($style, $is_mobile);
|
||||
break;
|
||||
|
||||
case 'service_wechat':
|
||||
$img = (string) ($opt['wechat_img'] ?? '');
|
||||
if ($img !== '') {
|
||||
$alt = sprintf(__('扫码添加微信%s', 'aksrt'), ' - ' . get_bloginfo('name'));
|
||||
$img_attr = function_exists('aksrt_get_lazy_attr')
|
||||
? aksrt_get_lazy_attr('lazy_other', $img, 'radius4 relative')
|
||||
: ' class="radius4 relative" src="' . esc_url($img) . '"';
|
||||
$btn .= '<a' . $style . ' class="float-btn service-wechat hover-show nowave" title="' . esc_attr__('扫码添加微信', 'aksrt') . '" href="javascript:;"><i class="fa fab fa-weixin"></i>'
|
||||
. '<div class="hover-show-con dropdown-menu"><img width="100%"' . $img_attr . ' alt="' . esc_attr($alt) . '"></div></a>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'qrcode':
|
||||
$desc = (string) ($opt['desc'] ?? '');
|
||||
$desc = $desc !== '' ? '<div class="mt6 px12 muted-color">' . esc_html($desc) . '</div>' : '';
|
||||
$btn .= '<span' . $style . ' class="float-btn qrcode-btn hover-show service-wechat"><i class="fa fas fa-qrcode"></i>'
|
||||
. '<div class="hover-show-con dropdown-menu"><div class="qrcode" data-size="100"></div>' . $desc . '</div></span>';
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
$btn .= aksrt_get_float_add_html((array) ($opt['btns'] ?? []), (string) ($opt['icon'] ?? ''), $style);
|
||||
break;
|
||||
|
||||
case 'more':
|
||||
$btn .= aksrt_get_float_more_html((array) ($opt['buttons'] ?? []), $is_mobile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($btn === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$filter = (array) ($settings['filter_blur'] ?? []);
|
||||
$class = ($settings['style'] ?? 'round') . ' position-' . ($settings['position'] ?? 'bottom');
|
||||
$class .= (($is_mobile && in_array('m_s', $filter, true)) || (!$is_mobile && in_array('pc_s', $filter, true))) ? ' filter' : '';
|
||||
$class .= !empty($settings['scroll_hide']) ? ' scrolling-hide' : '';
|
||||
$class .= !empty($settings['scroll_down_hide']) ? ' scroll-down-hide' : '';
|
||||
|
||||
echo '<div class="float-right ' . esc_attr($class) . '">' . $btn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 各片段已转义
|
||||
}
|
||||
add_action('wp_footer', 'aksrt_float_right');
|
||||
endif;
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Float button commerce types + runtime window._win bridge (legacy parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_float_pay_vip_html')) :
|
||||
/**
|
||||
* Floating VIP open button.
|
||||
*
|
||||
* Always opens the site VIP modal for logged-in non-max VIP users
|
||||
* (class pay-vip → assets/js/pay-vip.js). Guests get signin-loader.
|
||||
* Admin "link/hover" fields are intentionally ignored for this type.
|
||||
*/
|
||||
function aksrt_get_float_pay_vip_html(array $item = []): string {
|
||||
$show_vip = false;
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
$show_vip = !empty($vs['enabled']);
|
||||
}
|
||||
if (!$show_vip) {
|
||||
return '';
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
$level = 0;
|
||||
if ($user_id > 0 && function_exists('aksrt_get_user_vip_level')) {
|
||||
$level = (int) aksrt_get_user_vip_level($user_id);
|
||||
}
|
||||
// Already top tier — no upsell entry on float rail.
|
||||
if ($level >= 2) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Prefer admin title; fallback by upgrade vs open.
|
||||
$admin_title = trim((string) ($item['text'] ?? ''));
|
||||
if ($admin_title !== '') {
|
||||
$title = $admin_title;
|
||||
} else {
|
||||
$title = $level === 1 ? __('升级会员', 'aksrt') : __('开通会员', 'aksrt');
|
||||
}
|
||||
|
||||
$icon = trim((string) ($item['icon'] ?? ''));
|
||||
$icon_html = aksrt_icon_markup($icon);
|
||||
if ($icon_html === '') {
|
||||
$icon_html = aksrt_icon_markup('fa fas fa-gem');
|
||||
}
|
||||
|
||||
// Logged-in: open modal. Guest: open login.
|
||||
$cls = $user_id > 0 ? 'float-btn pay-vip nowave' : 'float-btn signin-loader nowave';
|
||||
$style = [];
|
||||
if (!empty($item['color'])) {
|
||||
$style[] = '--this-color:' . esc_attr((string) $item['color']);
|
||||
}
|
||||
if (!empty($item['bg'])) {
|
||||
$style[] = '--this-bg:' . esc_attr((string) $item['bg']);
|
||||
}
|
||||
// Default VIP accent when admin left color empty.
|
||||
if (empty($item['color'])) {
|
||||
$style[] = '--this-color:#f5a623';
|
||||
}
|
||||
$style_attr = $style ? ' style="' . implode(';', $style) . '"' : '';
|
||||
|
||||
// Prefer upgrading one tier when already VIP1.
|
||||
$target_level = $user_id > 0 ? max(1, min(2, $level + 1)) : 1;
|
||||
$vip_level_attr = $user_id > 0
|
||||
? ' vip-level="' . esc_attr((string) $target_level) . '" data-vip-level="' . esc_attr((string) $target_level) . '"'
|
||||
: '';
|
||||
|
||||
return '<a class="' . esc_attr($cls) . '" href="javascript:;" role="button" aria-label="' . esc_attr($title) . '" title="' . esc_attr($title) . '"' . $style_attr . $vip_level_attr . '>'
|
||||
. $icon_html
|
||||
. '</a>';
|
||||
}
|
||||
endif;
|
||||
@@ -47,11 +47,6 @@ class GlobalController extends ControllerBase {
|
||||
'callback' => [$this, 'rest_handle_floating_buttons'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/floating-buttons/reorder', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_reorder_floating_buttons'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Friend Links
|
||||
register_rest_route(self::API_NAMESPACE, '/friend-links', [
|
||||
|
||||
@@ -93,188 +93,17 @@ trait GlobalControllerPart1 {
|
||||
|
||||
public function rest_handle_floating_buttons(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_floating_buttons_settings()
|
||||
: [
|
||||
'enabled' => true,
|
||||
'style' => 'round',
|
||||
'position' => 'bottom',
|
||||
'scroll_hide' => false,
|
||||
'scroll_down_hide' => true,
|
||||
'filter_blur' => [],
|
||||
'items' => [],
|
||||
];
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $settings]);
|
||||
return rest_ensure_response(['code' => 'success', 'data' => \AKSRT\Settings\FloatButtons::get()]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$items = isset($params['items']) ? array_map([$this, 'sanitize_floating_button_item'], (array) $params['items']) : [];
|
||||
$settings = \AKSRT\Settings\FloatButtons::sanitize($this->get_json_params($request));
|
||||
update_option(\AKSRT\Settings\FloatButtons::OPTION, $settings);
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
|
||||
$filter_blur = [];
|
||||
if (!empty($params['filter_blur']) && is_array($params['filter_blur'])) {
|
||||
foreach ($params['filter_blur'] as $device) {
|
||||
$device = sanitize_text_field((string) $device);
|
||||
if (in_array($device, ['pc', 'mobile'], true)) {
|
||||
$filter_blur[] = $device;
|
||||
}
|
||||
}
|
||||
$filter_blur = array_values(array_unique($filter_blur));
|
||||
}
|
||||
|
||||
$settings = [
|
||||
'enabled' => !empty($params['enabled']),
|
||||
'style' => in_array($params['style'] ?? '', ['round', 'square'], true) ? $params['style'] : 'round',
|
||||
'position' => in_array($params['position'] ?? '', ['bottom', 'center'], true) ? $params['position'] : 'bottom',
|
||||
'scroll_hide' => !empty($params['scroll_hide']),
|
||||
'scroll_down_hide' => !empty($params['scroll_down_hide']),
|
||||
'max' => max(1, min(10, (int) ($params['max'] ?? 5))),
|
||||
'size' => max(32, min(64, (int) ($params['size'] ?? 44))),
|
||||
'spacing' => max(4, min(24, (int) ($params['spacing'] ?? 10))),
|
||||
'shadow' => !empty($params['shadow']),
|
||||
'animation' => !empty($params['animation']),
|
||||
'opacity' => max(30, min(100, (int) ($params['opacity'] ?? 100))),
|
||||
'active_color' => sanitize_hex_color($params['active_color'] ?? '') ?: '',
|
||||
'bg_color' => sanitize_hex_color($params['bg_color'] ?? '') ?: '',
|
||||
'pc_show' => !empty($params['pc_show']),
|
||||
'm_show' => !empty($params['m_show']),
|
||||
'filter_blur' => $filter_blur,
|
||||
'items' => $items,
|
||||
];
|
||||
|
||||
update_option('aksrt_floating_buttons', $settings);
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Floating button settings saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_reorder_floating_buttons(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$ordered_ids = $params['ids'] ?? [];
|
||||
|
||||
if (!is_array($ordered_ids)) {
|
||||
return new \WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'aksrt')], 400);
|
||||
}
|
||||
|
||||
$settings = get_option('aksrt_floating_buttons', []);
|
||||
$items = $settings['items'] ?? [];
|
||||
|
||||
$order_map = array_flip($ordered_ids);
|
||||
usort($items, function ($a, $b) use ($order_map) {
|
||||
$idx_a = $order_map[($a['id'] ?? '')] ?? 999;
|
||||
$idx_b = $order_map[($b['id'] ?? '')] ?? 999;
|
||||
return $idx_a <=> $idx_b;
|
||||
});
|
||||
|
||||
foreach ($items as $index => &$item) {
|
||||
$item['sort_order'] = $index;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$settings['items'] = $items;
|
||||
update_option('aksrt_floating_buttons', $settings);
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
private function sanitize_floating_button_item(array $item): array {
|
||||
$valid_types = ['back_top', 'theme_mode', 'qrcode', 'service_qq', 'service_wechat', 'pay_vip', 'custom', 'add'];
|
||||
$type = sanitize_text_field($item['type'] ?? 'custom');
|
||||
if (!in_array($type, $valid_types, true)) {
|
||||
$type = 'custom';
|
||||
}
|
||||
|
||||
$hover_width = (int) ($item['hover_width'] ?? 200);
|
||||
if ($hover_width < 120) {
|
||||
$hover_width = 120;
|
||||
}
|
||||
if ($hover_width > 480) {
|
||||
$hover_width = 480;
|
||||
}
|
||||
|
||||
$qq = preg_replace('/\D+/', '', (string) ($item['qq'] ?? ''));
|
||||
$action_mode = sanitize_text_field($item['action_mode'] ?? 'link');
|
||||
if (!in_array($action_mode, ['link', 'qrcode'], true)) {
|
||||
$action_mode = 'link';
|
||||
}
|
||||
$qr_size = (int) ($item['qr_size'] ?? 120);
|
||||
if ($qr_size < 80) {
|
||||
$qr_size = 80;
|
||||
}
|
||||
if ($qr_size > 240) {
|
||||
$qr_size = 240;
|
||||
}
|
||||
|
||||
$add_items = [];
|
||||
if (!empty($item['add_items']) && is_array($item['add_items'])) {
|
||||
foreach ($item['add_items'] as $add_item) {
|
||||
if (!is_array($add_item)) {
|
||||
continue;
|
||||
}
|
||||
$label = sanitize_text_field($add_item['label'] ?? '');
|
||||
$url = esc_url_raw($add_item['url'] ?? '');
|
||||
$icon_item = aksrt_sanitize_icon_value((string) ($add_item['icon'] ?? ''));
|
||||
if ($label === '' && $url === '') {
|
||||
continue;
|
||||
}
|
||||
$add_items[] = [
|
||||
'id' => sanitize_text_field($add_item['id'] ?? uniqid('add_')),
|
||||
'label' => $label,
|
||||
'url' => $url,
|
||||
'icon' => $icon_item,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$icon = aksrt_sanitize_icon_value((string) ($item['icon'] ?? ''));
|
||||
$icon_dark = aksrt_sanitize_icon_value((string) ($item['icon_dark'] ?? ''));
|
||||
$icon_light = aksrt_sanitize_icon_value((string) ($item['icon_light'] ?? ''));
|
||||
if ($type === 'theme_mode') {
|
||||
$icon = 'fa fas fa-sun';
|
||||
$icon_dark = '';
|
||||
$icon_light = '';
|
||||
}
|
||||
// VIP float always uses modal path; drop stale link/hover and normalize icons.
|
||||
$link = esc_url_raw($item['link'] ?? '');
|
||||
$hover = wp_kses_post($item['hover'] ?? '');
|
||||
if ($type === 'pay_vip') {
|
||||
$link = '';
|
||||
$hover = '';
|
||||
if ($icon === '' || str_contains($icon, 'fa-gem')) {
|
||||
$icon = 'fa fas fa-gem';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => sanitize_text_field($item['id'] ?? uniqid('float_')),
|
||||
'type' => $type,
|
||||
'icon' => $icon,
|
||||
'icon_dark' => $icon_dark,
|
||||
'icon_light' => $icon_light,
|
||||
'text' => sanitize_text_field($item['text'] ?? ''),
|
||||
'desc' => sanitize_text_field($item['desc'] ?? ''),
|
||||
'action_mode' => $action_mode,
|
||||
'qq' => $qq,
|
||||
'qrcode_img' => esc_url_raw($item['qrcode_img'] ?? ''),
|
||||
'wechat_img' => esc_url_raw($item['wechat_img'] ?? ''),
|
||||
'link' => $link,
|
||||
'add_items' => $add_items,
|
||||
'hover' => $hover,
|
||||
'qr_size' => $qr_size,
|
||||
'hover_width' => $hover_width,
|
||||
'pc_s' => !empty($item['pc_s']),
|
||||
'm_s' => !empty($item['m_s']),
|
||||
'color' => sanitize_text_field($item['color'] ?? ''),
|
||||
'bg' => sanitize_text_field($item['bg'] ?? ''),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
'sort_order' => (int) ($item['sort_order'] ?? 0),
|
||||
];
|
||||
return rest_ensure_response([
|
||||
'code' => 'success',
|
||||
'message' => __('Floating button settings saved.', 'aksrt'),
|
||||
'data' => $settings,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +532,6 @@ trait ScriptsPart1c {
|
||||
'searchPanelId' => 'nav-search-panel',
|
||||
'drawerId' => 'mobile-navbar',
|
||||
'userMenuId' => 'sub-user-box',
|
||||
'floatActionsId' => 'float-right-btns',
|
||||
'tabbarId' => 'footer-tabbar',
|
||||
],
|
||||
'themeUri' => get_template_directory_uri(),
|
||||
|
||||
@@ -131,7 +131,7 @@ class InlineStyleBuilder {
|
||||
}
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings') && !empty(\AKSRT\Settings\Settings::get_value('close_backdrop', false))) {
|
||||
echo '<style id="aksrt-no-backdrop">.aksrt-header,.header-inner,.site-header,.header,.mobile-nav-overlay,.modal-backdrop,.aksrt-drawer-overlay,.aksrt-float-btn,.sidebar-inner{backdrop-filter:none!important;-webkit-backdrop-filter:none!important;background-color:var(--bg-card,#fff)!important}</style>' . "\n";
|
||||
echo '<style id="aksrt-no-backdrop">.aksrt-header,.header-inner,.site-header,.header,.mobile-nav-overlay,.modal-backdrop,.aksrt-drawer-overlay,.sidebar-inner{backdrop-filter:none!important;-webkit-backdrop-filter:none!important;background-color:var(--bg-card,#fff)!important}</style>' . "\n";
|
||||
}
|
||||
echo '<style id="aksrt-runtime-components">'
|
||||
. '.aksrt-search-hot-keywords{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:12px;font-family:var(--font-ui);}'
|
||||
|
||||
@@ -106,21 +106,12 @@ trait CategoryIntegrationGroups {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取右侧浮动按钮完整配置
|
||||
* @return array {enabled, style, position, scroll_hide, scroll_down_hide, filter_blur, items}
|
||||
* 获取右侧浮动按钮完整配置(结构见 FloatButtons::defaults())。
|
||||
*/
|
||||
public static function get_floating_buttons_settings(): array {
|
||||
return Integration::get_floating_buttons_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取右侧浮动按钮项(仅返回启用的,按排序)
|
||||
* @return array
|
||||
*/
|
||||
public static function get_floating_buttons_items(): array {
|
||||
return Integration::get_floating_buttons_items();
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizer 注册(可选)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* 右侧悬浮按钮:唯一的默认值 + 规范化来源(读取与保存共用)。
|
||||
*
|
||||
* 结构对齐参考主题 float_btn(sortable,固定类型,数组顺序即显示顺序)。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Settings;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class FloatButtons {
|
||||
|
||||
public const OPTION = 'aksrt_floating_buttons';
|
||||
|
||||
/** 固定按钮类型(默认显示顺序)。 */
|
||||
public const TYPES = ['more', 'pay_vip', 'add', 'service_qq', 'service_wechat', 'theme_mode', 'qrcode', 'back_top'];
|
||||
|
||||
/** 发布按钮可选类型。 */
|
||||
public const ADD_BTNS = ['post'];
|
||||
|
||||
/**
|
||||
* 默认配置。
|
||||
*/
|
||||
public static function defaults(): array {
|
||||
$empty_color = ['color' => '', 'bg' => ''];
|
||||
return [
|
||||
'style' => 'round',
|
||||
'position' => 'bottom',
|
||||
'filter_blur' => ['m_s'],
|
||||
'scroll_hide' => false,
|
||||
'scroll_down_hide' => true,
|
||||
'items' => [
|
||||
[
|
||||
'type' => 'more',
|
||||
'buttons' => [self::default_more_button()],
|
||||
],
|
||||
['type' => 'pay_vip', 'pc_s' => true, 'm_s' => true, 'color' => ['color' => '#f2c97d', 'bg' => 'rgba(62,62,67,0.9)']],
|
||||
['type' => 'add', 'pc_s' => true, 'm_s' => false, 'color' => $empty_color, 'btns' => ['post'], 'icon' => 'fa fas fa-pencil-alt'],
|
||||
['type' => 'service_qq', 'pc_s' => false, 'm_s' => false, 'color' => $empty_color, 'qq' => ''],
|
||||
['type' => 'service_wechat', 'pc_s' => true, 'm_s' => true, 'color' => $empty_color, 'wechat_img' => ''],
|
||||
['type' => 'theme_mode', 'pc_s' => false, 'm_s' => false, 'color' => $empty_color],
|
||||
['type' => 'qrcode', 'pc_s' => true, 'm_s' => false, 'color' => $empty_color, 'desc' => __('在手机上浏览此页面', 'aksrt')],
|
||||
['type' => 'back_top', 'pc_s' => true, 'm_s' => true, 'color' => $empty_color],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认「更多按钮」子项。
|
||||
*/
|
||||
public static function default_more_button(): array {
|
||||
return [
|
||||
'desc' => '',
|
||||
'icon' => 'fa fas fa-heart',
|
||||
'link' => ['url' => '', 'target' => ''],
|
||||
'hover' => '',
|
||||
'hover_width' => 200,
|
||||
'pc_s' => false,
|
||||
'm_s' => false,
|
||||
'color' => ['color' => '', 'bg' => 'rgba(255, 111, 6, 0.2)'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取已规范化的配置。
|
||||
*/
|
||||
public static function get(): array {
|
||||
$saved = get_option(self::OPTION, []);
|
||||
if (!is_array($saved) || $saved === []) {
|
||||
return self::defaults();
|
||||
}
|
||||
return self::sanitize($saved);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化完整配置(REST 保存与读取共用)。
|
||||
*/
|
||||
public static function sanitize(array $raw): array {
|
||||
$defaults = self::defaults();
|
||||
|
||||
$filter_blur = [];
|
||||
foreach ((array) ($raw['filter_blur'] ?? []) as $device) {
|
||||
$device = (string) $device;
|
||||
if (in_array($device, ['pc_s', 'm_s'], true) && !in_array($device, $filter_blur, true)) {
|
||||
$filter_blur[] = $device;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'style' => in_array($raw['style'] ?? '', ['round', 'square'], true) ? $raw['style'] : 'round',
|
||||
'position' => in_array($raw['position'] ?? '', ['bottom', 'center'], true) ? $raw['position'] : 'bottom',
|
||||
'filter_blur' => $filter_blur,
|
||||
'scroll_hide' => !empty($raw['scroll_hide']),
|
||||
'scroll_down_hide' => !empty($raw['scroll_down_hide']),
|
||||
'items' => self::sanitize_items((array) ($raw['items'] ?? []), $defaults['items']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 按提交顺序保留已知类型,重复丢弃,缺失类型用默认值补到末尾。
|
||||
*/
|
||||
private static function sanitize_items(array $raw_items, array $default_items): array {
|
||||
$default_map = [];
|
||||
foreach ($default_items as $item) {
|
||||
$default_map[$item['type']] = $item;
|
||||
}
|
||||
|
||||
$out = [];
|
||||
$seen = [];
|
||||
foreach ($raw_items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$type = (string) ($item['type'] ?? '');
|
||||
if (!isset($default_map[$type]) || isset($seen[$type])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$type] = true;
|
||||
$out[] = self::sanitize_item($type, $item, $default_map[$type]);
|
||||
}
|
||||
foreach ($default_items as $item) {
|
||||
if (!isset($seen[$item['type']])) {
|
||||
$out[] = $item;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function sanitize_item(string $type, array $item, array $default): array {
|
||||
if ($type === 'more') {
|
||||
$buttons = [];
|
||||
foreach ((array) ($item['buttons'] ?? []) as $button) {
|
||||
if (is_array($button)) {
|
||||
$buttons[] = self::sanitize_more_button($button);
|
||||
}
|
||||
}
|
||||
// 参考主题:至少保留一个。
|
||||
if ($buttons === []) {
|
||||
$buttons[] = self::default_more_button();
|
||||
}
|
||||
return ['type' => 'more', 'buttons' => $buttons];
|
||||
}
|
||||
|
||||
$out = [
|
||||
'type' => $type,
|
||||
'pc_s' => !empty($item['pc_s']),
|
||||
'm_s' => !empty($item['m_s']),
|
||||
'color' => self::sanitize_color_group($item['color'] ?? []),
|
||||
];
|
||||
|
||||
switch ($type) {
|
||||
case 'add':
|
||||
$btns = [];
|
||||
foreach ((array) ($item['btns'] ?? []) as $btn) {
|
||||
$btn = (string) $btn;
|
||||
if (in_array($btn, self::ADD_BTNS, true) && !in_array($btn, $btns, true)) {
|
||||
$btns[] = $btn;
|
||||
}
|
||||
}
|
||||
$out['btns'] = $btns;
|
||||
$out['icon'] = aksrt_sanitize_icon_value((string) ($item['icon'] ?? ''));
|
||||
break;
|
||||
case 'service_qq':
|
||||
$out['qq'] = (string) preg_replace('/\D+/', '', (string) ($item['qq'] ?? ''));
|
||||
break;
|
||||
case 'service_wechat':
|
||||
$out['wechat_img'] = esc_url_raw((string) ($item['wechat_img'] ?? ''));
|
||||
break;
|
||||
case 'qrcode':
|
||||
$out['desc'] = sanitize_text_field((string) ($item['desc'] ?? ''));
|
||||
break;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function sanitize_more_button(array $button): array {
|
||||
$link = is_array($button['link'] ?? null) ? $button['link'] : [];
|
||||
$target = (string) ($link['target'] ?? '');
|
||||
|
||||
return [
|
||||
'desc' => sanitize_text_field((string) ($button['desc'] ?? '')),
|
||||
'icon' => aksrt_sanitize_icon_value((string) ($button['icon'] ?? '')),
|
||||
'link' => [
|
||||
'url' => esc_url_raw((string) ($link['url'] ?? '')),
|
||||
'target' => $target === '_blank' ? '_blank' : '',
|
||||
],
|
||||
'hover' => wp_kses_post((string) ($button['hover'] ?? '')),
|
||||
'hover_width' => max(50, min(500, (int) ($button['hover_width'] ?? 200))),
|
||||
'pc_s' => !empty($button['pc_s']),
|
||||
'm_s' => !empty($button['m_s']),
|
||||
'color' => self::sanitize_color_group($button['color'] ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $group
|
||||
* @return array{color:string,bg:string}
|
||||
*/
|
||||
private static function sanitize_color_group($group): array {
|
||||
$group = is_array($group) ? $group : [];
|
||||
return [
|
||||
'color' => self::sanitize_color((string) ($group['color'] ?? '')),
|
||||
'bg' => self::sanitize_color((string) ($group['bg'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许 #hex(3/4/6/8 位)与 rgb()/rgba()。
|
||||
*/
|
||||
public static function sanitize_color(string $value): string {
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/', $value)) {
|
||||
return $value;
|
||||
}
|
||||
if (preg_match('/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d+)\s*)?\)$/', $value)) {
|
||||
return $value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -260,122 +260,10 @@ class Integration {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取右侧浮动按钮完整配置
|
||||
* @return array {enabled, style, position, scroll_hide, scroll_down_hide, filter_blur, items}
|
||||
* 获取右侧浮动按钮完整配置(结构见 FloatButtons::defaults())。
|
||||
*/
|
||||
public static function get_floating_buttons_settings(): array {
|
||||
$defaults = [
|
||||
'enabled' => true,
|
||||
'style' => 'round',
|
||||
'position' => 'bottom',
|
||||
'scroll_hide' => false,
|
||||
'scroll_down_hide' => true,
|
||||
'max' => 5,
|
||||
'size' => 44,
|
||||
'spacing' => 10,
|
||||
'shadow' => true,
|
||||
'animation' => true,
|
||||
'opacity' => 100,
|
||||
'active_color' => '',
|
||||
'bg_color' => '',
|
||||
'pc_show' => true,
|
||||
'm_show' => true,
|
||||
'filter_blur' => ['mobile'],
|
||||
'items' => [
|
||||
[
|
||||
'id' => 'btn_wechat',
|
||||
'type' => 'service_wechat',
|
||||
'icon' => 'fa fab fa-weixin',
|
||||
'icon_dark' => '',
|
||||
'icon_light' => '',
|
||||
'text' => __('微信', 'aksrt'),
|
||||
'desc' => '',
|
||||
'action_mode'=> 'qrcode',
|
||||
'qq' => '',
|
||||
'qrcode_img' => '',
|
||||
'wechat_img' => '',
|
||||
'link' => '',
|
||||
'add_items' => [],
|
||||
'hover' => '',
|
||||
'qr_size' => 120,
|
||||
'hover_width' => 200,
|
||||
'pc_s' => true,
|
||||
'm_s' => true,
|
||||
'color' => '',
|
||||
'bg' => '',
|
||||
'enabled' => true,
|
||||
'sort_order' => 0,
|
||||
],
|
||||
[
|
||||
'id' => 'btn_theme',
|
||||
'type' => 'theme_mode',
|
||||
'icon' => 'fa fas fa-sun',
|
||||
'icon_dark' => '',
|
||||
'icon_light' => '',
|
||||
'text' => __('主题', 'aksrt'),
|
||||
'desc' => __('切换主题', 'aksrt'),
|
||||
'action_mode'=> 'link',
|
||||
'qq' => '',
|
||||
'qrcode_img' => '',
|
||||
'wechat_img' => '',
|
||||
'link' => '',
|
||||
'add_items' => [],
|
||||
'hover' => '',
|
||||
'qr_size' => 120,
|
||||
'hover_width' => 200,
|
||||
'pc_s' => true,
|
||||
'm_s' => false,
|
||||
'color' => '',
|
||||
'bg' => '',
|
||||
'enabled' => true,
|
||||
'sort_order' => 1,
|
||||
],
|
||||
[
|
||||
'id' => 'btn_top',
|
||||
'type' => 'back_top',
|
||||
'icon' => 'fa fas fa-chevron-up',
|
||||
'icon_dark' => '',
|
||||
'icon_light' => '',
|
||||
'text' => __('顶部', 'aksrt'),
|
||||
'desc' => __('返回顶部', 'aksrt'),
|
||||
'action_mode'=> 'link',
|
||||
'qq' => '',
|
||||
'qrcode_img' => '',
|
||||
'wechat_img' => '',
|
||||
'link' => '',
|
||||
'add_items' => [],
|
||||
'hover' => '',
|
||||
'qr_size' => 120,
|
||||
'hover_width' => 200,
|
||||
'pc_s' => true,
|
||||
'm_s' => true,
|
||||
'color' => '',
|
||||
'bg' => '',
|
||||
'enabled' => true,
|
||||
'sort_order' => 2,
|
||||
],
|
||||
],
|
||||
];
|
||||
$saved = get_option('aksrt_floating_buttons', []);
|
||||
if (!is_array($saved)) {
|
||||
$saved = [];
|
||||
}
|
||||
return array_merge($defaults, $saved);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取右侧浮动按钮项(仅返回启用的,按排序)
|
||||
* @return array
|
||||
*/
|
||||
public static function get_floating_buttons_items(): array {
|
||||
$settings = self::get_floating_buttons_settings();
|
||||
if (empty($settings['enabled'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = array_filter($settings['items'], fn($item) => !empty($item['enabled']));
|
||||
usort($items, fn($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0));
|
||||
return array_values($items);
|
||||
return FloatButtons::get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -96,26 +96,33 @@ test('FA5 loads under a single canonical handle everywhere', () => {
|
||||
|
||||
test('custom settings pages and standalone save paths speak the three icon formats', () => {
|
||||
// 手机底部Tab / 右侧悬浮按钮 custom pages render the shared picker.
|
||||
for (const page of ['assets/admin/src/pages/content/FooterTabbarSettings.vue', 'assets/admin/src/pages/content/FloatingButtonsSettings.vue']) {
|
||||
// FaIconPicker declares modelValue / emits update:modelValue; the Naive UI
|
||||
// v-model:value convention binds a prop the picker never reads, so
|
||||
// selection clicks would be dropped.
|
||||
const pickerFiles = [
|
||||
'assets/admin/src/pages/content/FooterTabbarSettings.vue',
|
||||
'assets/admin/src/components/floating/FloatBtnItemPanel.vue',
|
||||
'assets/admin/src/components/floating/FloatMoreButtonsEditor.vue',
|
||||
];
|
||||
for (const page of pickerFiles) {
|
||||
const source = read(page);
|
||||
assert.match(source, /import FaIconPicker from '@\/components\/form\/FaIconPicker\.vue'/);
|
||||
assert.match(source, /import IconValue from '@\/components\/form\/IconValue\.vue'/);
|
||||
assert.match(source, /<FaIconPicker/, `${page} uses the picker for icon fields`);
|
||||
// FaIconPicker declares modelValue / emits update:modelValue; the Naive UI
|
||||
// v-model:value convention binds a prop the picker never reads, so
|
||||
// selection clicks would be dropped.
|
||||
assert.doesNotMatch(source, /<FaIconPicker[^>]*v-model:value/, `${page} must not bind the picker with v-model:value`);
|
||||
assert.match(source, /<FaIconPicker v-model=/, `${page} binds the picker with v-model`);
|
||||
}
|
||||
assert.match(read('assets/admin/src/pages/content/FooterTabbarSettings.vue'), /import IconValue from '@\/components\/form\/IconValue\.vue'/);
|
||||
assert.match(read('assets/admin/src/pages/content/FooterTabbarSettings.vue'), /<FaIconPicker v-model=/);
|
||||
assert.match(read('assets/admin/src/components/floating/FloatBtnItemPanel.vue'), /<FaIconPicker v-model=/);
|
||||
assert.match(read('assets/admin/src/components/floating/FloatMoreButtonsEditor.vue'), /import IconValue from '@\/components\/form\/IconValue\.vue'/);
|
||||
// Their REST save paths keep customsvg markup intact.
|
||||
const controller = read('src/Admin/GlobalControllerPart1.php');
|
||||
assert.match(controller, /aksrt_sanitize_icon_value\(/);
|
||||
assert.doesNotMatch(controller, /preg_match\('\/\^fa\\s\+/, 'no FA-only regex gate on icon fields');
|
||||
assert.match(read('src/Settings/FloatButtons.php'), /aksrt_sanitize_icon_value\(/);
|
||||
// Frontend consumers render every format.
|
||||
const footer = read('footer.php');
|
||||
assert.match(footer, /aksrt_icon_markup\(\(string\) \$tab_icon\)/);
|
||||
assert.match(footer, /aksrt_icon_markup\(\(string\) \$item_icon\)/);
|
||||
assert.match(read('inc/helpers/aksrt-runtime-win.php'), /aksrt_icon_markup\(\$icon\)/);
|
||||
assert.match(read('inc/helpers/aksrt-float-buttons.php'), /aksrt_icon_markup\(\(string\) \(\$opt\['icon'\]/);
|
||||
// The popover tabs must wrap instead of overflowing the panel.
|
||||
assert.match(read('assets/admin/src/components/form/FaIconPicker.vue'), /flex-wrap: wrap/);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-09-24T08:11:53+00:00",
|
||||
"generated_at": "2026-09-26T12:42:35+00:00",
|
||||
"errors": [],
|
||||
"warnings": [
|
||||
"customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",
|
||||
@@ -61,7 +61,7 @@
|
||||
],
|
||||
"info": [
|
||||
"PHP schema domains: global, page_display, post_list, permissions, users, payment, extensions, tools, ExtensionsAudit, ExtensionsIP, ExtensionsPush, ToolsBackup, ToolsHealth, ToolsImportExport",
|
||||
"Menu pages discovered: 61",
|
||||
"Menu pages discovered: 60",
|
||||
"admin build ok; chunks=30"
|
||||
],
|
||||
"domains": {
|
||||
@@ -118,7 +118,6 @@
|
||||
"sections": [
|
||||
"attachment",
|
||||
"capabilities",
|
||||
"download",
|
||||
"login",
|
||||
"moderation"
|
||||
],
|
||||
@@ -227,13 +226,13 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"page_count": 61,
|
||||
"page_count": 60,
|
||||
"build": {
|
||||
"present": true,
|
||||
"entry": true,
|
||||
"missing_chunks": [],
|
||||
"chunks": [
|
||||
"SchemaPage.QsKL1FOA.js",
|
||||
"SchemaPage.DXZIPvzD.js",
|
||||
"vendor-vue.BYzo-2I-.js",
|
||||
"FaIcon.B3OOcphx.js",
|
||||
"FaIconPicker.DHrC0Y72.js",
|
||||
@@ -248,8 +247,8 @@
|
||||
"vendor-draggable.YNevSrwY.js",
|
||||
"PageHeader.jmNZVzHG.js",
|
||||
"FooterTabbarSettings.CwSsjNlJ.js",
|
||||
"PostPageSettings.DBCiH7_B.js",
|
||||
"CommentSettings.DwLb53BZ.js",
|
||||
"PostPageSettings.D7aU7Dv6.js",
|
||||
"CommentSettings.CyFC7ABH.js",
|
||||
"PermissionsCapabilities.BeLUij9c.js",
|
||||
"CmsCredits.CTDJTkG-.js",
|
||||
"ThemeInfoPage.CbJKzVFa.js",
|
||||
|
||||
Reference in New Issue
Block a user