diff --git a/.gitignore b/.gitignore index 959bef15..3ddfbadd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /.specs/ /docs/ /tools/* +!/tools/build-public-assets.mjs /tests/* !/tools/wp-cli-visual-fixtures.php !/tests/fixtures/ diff --git a/assets/css/build/core.css b/assets/css/build/core.css new file mode 100644 index 00000000..a8552ead --- /dev/null +++ b/assets/css/build/core.css @@ -0,0 +1,2388 @@ +/* source: assets/css/core/tokens.css */ +/** + * AKSRT Design Tokens v7 — 1:1 legacy Alignment + * ------------------------------------------------------------ + * 单一真理源(Single Source of Truth) + * 所有颜色、字体、间距、动效都从此处读取,禁止在组件内硬编码。 + * 严格对齐 legacy main.css 的视觉值。 + */ + +:root { + /* ===== Primary — legacy theme-color (#f04494 pink) ===== */ + --color-primary: #f04494; + --color-primary-hover: #e0307e; + --color-primary-active: #c9206d; + --color-primary-soft: rgba(240, 68, 148, 0.10); + --color-primary-softer: rgba(240, 68, 148, 0.05); + --color-on-primary: #FFFFFF; + + /* ===== Ink (text) — 对齐 legacy --key-color / --main-color / --muted-color ===== */ + --color-ink: #333333; /* legacy --key-color */ + --color-ink-2: #4e5358; /* legacy --main-color */ + --color-ink-3: #777777; /* legacy --muted-color */ + --color-ink-4: #999999; /* legacy --muted-2-color */ + + /* ===== Surface — 对齐 legacy --body-bg-color / --main-bg-color / --muted-bg-color ===== */ + --surface-page: #f5f6f7; /* legacy --body-bg-color */ + --surface-1: #FFFFFF; /* legacy --main-bg-color */ + --surface-2: #eeeeee; /* legacy --muted-bg-color #eee */ + --surface-3: #e9ecef; /* 输入框 / code / 次级面 */ + --surface-4: #f8f8f8; /* 分页 / 标签 / hover 薄层 */ + + /* ===== Borders — 对齐 legacy --main-border-color ===== */ + --color-line: rgba(50, 50, 50, 0.06); + --color-line-soft: rgba(0, 0, 0, 0.03); + --color-line-stronger: rgba(50, 50, 50, 0.12); + + /* ===== State colors ===== */ + --color-success: #10B981; + --color-warning: #F59E0B; + --color-error: #EF4444; + --color-info: #3B82F6; + + /* ===== Accent (action green — legacy tabbar/CTA) ===== */ + --color-accent: #18a058; + --color-accent-soft: rgba(24, 160, 88, 0.12); + --color-accent-shadow: rgba(24, 160, 88, 0.28); + + /* ===== Fonts — 对齐 legacy(系统默认字体栈)===== */ + --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; + --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; + --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; + --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + + /* ===== Type scale ===== */ + --text-xs: 0.75rem; /* 12 */ + --text-sm: 0.875rem; /* 14 */ + --text-base: 1rem; /* 16 */ + --text-md: 1.125rem; /* 18 */ + --text-lg: 1.25rem; /* 20 */ + --text-xl: 1.5rem; /* 24 */ + --text-2xl: 1.875rem; /* 30 */ + --text-3xl: 2.25rem; /* 36 */ + --text-4xl: 3rem; /* 48 */ + --text-5xl: 3.75rem; /* 60 */ + --leading-tight: 1.2; + --leading-snug: 1.4; + --leading-normal: 1.6; /* legacy body line-height */ + --leading-relaxed: 1.6; + + /* ===== Spacing (4px base) ===== */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 32px; + --space-8: 40px; + --space-9: 48px; + --space-10: 64px; + --space-11: 80px; + --space-12: 96px; + + /* ===== Radius — 对齐 legacy --main-radius: 8px ===== */ + --radius-xs: 3px; + --radius-sm: 4px; + --radius: 8px; + --radius-lg: 8px; /* legacy main-radius — was 10px */ + --radius-xl: 12px; + --radius-2xl: 16px; + --radius-pill: 100px; + --radius-avatar: 50%; + + /* ===== Shadows — 对齐 legacy --main-shadow: rgba(116,116,116,0.08) ===== */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.03); + --shadow-sm: 0 0 10px rgba(116, 116, 116, 0.08); + --shadow: 0 0 10px rgba(116, 116, 116, 0.12); + --shadow-lg: 0 12px 32px rgba(18, 18, 18, 0.10); + --shadow-drawer: 0 18px 60px rgba(0, 0, 0, 0.20); + + /* ===== Layout — 对齐 legacy --mian-max-width: 1200px ===== */ + --nav-height: 60px; + --container: 1200px; + --container-wide: 1440px; + --container-read: 760px; + --container-page: 880px; + --sidebar-width: 311px; + --content-gap: 24px; + --section-gap: 24px; + --sidebar-bp: 992px; + + /* ===== Motion ===== */ + --ease: cubic-bezier(.4, 0, .2, 1); + --ease-out: cubic-bezier(.16, 1, .3, 1); + --ease-spring: cubic-bezier(.34, 1.56, .64, 1); + --dur-fast: 0.15s; + --dur: 0.25s; + --dur-slow: 0.4s; + + /* ===== Focus Ring — 对齐 legacy focus-shadow-color ===== */ + --focus-ring: 0 0 2px 3px rgba(253, 83, 161, 0.4); + + /* ===== Z-index layers ===== */ + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-overlay: 400; + --z-modal: 500; + --z-popover: 600; + --z-toast: 700; + --z-tooltip: 800; + + /* ===== Button sizes — 对齐 legacy ===== */ + --radius-btn: 4px; + --btn-sm-h: 28px; + --btn-md-h: 34px; + --btn-lg-h: 44px; + + /* ===== Card density — 对齐 legacy .box-body { padding: 15px } ===== */ + --card-padding: 15px; + --card-padding-sm: 10px; + --card-padding-lg: 20px; + --card-gap: 16px; + --card-gap-lg: 20px; + + /* ===== Skeleton loader ===== */ + --skeleton-base: var(--surface-2); + --skeleton-shine: var(--surface-1); + + /* ===== Extended tokens (used by components) ===== */ + --color-primary-light: var(--color-primary-soft); + --color-surface: var(--surface-1); + --color-line-strong: var(--color-line); + --color-bg-hover: var(--surface-2); + --color-border-light: var(--color-line-soft); + --color-fill: var(--surface-2); + --color-warning-bg: #fffbe6; + --color-warning-text: #ad6800; + + --text-secondary: var(--color-ink-3); + --text-tertiary: var(--color-ink-4); + + --surface-active: var(--surface-2); + --surface-card: var(--surface-1); + --surface-hover: var(--surface-2); + --surface-secondary: var(--surface-2); + + --radius-code: var(--radius); + --radius-full: 100px; + --shadow-xl: 0 0 10px rgba(116, 116, 116, 0.12); + --space-16: 64px; + --z-floating: 900; + + --transition-fast: 0.15s ease; + --transition-base: 0.25s ease; + + /* ===== Composite / Context tokens — 对齐 legacy ===== */ + --header-bg: var(--blur-bg); + --header-color: var(--color-ink-2); + --footer-bg: var(--surface-1); + --footer-color: var(--color-ink-4); + --blur-bg: rgba(255, 255, 255, 0.80); + --float-btn-bg: rgba(200, 200, 200, 0.40); + + /* ===== AKSRT-to-AKSRT canonical aliases ===== */ + --content-max: var(--container); + --font-heading: var(--font-ui); + --radius-card: var(--radius); + --radius-md: var(--radius); + --color-primary-50: var(--color-primary-soft); + --color-primary-dark: var(--color-primary-active); + --shadow-md: var(--shadow); + --mini-radius: calc(var(--radius) / 1.4); + --single-cover-scale: 35%; + + /* ===== Avatar size system — legacy --this-size 等价 ===== */ + --aksrt-avatar-size-xs: 24px; + --aksrt-avatar-size-sm: 32px; + --aksrt-avatar-size: 40px; + --aksrt-avatar-size-lg: 56px; + --aksrt-avatar-size-xl: 80px; + + /* ===== Post card thumbnail scale ===== */ + --aksrt-card-thumb-scale: 56.25%; /* 16:9 */ + + /* ===== Multi-color palette (for buttons / badges / gradients) ===== */ + --aksrt-rose: #f43f5e; + --aksrt-rose-soft: rgba(244, 63, 94, 0.12); + --aksrt-blue: #3b82f6; + --aksrt-blue-soft: rgba(59, 130, 246, 0.12); + --aksrt-green: #10b981; + --aksrt-green-soft: rgba(16, 185, 129, 0.12); + --aksrt-orange: #f97316; + --aksrt-orange-soft:rgba(249, 115, 22, 0.12); + --aksrt-purple: #8b5cf6; + --aksrt-purple-soft:rgba(139, 92, 246, 0.12); + --aksrt-cyan: #06b6d4; + --aksrt-cyan-soft: rgba(6, 182, 212, 0.12); + --aksrt-yellow: #eab308; + --aksrt-yellow-soft:rgba(234, 179, 8, 0.12); + --aksrt-red: #ef4444; + --aksrt-red-soft: rgba(239, 68, 68, 0.12); + --aksrt-teal: #14b8a6; + --aksrt-teal-soft: rgba(20, 184, 166, 0.12); + --aksrt-indigo: #6366f1; + --aksrt-indigo-soft:rgba(99, 102, 241, 0.12); + --aksrt-pink: #ec4899; + --aksrt-pink-soft: rgba(236, 72, 153, 0.12); + + /* ===== VIP / special gradients ===== */ + --aksrt-vip-gold-1: #f6d365; + --aksrt-vip-gold-2: #fda085; + --aksrt-grad-sunset-1: #f093fb; + --aksrt-grad-sunset-2: #f5576c; + --aksrt-grad-ocean-1: #4facfe; + --aksrt-grad-ocean-2: #00f2fe; + --aksrt-grad-forest-1: #43e97b; + --aksrt-grad-forest-2: #38f9d7; + --aksrt-grad-night-1: #a18cd1; + --aksrt-grad-night-2: #fbc2eb; + --aksrt-grad-fire-1: #f83600; + --aksrt-grad-fire-2: #f9d423; + + /* ===== Component sizing ===== */ + --aksrt-toggle-w: 44px; + --aksrt-toggle-h: 24px; + --aksrt-stepper-h: 36px; + --aksrt-badge-h: 22px; + + /* ===== Legacy name bridge ===== */ + --text-1: var(--color-ink); + --text-2: var(--color-ink-2); + --text-3: var(--color-ink-3); + --border: var(--color-line); + --color-text-1: var(--color-ink); + --color-text-2: var(--color-ink-2); + --color-text-3: var(--color-ink-3); + --color-primary-bg: var(--color-primary-softer); + --color-danger: var(--color-error); +} + +/* ============================================================ + * Blur-bg fallback cascade (mirrors legacy behaviour) + * ============================================================ */ +@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { + :root { + --blur-bg: var(--surface-1); + --header-bg: var(--surface-1); + } +} + +@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { + html.dark, + body.dark-theme { + --blur-bg: #37383a; + --header-bg: #37383a; + } +} + +body.modal-open, +body.dialog-open { + --blur-bg: var(--surface-1); + --header-bg: var(--surface-1); +} + +body.modal-open .site-nav, +body.modal-open .footer-tabbar, +body.modal-open .float-right .float-btn, +body.dialog-open .site-nav, +body.dialog-open .footer-tabbar, +body.dialog-open .float-right .float-btn { + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; +} + +html.dark body.modal-open, +html.dark body.dialog-open, +body.dark-theme.modal-open, +body.dark-theme.dialog-open { + --blur-bg: #37383a; + --header-bg: #37383a; +} + +/* ============================================================ + * Multi-level container responsive — 对齐 legacy --mian-max-width + * 不同视口宽度下容器自动调整 + * ============================================================ */ +@media (min-width: 992px) { + :root { + --container: 960px; + } +} + +@media (min-width: 1200px) { + :root { + --container: 1140px; + } +} + +@media (min-width: 1400px) { + :root { + --container: 1200px; + } +} + +/* ============================================================ + * Dark Theme — 对齐 legacy .dark-theme 精确 hex + * body.dark-theme 与 html.dark 同权(legacy 用 body class) + * ============================================================ */ +html.dark, +body.dark-theme { + --color-ink: #f8fafc; /* legacy --key-color */ + --color-ink-2: #e5eef7; /* legacy --main-color */ + --color-ink-3: #b4b6bb; /* legacy --muted-color */ + --color-ink-4: #888a8f; /* legacy --muted-2-color */ + + --surface-page: #292a2d; /* legacy --body-bg-color */ + --surface-1: #323335; /* legacy --main-bg-color */ + --surface-2: #2d2e31; /* legacy --muted-bg-color */ + --surface-3: #3a3c42; + --surface-4: #26282d; + + --color-line: rgba(114, 114, 114, 0.1); + --color-line-soft: rgba(184, 184, 184, 0.02); + --color-line-stronger: rgba(255, 255, 255, 0.12); + + --color-primary-soft: rgba(240, 68, 148, 0.16); + --color-primary-softer: rgba(240, 68, 148, 0.08); + + --color-primary-light: var(--color-primary-soft); + --color-surface: var(--surface-1); + --color-line-strong: var(--color-line); + --color-bg-hover: var(--surface-2); + --color-border-light: var(--color-line-soft); + --color-fill: var(--surface-2); + + --text-secondary: var(--color-ink-3); + --text-tertiary: var(--color-ink-4); + + --surface-active: var(--surface-2); + --surface-card: var(--surface-1); + --surface-hover: var(--surface-2); + --surface-secondary: var(--surface-2); + + --shadow-xl: 0 0 10px rgba(24, 24, 24, 0.18); + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.18); + --shadow-sm: 0 0 10px rgba(24, 24, 24, 0.1); /* legacy --main-shadow dark */ + --shadow: 0 0 10px rgba(24, 24, 24, 0.14); + --shadow-lg: 0 14px 36px rgba(0, 0, 0, 0.32); + --shadow-drawer: 0 20px 60px rgba(0, 0, 0, 0.60); + + --blur-bg: rgba(50, 51, 53, 0.80); + --float-btn-bg: rgba(70, 70, 70, 0.40); + --header-bg: var(--blur-bg); + --footer-bg: var(--surface-1); + --footer-color: var(--color-ink-4); + --shadow-md: var(--shadow); + + --skeleton-base: #3a3b3e; + --skeleton-shine: #444548; + + /* Multi-color palette — dark mode: same hue, softer tint backgrounds */ + --aksrt-rose-soft: rgba(244, 63, 94, 0.18); + --aksrt-blue-soft: rgba(59, 130, 246, 0.18); + --aksrt-green-soft: rgba(16, 185, 129, 0.18); + --aksrt-orange-soft:rgba(249, 115, 22, 0.18); + --aksrt-purple-soft:rgba(139, 92, 246, 0.18); + --aksrt-cyan-soft: rgba(6, 182, 212, 0.18); + --aksrt-yellow-soft:rgba(234, 179, 8, 0.18); + --aksrt-red-soft: rgba(239, 68, 68, 0.18); + --aksrt-teal-soft: rgba(20, 184, 166, 0.18); + --aksrt-indigo-soft:rgba(99, 102, 241, 0.18); + --aksrt-pink-soft: rgba(236, 72, 153, 0.18); +} + +/* Dark mode component refinements */ +html.dark .site-nav, +body.dark-theme .site-nav { + border-bottom-color: var(--color-line); + box-shadow: none; +} + +html.dark .footer-tabbar, +body.dark-theme .footer-tabbar { + background: rgba(50, 51, 53, 0.80); + box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.20); +} + +html.dark .n-drawer, +html.dark .n-modal, +html.dark .n-dialog, +body.dark-theme .n-drawer, +body.dark-theme .n-modal, +body.dark-theme .n-dialog { + background: var(--surface-1); +} + +html.dark .post-card, +html.dark .widget, +html.dark .aksrt-theme-box, +html.dark .theme-box, +body.dark-theme .post-card, +body.dark-theme .widget, +body.dark-theme .aksrt-theme-box, +body.dark-theme .theme-box { + border-color: transparent; +} + +/* source: assets/css/core/reset.css */ +/** + * AKSRT Reset + * 现代精简 reset,结合 box-sizing 与 a11y 友好的默认值。 + */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-size: 16px; + scroll-behavior: smooth; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + font-family: var(--font-body); + font-size: var(--text-base); + line-height: var(--leading-normal); + background-color: var(--surface-page); + color: var(--color-ink-2); + transition: background-color 0.25s ease, color 0.25s ease; + overflow-x: hidden; +} + +/* 媒体元素 */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; + height: auto; +} + +/* 表单元素继承字体 */ +input, +button, +textarea, +select { + font: inherit; + color: inherit; +} + +button { + background: none; + border: 0; + padding: 0; + cursor: pointer; +} + +/* 链接 */ +a { + color: inherit; + text-decoration: none; + transition: color var(--dur-fast) var(--ease); +} + +a:hover { + color: var(--color-primary); +} + +/* 焦点环:a11y 关键 */ +:focus { + outline: none; +} + +:focus-visible { + outline: none; + box-shadow: var(--focus-ring); + border-radius: 4px; +} + +/* 选区色 */ +::selection { + background: var(--color-primary-soft); + color: var(--color-ink); +} + +/* 屏幕阅读器隐藏 */ +.screen-reader-text, +.sr-only { + position: absolute !important; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.screen-reader-text:focus, +.sr-only:focus { + position: fixed !important; + top: 16px; + left: 16px; + width: auto; + height: auto; + padding: 8px 16px; + background: var(--surface-1); + color: var(--color-ink); + z-index: 100000; + clip: auto; + box-shadow: var(--shadow-lg); + border-radius: var(--radius); +} + +/* 减少动效偏好 */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* source: assets/css/core/typography.css */ +/** + * AKSRT Typography — 1:1 legacy Alignment + * + * 对齐 legacy main.css 的排版系统 + */ + +/* ===== 全局标题 — 对齐 legacy line 8947 ===== */ +h1, h2, h3, h4, h5, h6 { + font-weight: 700; + margin: 20px 0 10px; + color: var(--key-color, #333); +} + +h1 { font-size: 22px; } +h2 { font-size: 20px; } +h3 { font-size: 18px; } +h4 { font-size: 16px; } +h5 { font-size: 15px; } +h6 { font-size: 14px; } + +/* ===== 文章正文 — 对齐 legacy line 8740 ===== */ +.wp-posts-content, +.entry-content, +.prose { + font-size: 16px; + line-height: 2; + color: var(--main-color, #4e5358); + min-height: 90px; +} + +/* ===== 文章内标题 — 对齐 legacy line 8947 ===== */ +.wp-posts-content h1, +.wp-posts-content h2, +.wp-posts-content h3, +.wp-posts-content h4, +.wp-posts-content h5, +.entry-content h1, +.entry-content h2, +.entry-content h3, +.entry-content h4, +.entry-content h5, +.prose h1, +.prose h2, +.prose h3, +.prose h4, +.prose h5 { + font-weight: 700; + margin: 20px 0 10px; + color: var(--key-color, #333); +} + +.wp-posts-content h1, +.entry-content h1, +.prose h1 { font-size: 22px; } + +.wp-posts-content h2, +.entry-content h2, +.prose h2 { font-size: 20px; } + +.wp-posts-content h3, +.entry-content h3, +.prose h3 { font-size: 18px; } + +.wp-posts-content h4, +.entry-content h4, +.prose h4 { font-size: 16px; } + +/* ===== 段落 — 对齐 legacy line 8999 ===== */ +.wp-posts-content p, +.entry-content p, +.prose p { + margin: 0 0 20px; + word-wrap: break-word; +} + +/* ===== 链接 — 对齐 legacy line 8836 ===== */ +.wp-posts-content a, +.entry-content a, +.prose a { + color: var(--focus-color, #f04494); + text-decoration: none; +} + +.wp-posts-content a:hover, +.entry-content a:hover, +.prose a:hover { + color: var(--focus-color, #f04494); +} + +/* ===== 图片 — 对齐 legacy line 8850 ===== */ +.wp-posts-content img, +.entry-content img, +.prose img { + max-width: 100%; + height: auto; + display: block; + margin: auto; +} + +/* ===== 内联代码 — 对齐 legacy line 8844 ===== */ +.wp-posts-content code:not(pre code), +.entry-content code:not(pre code), +.prose code:not(pre code) { + color: #ff3c98; + background: rgba(253, 153, 153, 0.2); + font-family: var(--font-mono, monospace); + font-size: 0.9em; +} + +/* ===== 代码块 — 对齐 legacy ===== */ +.wp-posts-content pre, +.entry-content pre, +.prose pre { + position: relative; + background: #282c34; + color: #abb2bf; + padding: 15px; + border-radius: var(--main-radius, 8px); + overflow-x: auto; + font-size: 14px; + line-height: 1.5; + margin: 0 0 20px; +} + +.wp-posts-content pre code, +.entry-content pre code, +.prose pre code { + color: inherit; + background: none; + padding: 0; + font-size: inherit; +} + +/* ===== 表格 — 对齐 legacy line 8897 ===== */ +.wp-posts-content table, +.entry-content table, +.prose table { + font-size: 14px; + width: 100%; + border-collapse: collapse; + margin: 0 0 20px; +} + +.wp-posts-content th, +.wp-posts-content td, +.entry-content th, +.entry-content td, +.prose th, +.prose td { + border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); + padding: 8px; +} + +.wp-posts-content th, +.entry-content th, +.prose th { + background: var(--muted-border-color, rgba(0, 0, 0, 0.03)); + font-weight: 700; +} + +.wp-posts-content tr:hover, +.entry-content tr:hover, +.prose tr:hover { + background-color: rgba(240, 68, 148, 0.05); +} + +/* ===== 列表 — 对齐 legacy line 8977 ===== */ +.wp-posts-content ul, +.wp-posts-content ol, +.entry-content ul, +.entry-content ol, +.prose ul, +.prose ol { + margin: 10px 0 20px 15px; + padding-left: 15px; +} + +.wp-posts-content li, +.entry-content li, +.prose li { + margin-bottom: 10px; +} + +/* ===== 引用 — 对齐 legacy line 680 ===== */ +.wp-posts-content blockquote, +.entry-content blockquote, +.prose blockquote { + padding: 15px 20px; + margin: 0 0 20px; + border-left: 4px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); + background: var(--muted-border-color, rgba(0, 0, 0, 0.03)); + font-size: 16px; + color: var(--muted-color, #777); + border-radius: 0; +} + +.wp-posts-content blockquote p:last-child, +.entry-content blockquote p:last-child, +.prose blockquote p:last-child { + margin-bottom: 0; +} + +/* ===== 水平分割线 — 对齐 legacy line 676 ===== */ +hr { + border-color: var(--main-border-color, rgba(50, 50, 50, 0.06)); +} + +/* ===== Strong / Bold ===== */ +strong, b { + font-weight: 700; +} + +/* source: assets/css/core/buttons.css */ +/** + * AKSRT Buttons — 1:1 legacy Alignment + * + * 对齐 legacy .but 系统: + * - 正常字重 (normal) + * - 4px 圆角 + * - 灰色默认背景 + * - opacity hover + */ + +.btn, +.btn-primary, +.btn-outline, +.btn-ghost, +.btn-danger { + --btn-h: 34px; + --btn-px: 14px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + height: var(--btn-h); + padding: 0 var(--btn-px); + font-size: var(--text-sm); + font-weight: normal; /* legacy .but: normal — was 500 */ + line-height: 1.44; + border: 1px solid transparent; + border-radius: var(--radius-btn); + cursor: pointer; + white-space: nowrap; + user-select: none; + text-decoration: none; + box-shadow: none; + transition: opacity 0.15s ease, + background 0.15s ease, + border-color 0.15s ease, + color 0.15s ease, + box-shadow 0.15s ease; +} + +/* 尺寸变体 */ +.btn-sm { --btn-h: 28px; --btn-px: 8px; font-size: var(--text-xs); } +.btn-lg { --btn-h: 44px; --btn-px: 18px; font-size: var(--text-base); } + +/* === Default (gray background, like legacy .but default) === */ +.btn { + background: rgba(136, 136, 136, 0.1); + color: #888; + border-color: transparent; +} + +.btn:hover { + opacity: 0.8; + color: #888; +} + +/* === Primary — solid theme, opacity hover (no translateY / heavy glow) === */ +.btn-primary { + background: var(--color-primary); + color: var(--color-on-primary); + border-color: var(--color-primary); + box-shadow: none; +} + +.btn-primary:hover { + opacity: 0.8; + color: var(--color-on-primary); + background: var(--color-primary); + border-color: var(--color-primary); + transform: none; + box-shadow: none; +} + +.btn-primary:active { + opacity: 1; + box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.08); +} + +/* === Outline === */ +.btn-outline { + background: transparent; + color: var(--color-ink-2); + border-color: var(--color-ink-2); +} + +.btn-outline:hover { + opacity: 0.8; + background: transparent; + border-color: var(--color-ink-2); + color: var(--color-ink-2); +} + +/* === Ghost === */ +.btn-ghost { + background: transparent; + color: var(--color-ink-2); + border-color: transparent; +} + +.btn-ghost:hover { + background: rgba(136, 136, 136, 0.1); + opacity: 0.8; +} + +/* === Danger === */ +.btn-danger { + background: var(--color-error); + color: #fff; + border-color: var(--color-error); +} + +.btn-danger:hover { + opacity: 0.8; + color: #fff; +} + +/* === Disabled === */ +.btn:disabled, +.btn-primary:disabled, +.btn-outline:disabled, +.btn-ghost:disabled, +.btn-danger:disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* === Icon button === */ +.btn-icon { + width: var(--btn-h); + padding: 0; + justify-content: center; +} + +.btn-icon.btn-sm { width: 28px; } +.btn-icon.btn-lg { width: 44px; } + +/* === Full width === */ +.btn-block { + width: 100%; +} + +/* ============================================================ + * legacy .but / .badg compat layer — 1:1 legacy main.css (.but system) + * ------------------------------------------------------------ + * Additive: does NOT alter the AKSRT .btn system above. Lets reused + * legacy-style markup (.but / .badg / .b-* / .jb-* / .c-* / .but-group / + * .but-average / .toggle-radius) render via the --this-* context-coloring vars. + * The --this-* DEFAULTS are set on the base rule here (BEFORE the .b-* and .jb-* + * modifiers) so the cascade resolves colored variants correctly; scoped to + * .but/.badg (never :root) so .float-btn's own var(--this-*, fallback) stays intact. + * Consumes --focus-color-opacity* from compat-vars.css. + * ============================================================ */ + +/* Base (legacy .but / .badg) */ +.but, +.badg { + display: inline-block; + vertical-align: middle; + padding: 0.3em 0.6em; + text-align: center; + font-weight: normal; + line-height: 1.44; + border-radius: 4px; + border: 1px solid var(--this-border); + box-shadow: var(--this-shadow); + background: var(--this-bg); + color: var(--this-color); + --main-color: var(--this-color); + --this-bg: rgba(136, 136, 136, 0.1); + --this-border: transparent; + --this-shadow: none; + --this-color: #888; + text-shadow: 0 0 0; + transition: 0.15s; + cursor: pointer; +} + +.but a { color: inherit; } + +.but .icon, +.but > .fa:not(.fa-angle-right), +.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; } + +/* hover / active */ +.but:focus, +.but:hover { opacity: 0.8; color: var(--this-color); } +.but:active { --this-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.08); } + +/* radius variants */ +.but.radius, +.badg.radius, +.radius > .but { border-radius: 50px; padding: 0.3em 1em; } +.but.main-radius, +.badg.main-radius { border-radius: var(--main-radius); } +.but.mini-radius, +.badg.mini-radius { border-radius: var(--mini-radius); } + +/* hollow (outline) */ +.but.hollow, +.badg.hollow { background: transparent; --this-border: var(--this-color); } +.but.hollow:hover { background: var(--this-bg); } +.badg.hollow[class*='b-'], +.but.hollow[class*='b-'] { background: 0 0; --this-border: var(--this-bg); --this-color: var(--this-bg); } +.but.hollow[class*='b-']:hover { color: #fff; } +.but.hollow.c-white:hover { background: 0 0; --this-color: var(--focus-color); color: var(--focus-color); } + +/* circular icon button */ +.circular, +.badg.cir, +.but.cir { + padding: 0; + border-radius: 5em; + --this-size: 1.8em; + display: inline-flex; + justify-content: center; + align-items: center; + height: var(--this-size); + min-width: var(--this-size); +} +.circular.radius4, +.badg.cir.radius4, +.but.cir.radius4 { border-radius: 4px; } +.but.cir svg.icon, +.but.cir > .fa { margin: 0; } + +/* button group (joined) */ +.but-group { display: inline-flex; } +.but-group > .but { border-radius: 0; margin: 0; } +.but-group > .but:first-of-type { border-radius: 4px 0 0 4px; } +.but-group > .but:last-of-type { border-radius: 0 4px 4px 0; } + +/* segmented control (equal-width) */ +.but-average { display: flex; border-radius: 4px; overflow: hidden; } +.but-average.inline { display: inline-flex; } +.but-average.radius { border-radius: 100px; } +.but-average.main-radius { border-radius: var(--main-radius); } +.but-average.mini-radius { border-radius: var(--mini-radius); } +.but-average > .but { margin: 0 !important; border-radius: 0; flex: auto; } +.but-average > .but.active { --this-bg: var(--focus-color-opacity1); --this-color: var(--focus-color); } + +/* size variants */ +.but.but-sm { font-size: 0.9em; padding: 0.1em 0.4em; vertical-align: 0.05em; } +.but.badg-sm, +.badg.badg-sm { font-size: 0.8em; padding: 0 0.2em; vertical-align: 0.1em; min-width: 1.6em; } +.badg.badg-sm.cir { --this-size: 1.6em; } +.padding-lg, +.badg.badg-lg, +.but.padding-lg { padding: 0.5em 2em; } + +/* active badge state */ +.active.badg, +.active > .badg:not(.vip-tag) { --this-bg: var(--focus-color); --this-color: #fff; } +.active.badg.hollow, +.active > .hollow:not(.vip-tag) { --this-color: var(--focus-color); } + +/* toggle-radius (round 29px toggle) — legacy 1:1 */ +.toggle-radius { + background: var(--this-bg); + width: 29px; + height: 29px; + display: inline-block; + text-align: center; + border-radius: 100%; + position: relative; + vertical-align: text-top; + --this-bg: var(--main-border-color); +} +/* legacy groups these with abs-* : absolute + vertical center; then pin left/right 0 */ +.toggle-radius .fa, +.toggle-radius .icon, +.toggle-radius > i { + position: absolute; + top: 50%; + right: 0 !important; + left: 0 !important; + margin: auto; + transform: translateY(-50%); + line-height: 1; + width: 1em; + height: 1em; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; +} + +/* solid color modifiers — set --this-bg; default --this-color white */ +.b-gray, .b-theme, .b-red, .b-yellow, .b-blue, .b-blue-2, .b-black, +.b-cyan, .b-green, .b-purple, +.jb-red, .jb-pink, .jb-yellow, .jb-blue, .jb-green, .jb-purple, +.jb-vip1, .jb-vip2 { + color: var(--this-color); + background: var(--this-bg); + --this-color: #fff; +} +.jb-red, .jb-pink, .jb-yellow, .jb-blue, .jb-green, .jb-purple, +.jb-vip1, .jb-vip2 { border: none; } + +.b-theme { --this-bg: var(--focus-color); } +.b-gray { --this-bg: #818b95; } +.b-red { --this-bg: #ff5473; } +.b-yellow { --this-bg: #ff6f06; } +.b-cyan { --this-bg: #08c4c1; } +.b-blue { --this-bg: #2997f7; } +.b-blue-2 { --this-bg: #5c7cff; } +.b-green { --this-bg: #12b928; } +.b-purple { --this-bg: #d448f5; } +.b-black { --this-bg: #121517; } + +/* gradient modifiers (legacy .jb-*) */ +.jb-red { --this-bg: linear-gradient(135deg, #fd7a64 10%, #fb2d2d 100%); } +.jb-yellow { --this-bg: linear-gradient(135deg, #f99d4d 10%, #f7631d 100%); } +.jb-blue { --this-bg: linear-gradient(135deg, #59c3fb 10%, #268df7 100%); } +.jb-green { --this-bg: linear-gradient(135deg, #60e464 10%, #5cb85b 100%); } +.jb-purple { --this-bg: linear-gradient(135deg, #f98dfb 10%, #ea00f9 100%); } +.jb-pink { --this-bg: linear-gradient(135deg, #ff5e7f 30%, #ff967e 100%); } +.jb-vip1 { --this-bg: linear-gradient(25deg, #eabe7b 10%, #f5e3c7 70%, #edc788 100%); --this-color: #866127; } +.jb-vip2 { --this-bg: linear-gradient(317deg, #4d4c4c 30%, #7b7b7b 70%, #5f5c5c 100%); --this-color: #ddd; } + +/* text-color modifiers (legacy .c-*) — set --this-color + light --this-bg */ +.c-theme, .c-gray, .c-white, .c-red, .c-red-2, .c-yellow, .c-blue, .c-blue-2, +.c-green, .c-cyan, .c-purple { color: var(--this-color); } +.c-theme { --this-color: var(--focus-color); --this-bg: var(--focus-color-opacity1); } +.c-gray { --this-color: #888; --this-bg: rgba(136, 136, 136, 0.1); } +.c-white { --this-color: #fff; --this-bg: rgba(255, 255, 255, 0.1); } +.c-red { --this-color: #ff5473; --this-bg: rgba(255, 84, 115, 0.1); } +.c-red-2 { --this-color: #d6064c; --this-bg: rgba(194, 41, 46, 0.1); } +.c-yellow { --this-color: #ff6f06; --this-bg: rgba(255, 111, 6, 0.1); } +.c-blue { --this-color: #2997f7; --this-bg: rgba(41, 151, 247, 0.1); } + +/* source: assets/css/core/forms.css */ +/** + * AKSRT Forms — 1:1 legacy Alignment + * + * 对齐 legacy .form-control: + * - 灰色背景 (#f5f6f7) + * - 透明边框 + * - 34px 高度 + * - 8px 圆角 + * - focus 时粉色边框 + 光晕 + */ + +/* ===== Field Wrapper ===== */ +.field { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: var(--space-4); +} + +.field-label { + font-size: var(--text-sm); + font-weight: 600; + color: var(--color-ink); +} + +.field-help { + font-size: var(--text-xs); + color: var(--color-ink-3); +} + +.field-error { + font-size: var(--text-xs); + color: var(--color-error); +} + +/* ===== Input / Textarea / Select — 对齐 legacy .form-control ===== */ +.input, +.textarea, +.select, +input[type="text"].form-input, +input[type="email"].form-input, +input[type="password"].form-input, +input[type="search"].form-input, +input[type="url"].form-input, +input[type="tel"].form-input, +textarea.form-textarea, +select.form-select { + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + color: var(--color-ink); + background: var(--surface-page); + border: 1px solid transparent; + border-radius: var(--radius); + transition: border-color var(--dur-fast) var(--ease), + box-shadow var(--dur-fast) var(--ease); +} + +.textarea, +textarea.form-textarea { + height: auto; + min-height: 120px; + padding: 12px; + line-height: var(--leading-snug); + resize: vertical; +} + +.input:hover, +.textarea:hover, +.select:hover { + border-color: var(--color-primary); +} + +.input:focus, +.textarea:focus, +.select:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 2px 3px rgba(253, 83, 161, 0.4); +} + +.input::placeholder, +.textarea::placeholder { + color: var(--color-ink-4); +} + +/* ===== Select ===== */ +.select, +select.form-select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23777' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 32px; +} + +/* ===== Checkbox / Radio ===== */ +.checkbox, +.radio { + display: inline-flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: var(--text-sm); + color: var(--color-ink-2); +} + +.checkbox input, +.radio input { + width: 16px; + height: 16px; + accent-color: var(--color-primary); + cursor: pointer; +} + +/* ===== Input Group ===== */ +.input-group { + display: flex; + align-items: stretch; +} + +.input-group .input { + border-radius: var(--radius) 0 0 var(--radius); +} + +.input-group .btn { + border-radius: 0 var(--radius) var(--radius) 0; +} + +/* ===== Form Layout ===== */ +.form-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-4); +} + +.form-actions { + display: flex; + gap: var(--space-3); + margin-top: var(--space-5); +} + +/* source: assets/css/core/alerts.css */ +/** + * AKSRT Alerts + * 统一提示组件:error / warning / info / success + * 用于替代各页面 inline 的 .login-error / .bind-error 等 + */ + +.alert { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + margin-bottom: var(--space-4); + font-size: var(--text-sm); + line-height: var(--leading-snug); + border-radius: var(--radius); + border: 1px solid transparent; +} + +.alert > svg { + flex-shrink: 0; + width: 18px; + height: 18px; + margin-top: 1px; +} + +.alert--error { + color: #B91C1C; + background: rgba(239, 68, 68, 0.08); + border-color: rgba(239, 68, 68, 0.3); +} + +.alert--warning { + color: #B45309; + background: rgba(245, 158, 11, 0.08); + border-color: rgba(245, 158, 11, 0.3); +} + +.alert--success { + color: #047857; + background: rgba(16, 185, 129, 0.08); + border-color: rgba(16, 185, 129, 0.3); +} + +.alert--info { + color: #1D4ED8; + background: rgba(59, 130, 246, 0.08); + border-color: rgba(59, 130, 246, 0.3); +} + +html.dark .alert--error { color: #FCA5A5; } +html.dark .alert--warning { color: #FCD34D; } +html.dark .alert--success { color: #6EE7B7; } +html.dark .alert--info { color: #93C5FD; } + +/* source: assets/css/core/animations.css */ +/** + * AKSRT Animations — animate.css 等价(独立重写) + * ------------------------------------------------------------ + * 关键帧 + 工具类 + IntersectionObserver 触发配合 + * 触发 JS 已在 enhancements2.js([data-aksrt-animate] 属性) + * 本文件仅定义 @keyframes 和 .aksrt-anim-* 工具类 + */ + +/* ============================================================ + * Keyframes + * ============================================================ */ + +/* Fade 系列 */ +@keyframes aksrtFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes aksrtFadeInUp { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtFadeInDown { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtFadeInLeft { + from { opacity: 0; transform: translateX(-20px); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtFadeInRight { + from { opacity: 0; transform: translateX(20px); } + to { opacity: 1; transform: none; } +} + +/* Zoom 系列 */ +@keyframes aksrtZoomIn { + from { opacity: 0; transform: scale(.85); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtZoomInUp { + from { opacity: 0; transform: scale(.85) translateY(20px); } + to { opacity: 1; transform: none; } +} + +/* Bounce 系列 */ +@keyframes aksrtBounceIn { + 0% { opacity: 0; transform: scale(.3); } + 50% { opacity: 1; transform: scale(1.08); } + 70% { transform: scale(.95); } + 100% { opacity: 1; transform: none; } +} + +@keyframes aksrtBounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-12px); } + 60% { transform: translateY(-6px); } +} + +/* Pulse */ +@keyframes aksrtPulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.06); } + 100% { transform: scale(1); } +} + +/* Flash */ +@keyframes aksrtFlash { + 0%, 50%, 100% { opacity: 1; } + 25%, 75% { opacity: 0; } +} + +/* RubberBand */ +@keyframes aksrtRubberBand { + 0% { transform: scale(1); } + 30% { transform: scaleX(1.25) scaleY(0.75); } + 40% { transform: scaleX(0.75) scaleY(1.25); } + 50% { transform: scaleX(1.15) scaleY(0.85); } + 65% { transform: scaleX(0.95) scaleY(1.05); } + 75% { transform: scaleX(1.05) scaleY(0.95); } + 100% { transform: scale(1); } +} + +/* Shake */ +@keyframes aksrtShake { + 0%, 100% { transform: translateX(0); } + 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } + 20%, 40%, 60%, 80% { transform: translateX(4px); } +} + +/* Swing */ +@keyframes aksrtSwing { + 20% { transform: rotate(15deg); } + 40% { transform: rotate(-10deg); } + 60% { transform: rotate(5deg); } + 80% { transform: rotate(-5deg); } + 100% { transform: none; } +} + +/* Tada */ +@keyframes aksrtTada { + 0% { transform: scale(1); } + 10%, 20% { transform: scale(0.9) rotate(-3deg); } + 30%, 50%, 70%, 90% { transform: scale(1.1) rotate(3deg); } + 40%, 60%, 80% { transform: scale(1.1) rotate(-3deg); } + 100% { transform: scale(1) rotate(0); } +} + +/* Wobble */ +@keyframes aksrtWobble { + 0% { transform: none; } + 15% { transform: translateX(-25%) rotate(-5deg); } + 30% { transform: translateX(20%) rotate(3deg); } + 45% { transform: translateX(-15%) rotate(-3deg); } + 60% { transform: translateX(10%) rotate(2deg); } + 75% { transform: translateX(-5%) rotate(-1deg); } + 100% { transform: none; } +} + +/* Slide 系列 */ +@keyframes aksrtSlideInUp { + from { opacity: 0; transform: translateY(100%); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtSlideInDown { + from { opacity: 0; transform: translateY(-100%); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtSlideInLeft { + from { opacity: 0; transform: translateX(-100%); } + to { opacity: 1; transform: none; } +} + +@keyframes aksrtSlideInRight { + from { opacity: 0; transform: translateX(100%); } + to { opacity: 1; transform: none; } +} + +/* HeartBeat */ +@keyframes aksrtHeartBeat { + 0% { transform: scale(1); } + 14% { transform: scale(1.3); } + 28% { transform: scale(1); } + 42% { transform: scale(1.3); } + 70% { transform: scale(1); } + 100% { transform: scale(1); } +} + +/* Jello */ +@keyframes aksrtJello { + 0%, 100% { transform: none; } + 11% { transform: skewX(-12.5deg) skewY(-12.5deg); } + 22% { transform: skewX(6.25deg) skewY(6.25deg); } + 33% { transform: skewX(-6.25deg) skewY(-6.25deg); } + 44% { transform: skewX(3.125deg) skewY(3.125deg); } + 55% { transform: skewX(-3.125deg) skewY(-3.125deg); } + 66% { transform: skewX(1.5625deg) skewY(1.5625deg); } + 77% { transform: skewX(-1.5625deg) skewY(-1.5625deg); } + 88% { transform: skewX(0.78125deg) skewY(0.78125deg); } +} + +/* HeadShake */ +@keyframes aksrtHeadShake { + 0% { transform: translateX(0); } + 6.5% { transform: translateX(-6px) rotateY(-9deg); } + 18.5% { transform: translateX(5px) rotateY(7deg); } + 31.5% { transform: translateX(-3px) rotateY(-5deg); } + 43.5% { transform: translateX(2px) rotateY(3deg); } + 50% { transform: translateX(0); } + 100% { transform: translateX(0); } +} + +/* BounceOut — 退出动画 */ +@keyframes aksrtBounceOut { + 0% { transform: scale(1); } + 25% { transform: scale(0.95); } + 50% { transform: scale(1.1); } + 75% { transform: scale(0.97); } + 100% { transform: scale(1); opacity: 0; } +} + +/* ZoomOut — 退出动画 */ +@keyframes aksrtZoomOut { + 0% { opacity: 1; transform: none; } + 100% { opacity: 0; transform: scale(0.85); } +} + +/* BackIn/BackOut */ +@keyframes aksrtBackInUp { + 0% { opacity: 0; transform: translateY(30px) scale(0.7); } + 80% { opacity: 0.8; transform: translateY(-5px) scale(1.02); } + 100% { opacity: 1; transform: none; } +} + +@keyframes aksrtBackInDown { + 0% { opacity: 0; transform: translateY(-30px) scale(0.7); } + 80% { opacity: 0.8; transform: translateY(5px) scale(1.02); } + 100% { opacity: 1; transform: none; } +} + +/* Flip */ +@keyframes aksrtFlip { + 0% { transform: perspective(400px) rotateY(90deg); opacity: 0; } + 40% { transform: perspective(400px) rotateY(-10deg); } + 70% { transform: perspective(400px) rotateY(10deg); } + 100% { transform: perspective(400px) rotateY(0deg); opacity: 1; } +} + +@keyframes aksrtFlipInX { + 0% { transform: perspective(400px) rotateX(90deg); opacity: 0; } + 40% { transform: perspective(400px) rotateX(-10deg); } + 70% { transform: perspective(400px) rotateX(10deg); } + 100% { transform: perspective(400px) rotateX(0deg); opacity: 1; } +} + +/* ============================================================ + * Animation utility classes + * ============================================================ */ +.aksrt-anim-fade-in { animation: aksrtFadeIn var(--dur-base, .4s) var(--ease-out, ease) both; } +.aksrt-anim-fade-up { animation: aksrtFadeInUp var(--dur-base, .4s) var(--ease-out, ease) both; } +.aksrt-anim-fade-down { animation: aksrtFadeInDown var(--dur-base, .4s) var(--ease-out, ease) both; } +.aksrt-anim-fade-left { animation: aksrtFadeInLeft var(--dur-base, .4s) var(--ease-out, ease) both; } +.aksrt-anim-fade-right { animation: aksrtFadeInRight var(--dur-base, .4s) var(--ease-out, ease) both; } + +.aksrt-anim-zoom-in { animation: aksrtZoomIn var(--dur-base, .4s) var(--ease-out, ease) both; } +.aksrt-anim-zoom-in-up { animation: aksrtZoomInUp var(--dur-base, .4s) var(--ease-out, ease) both; } + +.aksrt-anim-bounce-in { animation: aksrtBounceIn .6s var(--ease-out, ease) both; } +.aksrt-anim-bounce { animation: aksrtBounce 1s ease both; } + +.aksrt-anim-pulse { animation: aksrtPulse 1s ease infinite; } +.aksrt-anim-flash { animation: aksrtFlash 1s ease both; } +.aksrt-anim-rubber-band { animation: aksrtRubberBand .8s ease both; } +.aksrt-anim-shake { animation: aksrtShake .5s ease both; } +.aksrt-anim-swing { animation: aksrtSwing .6s var(--ease-out, ease) both; } +.aksrt-anim-tada { animation: aksrtTada 1s ease both; } +.aksrt-anim-wobble { animation: aksrtWobble .8s ease both; } +.aksrt-anim-heart-beat { animation: aksrtHeartBeat 1.3s ease both; } +.aksrt-anim-jello { animation: aksrtJello .8s ease both; } +.aksrt-anim-head-shake { animation: aksrtHeadShake .8s ease both; } + +/* 退出动画 */ +.aksrt-anim-bounce-out { animation: aksrtBounceOut .5s ease both; } +.aksrt-anim-zoom-out { animation: aksrtZoomOut .3s ease both; } + +/* BackIn 动画 */ +.aksrt-anim-back-in-up { animation: aksrtBackInUp .5s var(--ease-out, ease) both; } +.aksrt-anim-back-in-down { animation: aksrtBackInDown .5s var(--ease-out, ease) both; } + +.aksrt-anim-slide-up { animation: aksrtSlideInUp .5s var(--ease-out, ease) both; } +.aksrt-anim-slide-down { animation: aksrtSlideInDown .5s var(--ease-out, ease) both; } +.aksrt-anim-slide-left { animation: aksrtSlideInLeft .5s var(--ease-out, ease) both; } +.aksrt-anim-slide-right { animation: aksrtSlideInRight .5s var(--ease-out, ease) both; } + +.aksrt-anim-flip { animation: aksrtFlip .6s var(--ease-out, ease) both; } +.aksrt-anim-flip-x { animation: aksrtFlipInX .6s var(--ease-out, ease) both; } + +/* ============================================================ + * Duration modifiers + * ============================================================ */ +.aksrt-anim-fast { --dur-base: .2s; } +.aksrt-anim-slow { --dur-base: .6s; } + +/* ============================================================ + * Delay modifiers (配合 data-aksrt-delay 使用时为 inline, + * 也可用 class 方式独立设置) + * ============================================================ */ +.aksrt-anim-delay-1 { animation-delay: .1s; } +.aksrt-anim-delay-2 { animation-delay: .2s; } +.aksrt-anim-delay-3 { animation-delay: .3s; } +.aksrt-anim-delay-4 { animation-delay: .4s; } +.aksrt-anim-delay-5 { animation-delay: .5s; } +.aksrt-anim-delay-6 { animation-delay: .6s; } +.aksrt-anim-delay-7 { animation-delay: .7s; } +.aksrt-anim-delay-8 { animation-delay: .8s; } + +/* ============================================================ + * Infinite modifier + * ============================================================ */ +.aksrt-anim-infinite { animation-iteration-count: infinite; } + +/* ============================================================ + * Reduced-motion — 尊重用户偏好 + * ============================================================ */ +@media (prefers-reduced-motion: reduce) { + .aksrt-anim-fade-in, + .aksrt-anim-fade-up, + .aksrt-anim-fade-down, + .aksrt-anim-fade-left, + .aksrt-anim-fade-right, + .aksrt-anim-zoom-in, + .aksrt-anim-zoom-in-up, + .aksrt-anim-bounce-in, + .aksrt-anim-bounce, + .aksrt-anim-pulse, + .aksrt-anim-flash, + .aksrt-anim-rubber-band, + .aksrt-anim-shake, + .aksrt-anim-swing, + .aksrt-anim-tada, + .aksrt-anim-wobble, + .aksrt-anim-heart-beat, + .aksrt-anim-jello, + .aksrt-anim-head-shake, + .aksrt-anim-bounce-out, + .aksrt-anim-zoom-out, + .aksrt-anim-back-in-up, + .aksrt-anim-back-in-down, + .aksrt-anim-slide-up, + .aksrt-anim-slide-down, + .aksrt-anim-slide-left, + .aksrt-anim-slide-right, + .aksrt-anim-flip, + .aksrt-anim-flip-x { + animation: none !important; + } +} + +/* source: assets/css/core/components-ext.css */ +/** + * AKSRT Extended Components — 时间轴 / 步骤向导 / 九宫格 / 定价卡片 / + * Toggle 开关 / 自定义复选框 / 数字步进器 + * ------------------------------------------------------------ + * 全部使用 aksrt- 前缀,保持原生语义可访问性。 + */ + +/* ============================================================ + * 1. 时间轴 — .aksrt-timeline + * ============================================================ */ +.aksrt-timeline { + position: relative; + padding-left: 28px; + margin: 0; + list-style: none; +} + +/* 竖线 */ +.aksrt-timeline::before { + content: ''; + position: absolute; + left: 8px; + top: 4px; + bottom: 4px; + width: 2px; + background: var(--color-line, rgba(50, 50, 50, 0.1)); + border-radius: 1px; +} + +.aksrt-timeline__item { + position: relative; + padding-bottom: 24px; +} + +.aksrt-timeline__item:last-child { + padding-bottom: 0; +} + +/* 圆点 */ +.aksrt-timeline__dot { + position: absolute; + left: -24px; + top: 4px; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--color-primary); + border: 2px solid var(--surface-1, #fff); + box-shadow: 0 0 0 2px var(--color-primary-soft, rgba(240, 68, 148, 0.12)); + z-index: 1; +} + +/* 空心圆点变体 */ +.aksrt-timeline__dot--outline { + background: var(--surface-1, #fff); + border: 2px solid var(--color-primary); + box-shadow: none; +} + +/* 灰色圆点 — 已完成/已过去 */ +.aksrt-timeline__dot--muted { + background: var(--color-ink-4, #999); + box-shadow: 0 0 0 2px var(--surface-2, #eee); +} + +.aksrt-timeline__time { + display: block; + font-size: 12px; + color: var(--color-ink-4, #999); + margin-bottom: 4px; +} + +.aksrt-timeline__title { + font-weight: 600; + color: var(--color-ink, #333); + margin-bottom: 4px; +} + +.aksrt-timeline__content { + font-size: 14px; + color: var(--color-ink-3, #777); + line-height: 1.6; +} + +/* ============================================================ + * 2. 步骤向导 — .aksrt-steps + * ============================================================ */ +.aksrt-steps { + display: flex; + align-items: flex-start; + gap: 0; + margin: 0; + padding: 0; + list-style: none; + counter-reset: aksrt-step; +} + +.aksrt-steps__item { + flex: 1; + position: relative; + text-align: center; + counter-increment: aksrt-step; +} + +/* 连接线 */ +.aksrt-steps__item:not(:last-child)::after { + content: ''; + position: absolute; + top: 16px; + left: calc(50% + 20px); + right: calc(-50% + 20px); + height: 2px; + background: var(--color-line, rgba(50, 50, 50, 0.1)); +} + +.aksrt-steps__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background: var(--surface-2, #eee); + color: var(--color-ink-3, #777); + font-size: 14px; + font-weight: 700; + margin: 0 auto 8px; + position: relative; + z-index: 1; +} + +.aksrt-steps__item--active .aksrt-steps__icon { + background: var(--color-primary); + color: var(--color-on-primary, #fff); +} + +.aksrt-steps__item--done .aksrt-steps__icon { + background: var(--color-success); + color: #fff; +} + +.aksrt-steps__item--done:not(:last-child)::after { + background: var(--color-success); +} + +.aksrt-steps__label { + font-size: 13px; + font-weight: 600; + color: var(--color-ink-3, #777); +} + +.aksrt-steps__item--active .aksrt-steps__label { + color: var(--color-primary); +} + +.aksrt-steps__item--done .aksrt-steps__label { + color: var(--color-success); +} + +/* 垂直模式 */ +.aksrt-steps--vertical { + flex-direction: column; + gap: 0; + padding-left: 20px; +} + +.aksrt-steps--vertical .aksrt-steps__item { + text-align: left; + padding-left: 28px; + padding-bottom: 20px; +} + +.aksrt-steps--vertical .aksrt-steps__item:not(:last-child)::after { + top: 34px; + left: 36px; + right: auto; + width: 2px; + height: calc(100% - 34px); +} + +.aksrt-steps--vertical .aksrt-steps__icon { + position: absolute; + left: 0; + margin: 0; +} + +/* ============================================================ + * 3. 九宫格 — .aksrt-img-grid + * ============================================================ */ +.aksrt-img-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px; + border-radius: var(--radius, 8px); + overflow: hidden; +} + +.aksrt-img-grid img { + width: 100%; + aspect-ratio: 1; + object-fit: cover; + display: block; + cursor: pointer; + transition: transform .2s ease; +} + +.aksrt-img-grid img:hover { + transform: scale(1.03); +} + +/* 自适应列数 */ +.aksrt-img-grid--2 { grid-template-columns: repeat(2, 1fr); } +.aksrt-img-grid--4 { grid-template-columns: repeat(4, 1fr); } + +@media (max-width: 480px) { + .aksrt-img-grid--4 { grid-template-columns: repeat(3, 1fr); } +} + +/* ============================================================ + * 5. Toggle 开关 — .aksrt-toggle (纯 CSS + :checked) + * 保持原生 可访问性 + * ============================================================ */ +.aksrt-toggle { + position: relative; + display: inline-flex; + align-items: center; + cursor: pointer; + user-select: none; + gap: 8px; +} + +.aksrt-toggle__input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + pointer-events: none; +} + +.aksrt-toggle__track { + position: relative; + width: var(--aksrt-toggle-w, 44px); + height: var(--aksrt-toggle-h, 24px); + background: var(--surface-3, #e8e8e8); + border-radius: var(--aksrt-toggle-h, 24px); + transition: background .2s ease; + flex-shrink: 0; +} + +.aksrt-toggle__track::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: calc(var(--aksrt-toggle-h, 24px) - 4px); + height: calc(var(--aksrt-toggle-h, 24px) - 4px); + background: #fff; + border-radius: 50%; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + transition: transform .2s ease; +} + +/* Checked */ +.aksrt-toggle__input:checked + .aksrt-toggle__track { + background: var(--color-primary); +} + +.aksrt-toggle__input:checked + .aksrt-toggle__track::after { + transform: translateX(calc(var(--aksrt-toggle-w, 44px) - var(--aksrt-toggle-h, 24px))); +} + +/* Focus-visible */ +.aksrt-toggle__input:focus-visible + .aksrt-toggle__track { + box-shadow: var(--focus-ring, 0 0 2px 3px rgba(253, 83, 161, 0.4)); +} + +/* Disabled */ +.aksrt-toggle__input:disabled + .aksrt-toggle__track { + opacity: 0.5; + cursor: not-allowed; +} + +.aksrt-toggle__label { + font-size: 14px; + color: var(--color-ink-2, #4e5358); +} + +/* ============================================================ + * 6. 自定义复选框 — .aksrt-checkbox (纯 CSS + :checked) + * 保持原生 可访问性 + * ============================================================ */ +.aksrt-checkbox { + display: inline-flex; + align-items: center; + gap: 8px; + cursor: pointer; + user-select: none; +} + +.aksrt-checkbox__input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + pointer-events: none; +} + +.aksrt-checkbox__box { + position: relative; + width: 18px; + height: 18px; + border: 2px solid var(--color-line, rgba(50, 50, 50, 0.15)); + border-radius: 4px; + background: var(--surface-1, #fff); + transition: background .15s ease, border-color .15s ease; + flex-shrink: 0; +} + +/* 打勾 SVG */ +.aksrt-checkbox__box::after { + content: ''; + position: absolute; + top: 2px; + left: 5px; + width: 5px; + height: 9px; + border: 2px solid #fff; + border-top: none; + border-left: none; + transform: rotate(45deg) scale(0); + transition: transform .15s ease; +} + +.aksrt-checkbox__input:checked + .aksrt-checkbox__box { + background: var(--color-primary); + border-color: var(--color-primary); +} + +.aksrt-checkbox__input:checked + .aksrt-checkbox__box::after { + transform: rotate(45deg) scale(1); +} + +.aksrt-checkbox__input:focus-visible + .aksrt-checkbox__box { + box-shadow: var(--focus-ring, 0 0 2px 3px rgba(253, 83, 161, 0.4)); +} + +.aksrt-checkbox__input:disabled + .aksrt-checkbox__box { + opacity: 0.5; + cursor: not-allowed; +} + +.aksrt-checkbox__label { + font-size: 14px; + color: var(--color-ink-2, #4e5358); +} + +/* ============================================================ + * 7. 数字步进器 — .aksrt-stepper + * ============================================================ */ +.aksrt-stepper { + display: inline-flex; + align-items: center; + border: 1px solid var(--color-line, rgba(50, 50, 50, 0.1)); + border-radius: var(--radius, 8px); + overflow: hidden; + height: var(--aksrt-stepper-h, 36px); +} + +.aksrt-stepper__btn { + display: flex; + align-items: center; + justify-content: center; + width: var(--aksrt-stepper-h, 36px); + height: 100%; + background: var(--surface-2, #eee); + color: var(--color-ink-2, #4e5358); + border: none; + cursor: pointer; + font-size: 16px; + font-weight: 600; + transition: background .15s ease, color .15s ease; + user-select: none; +} + +.aksrt-stepper__btn:hover { + background: var(--surface-3, #e8e8e8); + color: var(--color-ink, #333); +} + +.aksrt-stepper__btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.aksrt-stepper__input { + width: 48px; + height: 100%; + border: none; + text-align: center; + font-size: 14px; + font-weight: 600; + color: var(--color-ink, #333); + background: var(--surface-1, #fff); + -moz-appearance: textfield; +} + +.aksrt-stepper__input::-webkit-outer-spin-button, +.aksrt-stepper__input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.aksrt-stepper__input:focus { + outline: none; + background: var(--color-primary-softer, rgba(240, 68, 148, 0.05)); +} + +/* ============================================================ + * 8. 头像尺寸变量系统 — .aksrt-avatar--{size} + * ============================================================ */ +.aksrt-avatar--xs { width: var(--aksrt-avatar-size-xs); height: var(--aksrt-avatar-size-xs); } +.aksrt-avatar--sm { width: var(--aksrt-avatar-size-sm); height: var(--aksrt-avatar-size-sm); } +.aksrt-avatar { width: var(--aksrt-avatar-size); height: var(--aksrt-avatar-size); } +.aksrt-avatar--lg { width: var(--aksrt-avatar-size-lg); height: var(--aksrt-avatar-size-lg); } +.aksrt-avatar--xl { width: var(--aksrt-avatar-size-xl); height: var(--aksrt-avatar-size-xl); } + +/* ============================================================ + * 9. 涟漪点击效果 — .aksrt-ripple + * 容器加此类,点击时 JS 注入 span.aksrt-ripple__wave + * ============================================================ */ +.aksrt-ripple { + position: relative; + overflow: hidden; +} + +.aksrt-ripple__wave { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.35); + transform: scale(0); + animation: aksrtRippleWave .6s ease-out forwards; + pointer-events: none; +} + +@keyframes aksrtRippleWave { + to { + transform: scale(4); + opacity: 0; + } +} + +/* Runtime broad ripple (.aksrt-wave injected into .but/.btn). + * MUST stay out of flow — missing absolute made header CTA grow on click. */ +.aksrt-wave { + position: absolute !important; + border-radius: 50%; + background: rgba(255, 255, 255, 0.35); + pointer-events: none !important; + z-index: 1; + transform: scale(0); + animation: aksrtWaveRipple 0.6s ease-out forwards; +} + +@keyframes aksrtWaveRipple { + to { + transform: scale(1); + opacity: 0; + } +} + +.nowave { + overflow: hidden; +} + +/* ============================================================ + * 10. 模态框堆叠动画 — 父模态缩小 + * ============================================================ */ +.modal-stacked .modal-dialog { + transition: transform .25s var(--ease, ease), filter .25s ease; +} + +.modal-stacked .modal-dialog.modal-behind { + transform: scale(0.92); + filter: brightness(0.85); + pointer-events: none; +} + +/* source: assets/css/core/color-utilities.css */ +/** + * AKSRT Color Utilities — 多色按钮 / 渐变按钮 / 文字渐变 / Badge + * ------------------------------------------------------------ + * 所有颜色走 CSS 变量(定义在 tokens.css),不硬编码十六进制。 + */ + +/* ============================================================ + * 1. 多色按钮 — .aksrt-btn--{color} + * ============================================================ */ +.aksrt-btn--rose { background: var(--aksrt-rose); color: #fff; } +.aksrt-btn--rose:hover, +.aksrt-btn--rose:focus-visible { background: #e11d48; } + +.aksrt-btn--blue { background: var(--aksrt-blue); color: #fff; } +.aksrt-btn--blue:hover, +.aksrt-btn--blue:focus-visible { background: #2563eb; } + +.aksrt-btn--green { background: var(--aksrt-green); color: #fff; } +.aksrt-btn--green:hover, +.aksrt-btn--green:focus-visible { background: #059669; } + +.aksrt-btn--orange { background: var(--aksrt-orange); color: #fff; } +.aksrt-btn--orange:hover, +.aksrt-btn--orange:focus-visible { background: #ea580c; } + +.aksrt-btn--purple { background: var(--aksrt-purple); color: #fff; } +.aksrt-btn--purple:hover, +.aksrt-btn--purple:focus-visible { background: #7c3aed; } + +.aksrt-btn--cyan { background: var(--aksrt-cyan); color: #fff; } +.aksrt-btn--cyan:hover, +.aksrt-btn--cyan:focus-visible { background: #0891b2; } + +.aksrt-btn--yellow { background: var(--aksrt-yellow); color: #422006; } +.aksrt-btn--yellow:hover, +.aksrt-btn--yellow:focus-visible { background: #ca8a04; color: #fff; } + +.aksrt-btn--red { background: var(--aksrt-red); color: #fff; } +.aksrt-btn--red:hover, +.aksrt-btn--red:focus-visible { background: #dc2626; } + +.aksrt-btn--teal { background: var(--aksrt-teal); color: #fff; } +.aksrt-btn--teal:hover, +.aksrt-btn--teal:focus-visible { background: #0d9488; } + +.aksrt-btn--indigo { background: var(--aksrt-indigo); color: #fff; } +.aksrt-btn--indigo:hover, +.aksrt-btn--indigo:focus-visible { background: #4f46e5; } + +.aksrt-btn--pink { background: var(--aksrt-pink); color: #fff; } +.aksrt-btn--pink:hover, +.aksrt-btn--pink:focus-visible { background: #db2777; } + +/* Soft variant — 浅色背景 + 深色文字 */ +.aksrt-btn--rose-soft { background: var(--aksrt-rose-soft); color: var(--aksrt-rose); } +.aksrt-btn--blue-soft { background: var(--aksrt-blue-soft); color: var(--aksrt-blue); } +.aksrt-btn--green-soft { background: var(--aksrt-green-soft); color: var(--aksrt-green); } +.aksrt-btn--orange-soft { background: var(--aksrt-orange-soft); color: var(--aksrt-orange); } +.aksrt-btn--purple-soft { background: var(--aksrt-purple-soft); color: var(--aksrt-purple); } +.aksrt-btn--cyan-soft { background: var(--aksrt-cyan-soft); color: var(--aksrt-cyan); } +.aksrt-btn--yellow-soft { background: var(--aksrt-yellow-soft); color: #92400e; } +.aksrt-btn--red-soft { background: var(--aksrt-red-soft); color: var(--aksrt-red); } +.aksrt-btn--teal-soft { background: var(--aksrt-teal-soft); color: var(--aksrt-teal); } +.aksrt-btn--indigo-soft { background: var(--aksrt-indigo-soft); color: var(--aksrt-indigo); } +.aksrt-btn--pink-soft { background: var(--aksrt-pink-soft); color: var(--aksrt-pink); } + +/* ============================================================ + * 2. 渐变按钮 — .aksrt-btn--grad-{name} + * ============================================================ */ +.aksrt-btn--grad-vip { + background: linear-gradient(135deg, var(--aksrt-vip-gold-1), var(--aksrt-vip-gold-2)); + color: #5a3d00; + border: none; +} + +.aksrt-btn--grad-sunset { + background: linear-gradient(135deg, var(--aksrt-grad-sunset-1), var(--aksrt-grad-sunset-2)); + color: #fff; + border: none; +} + +.aksrt-btn--grad-ocean { + background: linear-gradient(135deg, var(--aksrt-grad-ocean-1), var(--aksrt-grad-ocean-2)); + color: #fff; + border: none; +} + +.aksrt-btn--grad-forest { + background: linear-gradient(135deg, var(--aksrt-grad-forest-1), var(--aksrt-grad-forest-2)); + color: #064e3b; + border: none; +} + +.aksrt-btn--grad-night { + background: linear-gradient(135deg, var(--aksrt-grad-night-1), var(--aksrt-grad-night-2)); + color: #4c1d95; + border: none; +} + +.aksrt-btn--grad-fire { + background: linear-gradient(135deg, var(--aksrt-grad-fire-1), var(--aksrt-grad-fire-2)); + color: #fff; + border: none; +} + +.aksrt-btn--grad-primary { + background: linear-gradient(135deg, var(--color-primary), var(--color-primary-active)); + color: var(--color-on-primary); + border: none; +} + +/* 渐变按钮 hover — 亮度微调 */ +.aksrt-btn--grad-vip:hover, +.aksrt-btn--grad-sunset:hover, +.aksrt-btn--grad-ocean:hover, +.aksrt-btn--grad-forest:hover, +.aksrt-btn--grad-night:hover, +.aksrt-btn--grad-fire:hover, +.aksrt-btn--grad-primary:hover { + filter: brightness(1.08); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +/* ============================================================ + * 3. 文字渐变 — .aksrt-text-grad-{name} + * ============================================================ */ +.aksrt-text-grad-primary { + background: linear-gradient(135deg, var(--color-primary), var(--color-primary-active)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.aksrt-text-grad-vip { + background: linear-gradient(135deg, var(--aksrt-vip-gold-1), var(--aksrt-vip-gold-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.aksrt-text-grad-sunset { + background: linear-gradient(135deg, var(--aksrt-grad-sunset-1), var(--aksrt-grad-sunset-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.aksrt-text-grad-ocean { + background: linear-gradient(135deg, var(--aksrt-grad-ocean-1), var(--aksrt-grad-ocean-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.aksrt-text-grad-forest { + background: linear-gradient(135deg, var(--aksrt-grad-forest-1), var(--aksrt-grad-forest-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.aksrt-text-grad-night { + background: linear-gradient(135deg, var(--aksrt-grad-night-1), var(--aksrt-grad-night-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.aksrt-text-grad-fire { + background: linear-gradient(135deg, var(--aksrt-grad-fire-1), var(--aksrt-grad-fire-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* ============================================================ + * 4. Badge 系统 — .aksrt-badge / .aksrt-badge--{color} + * ============================================================ */ +.aksrt-badge { + display: inline-flex; + align-items: center; + justify-content: center; + height: var(--aksrt-badge-h, 22px); + padding: 0 8px; + border-radius: var(--radius-pill, 100px); + font-size: 12px; + font-weight: 600; + line-height: 1; + white-space: nowrap; + vertical-align: middle; +} + +.aksrt-badge--primary { background: var(--color-primary-soft); color: var(--color-primary); } +.aksrt-badge--success { background: rgba(16, 185, 129, 0.12); color: var(--color-success); } +.aksrt-badge--warning { background: rgba(245, 158, 11, 0.12); color: var(--color-warning); } +.aksrt-badge--error { background: rgba(239, 68, 68, 0.12); color: var(--color-error); } +.aksrt-badge--info { background: rgba(59, 130, 246, 0.12); color: var(--color-info); } + +.aksrt-badge--rose { background: var(--aksrt-rose-soft); color: var(--aksrt-rose); } +.aksrt-badge--blue { background: var(--aksrt-blue-soft); color: var(--aksrt-blue); } +.aksrt-badge--green { background: var(--aksrt-green-soft); color: var(--aksrt-green); } +.aksrt-badge--orange { background: var(--aksrt-orange-soft); color: var(--aksrt-orange); } +.aksrt-badge--purple { background: var(--aksrt-purple-soft); color: var(--aksrt-purple); } +.aksrt-badge--cyan { background: var(--aksrt-cyan-soft); color: var(--aksrt-cyan); } +.aksrt-badge--yellow { background: var(--aksrt-yellow-soft); color: #92400e; } +.aksrt-badge--red { background: var(--aksrt-red-soft); color: var(--aksrt-red); } +.aksrt-badge--teal { background: var(--aksrt-teal-soft); color: var(--aksrt-teal); } +.aksrt-badge--indigo { background: var(--aksrt-indigo-soft); color: var(--aksrt-indigo); } +.aksrt-badge--pink { background: var(--aksrt-pink-soft); color: var(--aksrt-pink); } + +/* 渐变 Badge */ +.aksrt-badge--grad-vip { + background: linear-gradient(135deg, var(--aksrt-vip-gold-1), var(--aksrt-vip-gold-2)); + color: #5a3d00; +} + +.aksrt-badge--grad-primary { + background: linear-gradient(135deg, var(--color-primary), var(--color-primary-active)); + color: var(--color-on-primary); +} + +/* Solid badge variant — 纯色背景 + 白色文字 */ +.aksrt-badge--solid-primary { background: var(--color-primary); color: #fff; } +.aksrt-badge--solid-success { background: var(--color-success); color: #fff; } +.aksrt-badge--solid-warning { background: var(--color-warning); color: #fff; } +.aksrt-badge--solid-error { background: var(--color-error); color: #fff; } +.aksrt-badge--solid-info { background: var(--color-info); color: #fff; } +.aksrt-badge--solid-rose { background: var(--aksrt-rose); color: #fff; } +.aksrt-badge--solid-blue { background: var(--aksrt-blue); color: #fff; } +.aksrt-badge--solid-green { background: var(--aksrt-green); color: #fff; } +.aksrt-badge--solid-orange { background: var(--aksrt-orange); color: #fff; } +.aksrt-badge--solid-purple { background: var(--aksrt-purple); color: #fff; } +.aksrt-badge--solid-cyan { background: var(--aksrt-cyan); color: #fff; } +.aksrt-badge--solid-yellow { background: var(--aksrt-yellow); color: #422006; } +.aksrt-badge--solid-red { background: var(--aksrt-red); color: #fff; } +.aksrt-badge--solid-teal { background: var(--aksrt-teal); color: #fff; } +.aksrt-badge--solid-indigo { background: var(--aksrt-indigo); color: #fff; } +.aksrt-badge--solid-pink { background: var(--aksrt-pink); color: #fff; } + +/* Badge sizes */ +.aksrt-badge--sm { height: 18px; padding: 0 6px; font-size: 11px; } +.aksrt-badge--lg { height: 28px; padding: 0 12px; font-size: 13px; } + +/* ============================================================ + * 5. 背景色工具类 — .aksrt-bg-{color} + * ============================================================ */ +.aksrt-bg-rose { background-color: var(--aksrt-rose-soft); } +.aksrt-bg-blue { background-color: var(--aksrt-blue-soft); } +.aksrt-bg-green { background-color: var(--aksrt-green-soft); } +.aksrt-bg-orange { background-color: var(--aksrt-orange-soft); } +.aksrt-bg-purple { background-color: var(--aksrt-purple-soft); } +.aksrt-bg-cyan { background-color: var(--aksrt-cyan-soft); } +.aksrt-bg-yellow { background-color: var(--aksrt-yellow-soft); } +.aksrt-bg-red { background-color: var(--aksrt-red-soft); } +.aksrt-bg-teal { background-color: var(--aksrt-teal-soft); } +.aksrt-bg-indigo { background-color: var(--aksrt-indigo-soft); } +.aksrt-bg-pink { background-color: var(--aksrt-pink-soft); } + +/* ============================================================ + * 6. 文字色工具类 — .aksrt-text-{color} + * ============================================================ */ +.aksrt-text-rose { color: var(--aksrt-rose); } +.aksrt-text-blue { color: var(--aksrt-blue); } +.aksrt-text-green { color: var(--aksrt-green); } +.aksrt-text-orange { color: var(--aksrt-orange); } +.aksrt-text-purple { color: var(--aksrt-purple); } +.aksrt-text-cyan { color: var(--aksrt-cyan); } +.aksrt-text-yellow { color: var(--aksrt-yellow); } +.aksrt-text-red { color: var(--aksrt-red); } +.aksrt-text-teal { color: var(--aksrt-teal); } +.aksrt-text-indigo { color: var(--aksrt-indigo); } +.aksrt-text-pink { color: var(--aksrt-pink); } diff --git a/assets/css/build/core.min.css.disabled b/assets/css/build/core.min.css.disabled deleted file mode 100644 index fef94eab..00000000 --- a/assets/css/build/core.min.css.disabled +++ /dev/null @@ -1,1032 +0,0 @@ -/** - * AKSRT Design Tokens v7 — 1:1 legacy Alignment - * ------------------------------------------------------------ - * 单一真理源(Single Source of Truth) - * 所有颜色、字体、间距、动效都从此处读取,禁止在组件内硬编码。 - * 严格对齐 legacy main.css 的视觉值。 - */ - -:root { - /* ===== Primary — legacy theme-color (#f04494 pink) ===== */ - --color-primary: #f04494; - --color-primary-hover: #e0307e; - --color-primary-active: #c9206d; - --color-primary-soft: rgba(240, 68, 148, 0.10); - --color-primary-softer: rgba(240, 68, 148, 0.05); - --color-on-primary: #FFFFFF; - - /* ===== Ink (text) — 对齐 legacy --key-color / --main-color / --muted-color ===== */ - --color-ink: #333333; /* legacy --key-color */ - --color-ink-2: #4e5358; /* legacy --main-color */ - --color-ink-3: #777777; /* legacy --muted-color */ - --color-ink-4: #999999; /* legacy --muted-2-color */ - - /* ===== Surface — 对齐 legacy --body-bg-color / --main-bg-color / --muted-bg-color ===== */ - --surface-page: #f5f6f7; /* legacy --body-bg-color */ - --surface-1: #FFFFFF; /* legacy --main-bg-color */ - --surface-2: #eeeeee; /* legacy --muted-bg-color */ - --surface-3: #e8e8e8; /* 输入框 / code */ - - /* ===== Borders — 对齐 legacy --main-border-color ===== */ - --color-line: rgba(50, 50, 50, 0.06); - --color-line-soft: rgba(0, 0, 0, 0.03); - - /* ===== State colors ===== */ - --color-success: #10B981; - --color-warning: #F59E0B; - --color-error: #EF4444; - --color-info: #3B82F6; - - /* ===== Accent (action green — legacy tabbar/CTA) ===== */ - --color-accent: #18a058; - --color-accent-soft: rgba(24, 160, 88, 0.12); - --color-accent-shadow: rgba(24, 160, 88, 0.28); - - /* ===== Fonts — 对齐 legacy(系统默认字体栈)===== */ - --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; - --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; - --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; - --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - - /* ===== Type scale ===== */ - --text-xs: 0.75rem; /* 12 */ - --text-sm: 0.875rem; /* 14 */ - --text-base: 1rem; /* 16 */ - --text-md: 1.125rem; /* 18 */ - --text-lg: 1.25rem; /* 20 */ - --text-xl: 1.5rem; /* 24 */ - --text-2xl: 1.875rem; /* 30 */ - --text-3xl: 2.25rem; /* 36 */ - --text-4xl: 3rem; /* 48 */ - --text-5xl: 3.75rem; /* 60 */ - --leading-tight: 1.2; - --leading-snug: 1.4; - --leading-normal: 1.6; /* legacy body line-height */ - --leading-relaxed: 1.6; - - /* ===== Spacing (4px base) ===== */ - --space-1: 4px; - --space-2: 8px; - --space-3: 12px; - --space-4: 16px; - --space-5: 20px; - --space-6: 24px; - --space-7: 32px; - --space-8: 40px; - --space-9: 48px; - --space-10: 64px; - --space-11: 80px; - --space-12: 96px; - - /* ===== Radius — 对齐 legacy --main-radius: 8px ===== */ - --radius-sm: 4px; - --radius: 8px; - --radius-lg: 8px; - --radius-xl: 12px; - --radius-pill: 100px; - --radius-avatar: 50%; - - /* ===== Shadows — 对齐 legacy --main-shadow: rgba(116,116,116,0.08) ===== */ - --shadow-sm: 0 0 10px rgba(116, 116, 116, 0.08); - --shadow: 0 0 10px rgba(116, 116, 116, 0.08); - --shadow-lg: 0 0 10px rgba(116, 116, 116, 0.12); - --shadow-drawer: 0 8px 40px rgba(0, 0, 0, 0.20); - - /* ===== Layout — 对齐 legacy --mian-max-width: 1200px ===== */ - --nav-height: 60px; - --container: 1200px; - --container-wide: 1440px; - --container-read: 720px; - --container-page: 860px; - --sidebar-width: 300px; - --content-gap: 48px; - --sidebar-bp: 1024px; - - /* ===== Motion ===== */ - --ease: cubic-bezier(.4, 0, .2, 1); - --ease-out: cubic-bezier(.16, 1, .3, 1); - --ease-spring: cubic-bezier(.34, 1.56, .64, 1); - --dur-fast: 0.15s; - --dur: 0.25s; - --dur-slow: 0.4s; - - /* ===== Focus Ring — 对齐 legacy focus-shadow-color ===== */ - --focus-ring: 0 0 2px 3px rgba(253, 83, 161, 0.4); - - /* ===== Z-index layers ===== */ - --z-dropdown: 100; - --z-sticky: 200; - --z-fixed: 300; - --z-overlay: 400; - --z-modal: 500; - --z-popover: 600; - --z-toast: 700; - --z-tooltip: 800; - - /* ===== Button sizes — 对齐 legacy ===== */ - --radius-btn: 4px; - --btn-sm-h: 28px; - --btn-md-h: 34px; - --btn-lg-h: 44px; - - /* ===== Card density — 对齐 legacy .box-body { padding: 15px } ===== */ - --card-padding: 15px; - --card-padding-sm: 10px; - --card-gap: 16px; - - /* ===== Skeleton loader ===== */ - --skeleton-base: var(--surface-2); - --skeleton-shine: var(--surface-1); - - /* ===== Extended tokens (used by components) ===== */ - --color-primary-light: var(--color-primary-soft); - --color-surface: var(--surface-1); - --color-line-strong: var(--color-line); - --color-bg-hover: var(--surface-2); - --color-border-light: var(--color-line-soft); - --color-fill: var(--surface-2); - --color-warning-bg: #fffbe6; - --color-warning-text: #ad6800; - - --text-secondary: var(--color-ink-3); - --text-tertiary: var(--color-ink-4); - - --surface-active: var(--surface-2); - --surface-card: var(--surface-1); - --surface-hover: var(--surface-2); - --surface-secondary: var(--surface-2); - - --radius-code: var(--radius); - --radius-full: 100px; - --shadow-xl: 0 0 10px rgba(116, 116, 116, 0.12); - --space-16: 64px; - --z-floating: 900; - - --transition-fast: 0.15s ease; - --transition-base: 0.25s ease; - - /* ===== Composite / Context tokens — 对齐 legacy ===== */ - --header-bg: var(--blur-bg); - --header-color: var(--color-ink-2); - --footer-bg: var(--surface-1); - --footer-color: var(--color-ink-4); - --blur-bg: rgba(255, 255, 255, 0.80); - --float-btn-bg: rgba(200, 200, 200, 0.40); - - /* ===== AKSRT-to-AKSRT canonical aliases ===== */ - --content-max: var(--container); - --font-heading: var(--font-ui); - --radius-card: var(--radius); - --radius-md: var(--radius); - --color-primary-50: var(--color-primary-soft); - --color-primary-dark: var(--color-primary-active); - --shadow-md: var(--shadow); - --mini-radius: calc(var(--radius) / 1.4); - --single-cover-scale: 35%; - - /* ===== Avatar size system — legacy --this-size 等价 ===== */ - --aksrt-avatar-size-xs: 24px; - --aksrt-avatar-size-sm: 32px; - --aksrt-avatar-size: 40px; - --aksrt-avatar-size-lg: 56px; - --aksrt-avatar-size-xl: 80px; - - /* ===== Post card thumbnail scale ===== */ - --aksrt-card-thumb-scale: 56.25%; /* 16:9 */ - - /* ===== Multi-color palette (for buttons / badges / gradients) ===== */ - --aksrt-rose: #f43f5e; - --aksrt-rose-soft: rgba(244, 63, 94, 0.12); - --aksrt-blue: #3b82f6; - --aksrt-blue-soft: rgba(59, 130, 246, 0.12); - --aksrt-green: #10b981; - --aksrt-green-soft: rgba(16, 185, 129, 0.12); - --aksrt-orange: #f97316; - --aksrt-orange-soft:rgba(249, 115, 22, 0.12); - --aksrt-purple: #8b5cf6; - --aksrt-purple-soft:rgba(139, 92, 246, 0.12); - --aksrt-cyan: #06b6d4; - --aksrt-cyan-soft: rgba(6, 182, 212, 0.12); - --aksrt-yellow: #eab308; - --aksrt-yellow-soft:rgba(234, 179, 8, 0.12); - --aksrt-red: #ef4444; - --aksrt-red-soft: rgba(239, 68, 68, 0.12); - --aksrt-teal: #14b8a6; - --aksrt-teal-soft: rgba(20, 184, 166, 0.12); - --aksrt-indigo: #6366f1; - --aksrt-indigo-soft:rgba(99, 102, 241, 0.12); - --aksrt-pink: #ec4899; - --aksrt-pink-soft: rgba(236, 72, 153, 0.12); - - /* ===== VIP / special gradients ===== */ - --aksrt-vip-gold-1: #f6d365; - --aksrt-vip-gold-2: #fda085; - --aksrt-grad-sunset-1: #f093fb; - --aksrt-grad-sunset-2: #f5576c; - --aksrt-grad-ocean-1: #4facfe; - --aksrt-grad-ocean-2: #00f2fe; - --aksrt-grad-forest-1: #43e97b; - --aksrt-grad-forest-2: #38f9d7; - --aksrt-grad-night-1: #a18cd1; - --aksrt-grad-night-2: #fbc2eb; - --aksrt-grad-fire-1: #f83600; - --aksrt-grad-fire-2: #f9d423; - - /* ===== Component sizing ===== */ - --aksrt-toggle-w: 44px; - --aksrt-toggle-h: 24px; - --aksrt-stepper-h: 36px; - --aksrt-badge-h: 22px; - - /* ===== Legacy name bridge ===== */ - --text-1: var(--color-ink); - --text-2: var(--color-ink-2); - --text-3: var(--color-ink-3); - --border: var(--color-line); - --color-text-1: var(--color-ink); - --color-text-2: var(--color-ink-2); - --color-text-3: var(--color-ink-3); - --color-primary-bg: var(--color-primary-softer); - --color-danger: var(--color-error); -} - -/* ============================================================ - * Blur-bg fallback cascade (mirrors legacy behaviour) - * ============================================================ */ -@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { - :root { - --blur-bg: var(--surface-1); - --header-bg: var(--surface-1); - } -} - -body.modal-open, -body.dialog-open { - --blur-bg: var(--surface-1); - --header-bg: var(--surface-1); -} - -body.modal-open .site-nav, -body.modal-open .footer-tabbar, -body.modal-open .float-right .float-btn, -body.dialog-open .site-nav, -body.dialog-open .footer-tabbar, -body.dialog-open .float-right .float-btn { - backdrop-filter: none !important; - -webkit-backdrop-filter: none !important; -} - -/* ============================================================ - * Dark Theme — 对齐 legacy .dark-theme (#292a2d 范围) - * ============================================================ */ -html.dark { - --color-ink: #f8fafc; - --color-ink-2: #e5eef7; - --color-ink-3: #b4b6bb; - --color-ink-4: #888a8f; - - --surface-page: #292a2d; - --surface-1: #323335; - --surface-2: #2d2e31; - --surface-3: #3a3b3e; - - --color-line: rgba(114, 114, 114, 0.1); - --color-line-soft: rgba(184, 184, 184, 0.02); - - --color-primary-soft: rgba(240, 68, 148, 0.16); - --color-primary-softer: rgba(240, 68, 148, 0.08); - - --color-primary-light: var(--color-primary-soft); - --color-surface: var(--surface-1); - --color-line-strong: var(--color-line); - --color-bg-hover: var(--surface-2); - --color-border-light: var(--color-line-soft); - --color-fill: var(--surface-2); - - --text-secondary: var(--color-ink-3); - --text-tertiary: var(--color-ink-4); - - --surface-active: var(--surface-2); - --surface-card: var(--surface-1); - --surface-hover: var(--surface-2); - --surface-secondary: var(--surface-2); - - --shadow-xl: 0 0 10px rgba(24, 24, 24, 0.15); - - --shadow-sm: 0 0 10px rgba(24, 24, 24, 0.1); - --shadow: 0 0 10px rgba(24, 24, 24, 0.1); - --shadow-lg: 0 0 10px rgba(24, 24, 24, 0.15); - --shadow-drawer: 0 8px 40px rgba(0, 0, 0, 0.55); - - --blur-bg: rgba(50, 51, 53, 0.80); - --float-btn-bg: rgba(70, 70, 70, 0.40); - --header-bg: var(--blur-bg); - --footer-bg: var(--surface-1); - --footer-color: var(--color-ink-4); - --shadow-md: var(--shadow); - - --skeleton-base: #3a3b3e; - --skeleton-shine: #444548; - - /* Multi-color palette — dark mode: same hue, softer tint backgrounds */ - --aksrt-rose-soft: rgba(244, 63, 94, 0.18); - --aksrt-blue-soft: rgba(59, 130, 246, 0.18); - --aksrt-green-soft: rgba(16, 185, 129, 0.18); - --aksrt-orange-soft:rgba(249, 115, 22, 0.18); - --aksrt-purple-soft:rgba(139, 92, 246, 0.18); - --aksrt-cyan-soft: rgba(6, 182, 212, 0.18); - --aksrt-yellow-soft:rgba(234, 179, 8, 0.18); - --aksrt-red-soft: rgba(239, 68, 68, 0.18); - --aksrt-teal-soft: rgba(20, 184, 166, 0.18); - --aksrt-indigo-soft:rgba(99, 102, 241, 0.18); - --aksrt-pink-soft: rgba(236, 72, 153, 0.18); -} - -/* Dark mode component refinements */ -html.dark .site-nav { - border-bottom-color: var(--color-line); - box-shadow: none; -} - -html.dark .footer-tabbar { - background: rgba(50, 51, 53, 0.80); - box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.20); -} - -html.dark .n-drawer, -html.dark .n-modal, -html.dark .n-dialog { - background: var(--surface-1); -} - -html.dark .post-card, -html.dark .widget, -html.dark .aksrt-theme-box, -html.dark .theme-box { - border-color: var(--color-line); -} -/** - * AKSRT Reset - * 现代精简 reset,结合 box-sizing 与 a11y 友好的默认值。 - */ - -*, -*::before, -*::after { - box-sizing: border-box; -} - -html { - font-size: 16px; - scroll-behavior: smooth; - -webkit-text-size-adjust: 100%; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - overflow-x: hidden; - text-rendering: optimizeLegibility; -} - -body { - margin: 0; - font-family: var(--font-body); - font-size: var(--text-base); - line-height: var(--leading-normal); - background-color: var(--surface-page); - color: var(--color-ink-2); - transition: background-color 0.25s ease, color 0.25s ease; - overflow-x: hidden; -} - -/* 媒体元素 */ -img, -picture, -video, -canvas, -svg { - display: block; - max-width: 100%; - height: auto; -} - -/* 表单元素继承字体 */ -input, -button, -textarea, -select { - font: inherit; - color: inherit; -} - -button { - background: none; - border: 0; - padding: 0; - cursor: pointer; -} - -/* 链接 */ -a { - color: inherit; - text-decoration: none; - transition: color var(--dur-fast) var(--ease); -} - -a:hover { - color: var(--color-primary); -} - -/* 焦点环:a11y 关键 */ -:focus { - outline: none; -} - -:focus-visible { - outline: none; - box-shadow: var(--focus-ring); - border-radius: 4px; -} - -/* 选区色 */ -::selection { - background: var(--color-primary-soft); - color: var(--color-ink); -} - -/* 屏幕阅读器隐藏 */ -.screen-reader-text, -.sr-only { - position: absolute !important; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -.screen-reader-text:focus, -.sr-only:focus { - position: fixed !important; - top: 16px; - left: 16px; - width: auto; - height: auto; - padding: 8px 16px; - background: var(--surface-1); - color: var(--color-ink); - z-index: 100000; - clip: auto; - box-shadow: var(--shadow-lg); - border-radius: var(--radius); -} - -/* 减少动效偏好 */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} -/** - * AKSRT Typography — 1:1 legacy Alignment - * - * 对齐 legacy main.css 的排版系统 - */ - -/* ===== 全局标题 — 对齐 legacy line 8947 ===== */ -h1, h2, h3, h4, h5, h6 { - font-weight: 700; - margin: 20px 0 10px; - color: var(--key-color, #333); -} - -h1 { font-size: 22px; } -h2 { font-size: 20px; } -h3 { font-size: 18px; } -h4 { font-size: 16px; } -h5 { font-size: 15px; } -h6 { font-size: 14px; } - -/* ===== 文章正文 — 对齐 legacy line 8740 ===== */ -.wp-posts-content, -.entry-content, -.prose { - font-size: 16px; - line-height: 2; - color: var(--main-color, #4e5358); - min-height: 90px; -} - -/* ===== 文章内标题 — 对齐 legacy line 8947 ===== */ -.wp-posts-content h1, -.wp-posts-content h2, -.wp-posts-content h3, -.wp-posts-content h4, -.wp-posts-content h5, -.entry-content h1, -.entry-content h2, -.entry-content h3, -.entry-content h4, -.entry-content h5, -.prose h1, -.prose h2, -.prose h3, -.prose h4, -.prose h5 { - font-weight: 700; - margin: 20px 0 10px; - color: var(--key-color, #333); -} - -.wp-posts-content h1, -.entry-content h1, -.prose h1 { font-size: 22px; } - -.wp-posts-content h2, -.entry-content h2, -.prose h2 { font-size: 20px; } - -.wp-posts-content h3, -.entry-content h3, -.prose h3 { font-size: 18px; } - -.wp-posts-content h4, -.entry-content h4, -.prose h4 { font-size: 16px; } - -/* ===== 段落 — 对齐 legacy line 8999 ===== */ -.wp-posts-content p, -.entry-content p, -.prose p { - margin: 0 0 20px; - word-wrap: break-word; -} - -/* ===== 链接 — 对齐 legacy line 8836 ===== */ -.wp-posts-content a, -.entry-content a, -.prose a { - color: var(--focus-color, #f04494); - text-decoration: none; -} - -.wp-posts-content a:hover, -.entry-content a:hover, -.prose a:hover { - color: var(--focus-color, #f04494); -} - -/* ===== 图片 — 对齐 legacy line 8850 ===== */ -.wp-posts-content img, -.entry-content img, -.prose img { - max-width: 100%; - height: auto; - display: block; - margin: auto; -} - -/* ===== 内联代码 — 对齐 legacy line 8844 ===== */ -.wp-posts-content code:not(pre code), -.entry-content code:not(pre code), -.prose code:not(pre code) { - color: #ff3c98; - background: rgba(253, 153, 153, 0.2); - font-family: var(--font-mono, monospace); - font-size: 0.9em; -} - -/* ===== 代码块 — 对齐 legacy ===== */ -.wp-posts-content pre, -.entry-content pre, -.prose pre { - position: relative; - background: #282c34; - color: #abb2bf; - padding: 15px; - border-radius: var(--main-radius, 8px); - overflow-x: auto; - font-size: 14px; - line-height: 1.5; - margin: 0 0 20px; -} - -.wp-posts-content pre code, -.entry-content pre code, -.prose pre code { - color: inherit; - background: none; - padding: 0; - font-size: inherit; -} - -/* ===== 表格 — 对齐 legacy line 8897 ===== */ -.wp-posts-content table, -.entry-content table, -.prose table { - font-size: 14px; - width: 100%; - border-collapse: collapse; - margin: 0 0 20px; -} - -.wp-posts-content th, -.wp-posts-content td, -.entry-content th, -.entry-content td, -.prose th, -.prose td { - border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); - padding: 8px; -} - -.wp-posts-content th, -.entry-content th, -.prose th { - background: var(--muted-border-color, rgba(0, 0, 0, 0.03)); - font-weight: 700; -} - -.wp-posts-content tr:hover, -.entry-content tr:hover, -.prose tr:hover { - background-color: rgba(240, 68, 148, 0.05); -} - -/* ===== 列表 — 对齐 legacy line 8977 ===== */ -.wp-posts-content ul, -.wp-posts-content ol, -.entry-content ul, -.entry-content ol, -.prose ul, -.prose ol { - margin: 10px 0 20px 15px; - padding-left: 15px; -} - -.wp-posts-content li, -.entry-content li, -.prose li { - margin-bottom: 10px; -} - -/* ===== 引用 — 对齐 legacy line 680 ===== */ -.wp-posts-content blockquote, -.entry-content blockquote, -.prose blockquote { - padding: 15px 20px; - margin: 0 0 20px; - border-left: 4px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); - background: var(--muted-border-color, rgba(0, 0, 0, 0.03)); - font-size: 16px; - color: var(--muted-color, #777); - border-radius: 0; -} - -.wp-posts-content blockquote p:last-child, -.entry-content blockquote p:last-child, -.prose blockquote p:last-child { - margin-bottom: 0; -} - -/* ===== 水平分割线 — 对齐 legacy line 676 ===== */ -hr { - border-color: var(--main-border-color, rgba(50, 50, 50, 0.06)); -} - -/* ===== Strong / Bold ===== */ -strong, b { - font-weight: 700; -} -/** - * AKSRT Buttons — 1:1 legacy Alignment - * - * 对齐 legacy .but 系统: - * - 正常字重 (normal) - * - 4px 圆角 - * - 灰色默认背景 - * - opacity hover - */ - -.btn, -.btn-primary, -.btn-outline, -.btn-ghost, -.btn-danger { - --btn-h: 34px; - --btn-px: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - height: var(--btn-h); - padding: 0 var(--btn-px); - font-size: var(--text-sm); - font-weight: normal; - line-height: 1.44; - border: 1px solid transparent; - border-radius: var(--radius-btn); - cursor: pointer; - white-space: nowrap; - user-select: none; - text-decoration: none; - transition: opacity var(--dur-fast) var(--ease), - background var(--dur-fast) var(--ease), - border-color var(--dur-fast) var(--ease), - color var(--dur-fast) var(--ease); -} - -/* 尺寸变体 */ -.btn-sm { --btn-h: 28px; --btn-px: 8px; font-size: var(--text-xs); } -.btn-lg { --btn-h: 44px; --btn-px: 18px; font-size: var(--text-base); } - -/* === Default (gray background, like legacy .but default) === */ -.btn { - background: rgba(136, 136, 136, 0.1); - color: #888; -} - -.btn:hover { - opacity: 0.8; -} - -/* === Primary === */ -.btn-primary { - background: var(--color-primary); - color: var(--color-on-primary); - border-color: var(--color-primary); -} - -.btn-primary:hover { - opacity: 0.85; - color: var(--color-on-primary); -} - -.btn-primary:active { - opacity: 1; - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15); -} - -/* === Outline === */ -.btn-outline { - background: transparent; - color: var(--color-ink-2); - border-color: var(--color-line); -} - -.btn-outline:hover { - border-color: var(--color-primary); - color: var(--color-primary); - opacity: 1; -} - -/* === Ghost === */ -.btn-ghost { - background: transparent; - color: var(--color-ink-2); - border-color: transparent; -} - -.btn-ghost:hover { - background: rgba(136, 136, 136, 0.1); - opacity: 1; -} - -/* === Danger === */ -.btn-danger { - background: var(--color-error); - color: #fff; - border-color: var(--color-error); -} - -.btn-danger:hover { - opacity: 0.85; - color: #fff; -} - -/* === Disabled === */ -.btn:disabled, -.btn-primary:disabled, -.btn-outline:disabled, -.btn-ghost:disabled, -.btn-danger:disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; -} - -/* === Icon button === */ -.btn-icon { - width: var(--btn-h); - padding: 0; - justify-content: center; -} - -.btn-icon.btn-sm { width: 28px; } -.btn-icon.btn-lg { width: 44px; } - -/* === Full width === */ -.btn-block { - width: 100%; -} -/** - * AKSRT Forms — 1:1 legacy Alignment - * - * 对齐 legacy .form-control: - * - 灰色背景 (#f5f6f7) - * - 透明边框 - * - 34px 高度 - * - 8px 圆角 - * - focus 时粉色边框 + 光晕 - */ - -/* ===== Field Wrapper ===== */ -.field { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: var(--space-4); -} - -.field-label { - font-size: var(--text-sm); - font-weight: 600; - color: var(--color-ink); -} - -.field-help { - font-size: var(--text-xs); - color: var(--color-ink-3); -} - -.field-error { - font-size: var(--text-xs); - color: var(--color-error); -} - -/* ===== Input / Textarea / Select — 对齐 legacy .form-control ===== */ -.input, -.textarea, -.select, -input[type="text"].form-input, -input[type="email"].form-input, -input[type="password"].form-input, -input[type="search"].form-input, -input[type="url"].form-input, -input[type="tel"].form-input, -textarea.form-textarea, -select.form-select { - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - color: var(--color-ink); - background: var(--surface-page); - border: 1px solid transparent; - border-radius: var(--radius); - transition: border-color var(--dur-fast) var(--ease), - box-shadow var(--dur-fast) var(--ease); -} - -.textarea, -textarea.form-textarea { - height: auto; - min-height: 120px; - padding: 12px; - line-height: var(--leading-snug); - resize: vertical; -} - -.input:hover, -.textarea:hover, -.select:hover { - border-color: var(--color-primary); -} - -.input:focus, -.textarea:focus, -.select:focus { - outline: none; - border-color: var(--color-primary); - box-shadow: 0 0 2px 3px rgba(253, 83, 161, 0.4); -} - -.input::placeholder, -.textarea::placeholder { - color: var(--color-ink-4); -} - -/* ===== Select ===== */ -.select, -select.form-select { - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23777' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 12px center; - padding-right: 32px; -} - -/* ===== Checkbox / Radio ===== */ -.checkbox, -.radio { - display: inline-flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: var(--text-sm); - color: var(--color-ink-2); -} - -.checkbox input, -.radio input { - width: 16px; - height: 16px; - accent-color: var(--color-primary); - cursor: pointer; -} - -/* ===== Input Group ===== */ -.input-group { - display: flex; - align-items: stretch; -} - -.input-group .input { - border-radius: var(--radius) 0 0 var(--radius); -} - -.input-group .btn { - border-radius: 0 var(--radius) var(--radius) 0; -} - -/* ===== Form Layout ===== */ -.form-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: var(--space-4); -} - -.form-actions { - display: flex; - gap: var(--space-3); - margin-top: var(--space-5); -} -/** - * AKSRT Alerts - * 统一提示组件:error / warning / info / success - * 用于替代各页面 inline 的 .login-error / .bind-error 等 - */ - -.alert { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 12px 14px; - margin-bottom: var(--space-4); - font-size: var(--text-sm); - line-height: var(--leading-snug); - border-radius: var(--radius); - border: 1px solid transparent; -} - -.alert > svg { - flex-shrink: 0; - width: 18px; - height: 18px; - margin-top: 1px; -} - -.alert--error { - color: #B91C1C; - background: rgba(239, 68, 68, 0.08); - border-color: rgba(239, 68, 68, 0.3); -} - -.alert--warning { - color: #B45309; - background: rgba(245, 158, 11, 0.08); - border-color: rgba(245, 158, 11, 0.3); -} - -.alert--success { - color: #047857; - background: rgba(16, 185, 129, 0.08); - border-color: rgba(16, 185, 129, 0.3); -} - -.alert--info { - color: #1D4ED8; - background: rgba(59, 130, 246, 0.08); - border-color: rgba(59, 130, 246, 0.3); -} - -html.dark .alert--error { color: #FCA5A5; } -html.dark .alert--warning { color: #FCD34D; } -html.dark .alert--success { color: #6EE7B7; } -html.dark .alert--info { color: #93C5FD; } diff --git a/assets/css/build/homepage.css b/assets/css/build/homepage.css new file mode 100644 index 00000000..9dcc658f --- /dev/null +++ b/assets/css/build/homepage.css @@ -0,0 +1,905 @@ +/* source: assets/css/frontend/homepage-fixed/base.css */ +/** + * Homepage Fixed Layout — legacy Visual Parity + * + * Implements legacy's homepage visual language: + * - System font stack with Chinese fallbacks + * - 1200px container, 311px sidebar + * - Post cards: list mode (190px thumb) + card mode (3-col grid) + * - legacy shadow/radius/spacing values + * - scale(1.02) thumbnail hover + * - Lazy-load animations (blur/fade/scale) + * - Full responsive: 1200/991/767/640px breakpoints + */ + +/* ================================================================ + 0. ANIMATIONS + ================================================================ */ + +@keyframes aksrt-skeleton-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +/* ================================================================ + 1. HOMEPAGE CONTAINER & TYPOGRAPHY + ================================================================ */ + +.aksrt-homepage { + font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; + font-size: 16px; + line-height: 1.42857143; + color: var(--main-color, #4e5358); + background: transparent; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* Slightly looser than stock legacy short pages: air between stream and footer */ + padding-bottom: 28px; +} + +.aksrt-homepage a[href] { + transition: color 0.2s, background 0.3s, opacity 0.3s, box-shadow 0.3s, transform 0.3s; +} + +.aksrt-homepage .aksrt-skeleton { + animation: aksrt-skeleton-pulse 1.5s ease-in-out infinite; +} + +/* ================================================================ + 2. LAYOUT SHELL + ================================================================ */ + +.aksrt-homepage__fluid-zone.content-shell, +.aksrt-homepage__shell.content-shell { + max-width: min(100%, var(--container, 1200px)); +} + +.aksrt-homepage__fluid-zone.content-shell { + padding-top: 18px; + padding-bottom: 0; +} + +.aksrt-homepage__hero { + width: 100vw; + max-width: 100vw; + margin: 0 calc(-50vw + 50%) 24px; +} + +.aksrt-homepage__hero .header-components-container { + margin-bottom: 0; +} + +.aksrt-homepage__shell.content-shell { + padding-top: 0; + /* 36px + a bit more breathing room before footer */ + padding-bottom: 56px; +} + +/* Bottom fluid zone (if any) keeps a little gap above footer */ +.aksrt-homepage__fluid-zone--bottom { + margin-bottom: 12px; +} + +@media (max-width: 767px) { + .aksrt-homepage { + padding-bottom: 20px; + } + + .aksrt-homepage__shell.content-shell { + padding-bottom: 40px; + } +} + +/* Dual-column geometry is owned by body.site-layout-* float rules in layout.css. */ +.aksrt-homepage__content-layout { + display: block; + gap: 0; +} + +@media (max-width: 1200px) { + .aksrt-homepage__fluid-zone.content-shell, + .aksrt-homepage__shell.content-shell { + max-width: 95%; + } +} + +.aksrt-homepage__content-zone, +.aksrt-homepage__fluid-zone, +.aksrt-homepage__aside { + display: block; +} + +.aksrt-homepage__system-modules { + gap: 0; + padding-bottom: 0; +} + +.aksrt-homepage__content-zone.aksrt-homepage__system-modules { + margin-bottom: 20px; +} + +.aksrt-homepage__content-zone--featured { + margin-bottom: 20px; +} + +.aksrt-homepage__content-zone--operated, +.aksrt-homepage__content-zone--mid, +.aksrt-homepage__content-zone--supplement { + margin-bottom: 20px; +} + +.aksrt-homepage__content-zone--supplement { + padding-top: 4px; +} + +.aksrt-homepage__content-zone .widget-card:last-child, +.aksrt-homepage__aside .widget:last-child, +.aksrt-homepage__fluid-zone .widget-fluid:last-child { + margin-bottom: 0; +} + +/* ================================================================ + 3. FLUID WIDGETS + ================================================================ */ + +.aksrt-homepage__fluid-zone .widget-fluid { + margin-bottom: 20px; + background: transparent; + padding: 0; + border: 0; + box-shadow: none; +} + +.aksrt-homepage .widget-fluid.widget_aksrt_tab_posts, +.aksrt-homepage .widget-fluid.widget_aksrt_category_block, +.aksrt-homepage .widget-fluid.widget_aksrt_category_tabs, +.aksrt-homepage .widget-fluid.widget_aksrt_category_recommend { + padding: 15px; + background: var(--surface-1, #fff); + border: none; + border-radius: var(--main-radius, var(--radius, 8px)); + box-shadow: var(--shadow-sm); +} + +.aksrt-homepage__content-zone .widget-card, +.aksrt-homepage__aside .widget { + background: var(--surface-1, #fff); + padding: 15px; + border: none; + border-radius: var(--main-radius, var(--radius, 8px)); + box-shadow: var(--shadow-sm); +} + +/* ================================================================ + 4. STREAM CONTAINER + ================================================================ */ + +.aksrt-homepage__stream-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 12px; + margin-bottom: 10px; + padding: 0; +} + +.aksrt-homepage__stream-intro { + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +.aksrt-homepage__stream-title { + margin: 0; + color: var(--color-ink); + font-size: 20px; + font-weight: 700; + line-height: 1.25; +} + +.aksrt-homepage__stream-subtitle { + margin: 0; + color: var(--color-ink-3, #777); + font-size: 13px; + line-height: 1.5; +} + +.aksrt-homepage__stream, +.aksrt-homepage__stream.widget-card { + overflow: visible; + padding: 0; + background: transparent; + border: 0; + border-radius: 0; + box-shadow: none; + margin-bottom: 0; +} + +/* ================================================================ + 5. TABS + ================================================================ */ + +.aksrt-homepage__tabs, +.aksrt-homepage__titlebox { + margin-bottom: 6px; + padding-bottom: 0; + border-bottom: 0; +} + +.aksrt-homepage__tabs.box-body, +.aksrt-homepage__titlebox.box-body { + padding: 0; +} + +.aksrt-homepage__tabs-sentinel { + width: 100%; + height: 1px; + margin: 0; + pointer-events: none; +} + +.aksrt-homepage__tabs.home-tab-nav-box { + position: static; + z-index: auto; + background: transparent; +} + +.aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"] { + position: sticky; + top: var(--nav-height, 60px); + z-index: 995; + background: transparent; + transition: background-color 0.2s ease, + box-shadow 0.2s ease, + border-radius 0.2s ease, + padding 0.2s ease, + margin 0.2s ease, + backdrop-filter 0.2s ease, + -webkit-backdrop-filter 0.2s ease; +} + +.aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-stuck, +.aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-affixed { + margin-bottom: 14px; + padding: 10px 16px; + background: rgba(255, 255, 255, 0.92); + border: none; + border-radius: var(--radius-2xl, 16px); + box-shadow: var(--shadow); + backdrop-filter: saturate(1.6) blur(20px); + -webkit-backdrop-filter: saturate(1.6) blur(20px); +} + +.aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-affixed { + position: fixed; +} + +html.dark .aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-stuck, +html.dark .aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-affixed, +html.dark .aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-stuck, +html.dark .aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-affixed { + background: rgba(24, 24, 24, 0.82); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.24); +} + +.aksrt-homepage__tab-list { + list-style: none; + display: flex; + align-items: center; + gap: 4px; + margin: 0; + padding: 0; +} + +.aksrt-homepage__admin-link { + margin-left: auto; +} + +.scroll-x { + overflow-x: auto; + overflow-y: hidden; +} + +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +.aksrt-homepage__tab-item { + display: inline-flex; + flex: 0 0 auto; + padding: 0; + margin: 0; + border-radius: 20px; + font-weight: 500; + transition: background 0.2s ease, color 0.2s ease; +} + +.aksrt-homepage__tab-button { + padding: 6px 14px; + border: 0; + background: transparent; + color: var(--color-ink); + font-family: inherit; + font-size: 14px; + font-weight: inherit; + line-height: 1.2; + white-space: nowrap; + border-radius: inherit; + cursor: pointer; +} + +.aksrt-homepage__tab-button:hover { + color: var(--color-primary, var(--color-primary)); +} + +.aksrt-homepage__tab-item.is-active { + background: var(--color-primary, var(--color-primary)); +} + +.aksrt-homepage__tab-item.is-active .aksrt-homepage__tab-button, +.aksrt-homepage__tab-button[aria-selected="true"] { + color: #fff !important; +} + +/* ================================================================ + 6. SORTBAR + VIEW TOGGLE + ================================================================ */ + +.aksrt-homepage__sortbar { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + padding: 0 0 8px; + border-bottom: 1px solid var(--color-line, rgba(50, 50, 50, 0.06)); + color: var(--color-ink-3, #777); + font-size: 13px; +} + +.aksrt-homepage__sortbar-links { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0; + flex: 1 1 auto; +} + +.aksrt-homepage__sortbar-link { + position: relative; + display: inline-flex; + align-items: center; + padding: 0 12px; + color: var(--color-ink-3, #777); + text-decoration: none; + line-height: 1.4; +} + +.aksrt-homepage__sortbar-link:first-child { + padding-left: 0; +} + +.aksrt-homepage__sortbar-link + .aksrt-homepage__sortbar-link::before { + content: "\00b7"; + position: absolute; + left: -2px; + color: var(--color-ink-4, #b1b1b1); +} + +.aksrt-homepage__sortbar-link:hover, +.aksrt-homepage__sortbar-link.is-active { + color: var(--color-ink); +} + +.aksrt-homepage__view-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: auto; + flex: 0 0 auto; +} + +.aksrt-homepage__view-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: 0; + background: transparent; + color: var(--color-ink-4, #999); + border-radius: 4px; + cursor: pointer; + transition: color 0.2s, background 0.2s; +} + +.aksrt-homepage__view-btn:hover { + color: var(--color-ink); + background: var(--surface-2, #eee); +} + +.aksrt-homepage__view-btn.is-active { + color: var(--color-primary); +} + +.aksrt-homepage__view-btn svg { + width: 16px; + height: 16px; + fill: currentColor; +} + +.title-theme { + position: relative; + padding-left: 1.2em; + font-size: 15px; + font-weight: 700; + line-height: 1.4; +} + +.title-theme::before { + position: absolute; + top: 10%; + bottom: 10%; + left: 2px; + width: 4px; + content: ""; + background: var(--color-primary); + border-radius: 5px; + box-shadow: 1px 1px 3px -1px var(--color-primary); +} + +.aksrt-homepage__page-indicator { + margin-left: 10px; + font-size: 80%; + font-weight: 400; + opacity: 0.7; +} + +.aksrt-homepage__panel[hidden], +.index-tab-main .tab-pane[hidden], +.home-tab-content .tab-pane[hidden] { + display: none !important; +} + +.aksrt-homepage__panels, +.home-tab-content { + display: block; +} + +.aksrt-homepage__panel { + opacity: 0; + transition: opacity 0.3s ease; +} + +.aksrt-homepage__panel.is-active, +.aksrt-homepage__panel.active, +.aksrt-homepage__panel.fade.in, +.aksrt-homepage__panel.tab-pane.active, +#index-tab-main.aksrt-homepage__panel, +.home-tab-content .tab-pane.is-active, +.home-tab-content .tab-pane.show, +.home-tab-content .tab-pane.active, +.aksrt-homepage__panel.show { + display: block !important; + visibility: visible !important; + opacity: 1 !important; +} + +.home-tab-content .tab-pane.fade.is-active, +.home-tab-content .tab-pane.fade.show, +.home-tab-content .tab-pane.fade.active, +#index-tab-main.fade { + opacity: 1 !important; +} + +.aksrt-homepage__panel-contract { + display: none; +} + +.flex { + display: flex !important; +} + +.flex.xx, +.xx { + flex-direction: column; +} + +.flex1, +.flex-1 { + flex: 1 1 auto; + min-width: 0; +} + +.jsb, +.jb { + justify-content: space-between; +} + +.ac { + align-items: center; +} + +/* ================================================================ + 11. PAGINATION + ================================================================ */ + +.aksrt-homepage__panel-footer { + display: flex; + justify-content: center; + margin-top: 18px; +} + +.aksrt-homepage__more, +.aksrt-homepage .aksrt-load-more-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 34px; + padding: 0 14px; + border: 1px solid var(--color-line, rgba(50, 50, 50, 0.06)); + border-radius: var(--radius, 8px); + background: var(--surface-1, #fff); + color: var(--color-ink-3, #777); + font-size: 13px; + font-weight: 500; + transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease; +} + +.aksrt-homepage__more:hover, +.aksrt-homepage .aksrt-load-more-btn:hover { + color: var(--color-primary, var(--color-primary)); + border-color: var(--color-primary, var(--color-primary)); + box-shadow: none; + transform: none; +} + +.aksrt-homepage .ajax-pagination { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + margin-top: 20px; +} + +.aksrt-homepage .navigation.pagination .nav-links, +.aksrt-homepage .pagination { + gap: 0; + margin: 20px 0 10px; +} + +.aksrt-homepage .navigation.pagination .page-numbers, +.aksrt-homepage .pagination .page-numbers { + min-width: 34px; + height: 34px; + padding: 0 10px; + margin: 3px; + border: 1px solid transparent; + border-radius: var(--radius-xs, 3px); + background: var(--surface-4, #eee); + color: var(--color-ink-3, #777); + font-size: 13px; + line-height: 1.4; +} + +.aksrt-homepage .navigation.pagination .page-numbers:hover, +.aksrt-homepage .pagination .page-numbers:hover { + opacity: 1; + background: var(--color-primary-soft); + color: var(--color-primary); +} + +.aksrt-homepage .navigation.pagination .current, +.aksrt-homepage .pagination .current { + background: var(--color-primary, var(--color-primary)) !important; + color: #fff !important; +} + +/* ================================================================ + 12. SIDEBAR + ================================================================ */ + +.aksrt-homepage__aside > .aksrt-panel, +.aksrt-homepage__aside > .widget { + margin-bottom: 15px; + padding: 15px; + background: var(--surface-1, #fff); + border: 0; + border-radius: var(--main-radius, var(--radius, 8px)); + box-shadow: var(--shadow-sm); +} + +.aksrt-homepage__aside > .aksrt-panel:last-child, +.aksrt-homepage__aside > .widget:last-child { + margin-bottom: 0; +} + +.aksrt-homepage__aside .widget-list li, +.aksrt-homepage__aside .widget_recent_entries li, +.aksrt-homepage__aside .recent-posts-row { + padding-top: 8px; + padding-bottom: 8px; +} + +.aksrt-homepage__aside .widget-title, +.aksrt-homepage__content-zone .widget-title { + margin-bottom: 12px; +} + +.aksrt-homepage__aside .widget-title { + position: relative; + margin: 0 0 10px; + padding-left: 1.2em; + padding-bottom: 8px; + font-size: 15px; + font-weight: 700; + line-height: 1.4; +} + +.aksrt-homepage__aside .widget-title::before { + content: ""; + position: absolute; + top: 10%; + bottom: 10%; + left: 2px; + width: 4px; + height: auto; + background: var(--color-primary, var(--color-primary)); + border-radius: 5px; + box-shadow: 1px 1px 3px -1px var(--color-primary, var(--color-primary)); + transition: transform 0.4s ease; + transform-origin: left; +} + +.aksrt-homepage__aside .widget_recent_entries li a, +.aksrt-homepage__aside .widget-list li a { + font-size: 13px; + font-weight: 500; + line-height: 1.5; +} + +.aksrt-homepage__aside .widget_recent_entries li { + display: block; +} + +.aksrt-homepage__aside .widget_search .search-field { + height: 40px; + border-radius: 30px; + background: var(--surface-2, #eee); +} + +.aksrt-homepage__aside .widget_search .search-submit { + top: 4px; + right: 4px; + height: 32px; + padding: 0 12px; + border-radius: 30px; + font-size: 12px; +} + +.aksrt-homepage__aside .tag-cloud { + gap: 6px; +} + +.aksrt-homepage__aside .tag-cloud a { + padding: 4px 10px; + border-radius: 20px; + font-size: 12px; + border: 0; + background: var(--surface-2, #eee); + transition: background 0.2s, color 0.2s; +} + +.aksrt-homepage__aside .tag-cloud a:hover { + background: var(--color-primary, var(--color-primary)); + color: #fff; +} + +.aksrt-homepage .widget.notop, +.aksrt-homepage .box-body.notop { + padding-top: 0; +} + +.aksrt-homepage .widget.nobottom, +.aksrt-homepage .box-body.nobottom { + padding-bottom: 0; +} + +.aksrt-homepage .widget.widget_custom_html { + padding: 0; + background: transparent; + box-shadow: none; + border-radius: 0; +} + +/* ================================================================ + 13. LOADING & EMPTY STATES + ================================================================ */ + +.aksrt-homepage .aksrt-loading-indicator { + display: flex; + align-items: center; + gap: 8px; + color: var(--color-ink-3, #777); + font-size: 13px; +} + +.aksrt-homepage__stream .no-results { + padding: 32px 16px; + text-align: center; + color: var(--color-ink-4, #999); +} + +/* source: assets/css/frontend/homepage-fixed/ajax-tab-skeleton-placeholder.css */ +/* AJAX tab skeleton placeholder */ +.aksrt-homepage__tab-skeleton { + display: flex; + flex-direction: column; + gap: 15px; + padding: 10px 0; +} + +.aksrt-homepage__tab-skeleton-item { + display: flex; + gap: 16px; + padding: 20px; + background: var(--surface-1, #fff); + border: none; + border-radius: var(--main-radius, var(--radius, 8px)); + box-shadow: var(--shadow-sm); + animation: aksrt-skeleton-pulse 1.5s ease-in-out infinite; +} + +.aksrt-homepage__tab-skeleton-thumb { + width: 190px; + min-width: 190px; + height: 133px; + background: var(--surface-2, #eee); + border-radius: var(--radius, 8px); +} + +.aksrt-homepage__tab-skeleton-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 10px; +} + +.aksrt-homepage__tab-skeleton-line { + height: 14px; + background: var(--surface-2, #eee); + border-radius: 4px; +} + +.aksrt-homepage__tab-skeleton-line:first-child { + width: 70%; + height: 18px; +} + +.aksrt-homepage__tab-skeleton-line:nth-child(2) { + width: 100%; +} + +.aksrt-homepage__tab-skeleton-line:nth-child(3) { + width: 40%; +} + +/* source: assets/css/frontend/homepage-fixed/ajax-tab-error-state.css */ +/* AJAX tab error state */ +.aksrt-homepage__tab-error { + padding: 32px 16px; + text-align: center; + color: var(--color-ink-4, #999); + font-size: 14px; + background: var(--surface-1); + border: none; + border-radius: var(--main-radius, var(--radius, 8px)); + box-shadow: var(--shadow-sm); +} + +.aksrt-homepage__tab-retry { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 12px; + padding: 8px 16px; + border: 1px solid var(--color-line); + border-radius: var(--radius-btn, 4px); + background: var(--surface-4, #fff); + color: var(--color-ink-3, #777); + font-size: 13px; + cursor: pointer; + transition: color 0.2s, border-color 0.2s, background 0.2s; +} + +.aksrt-homepage__tab-retry:hover { + color: var(--color-primary, var(--color-primary)); + border-color: var(--color-primary, var(--color-primary)); + background: var(--color-primary-soft); +} + +/* ================================================================ + 14. RESPONSIVE — TABLET (max-width: 991px) + ================================================================ */ + +@media (max-width: 991.98px) { + .aksrt-homepage__shell.content-shell, + .aksrt-homepage__fluid-zone.content-shell { + padding-top: 16px; + } + +} + +/* ================================================================ + 15. RESPONSIVE — MOBILE (max-width: 767px) + ================================================================ */ + +@media (max-width: 767px) { + .aksrt-homepage__shell.content-shell { + padding-left: 10px; + padding-right: 10px; + } + + /* Tab skeleton: smaller thumb */ + .aksrt-homepage__tab-skeleton-thumb { + width: 120px; + min-width: 120px; + height: 84px; + } +} + +/* ================================================================ + 16. RESPONSIVE — SMALL MOBILE (max-width: 640px) + ================================================================ */ + +@media (max-width: 640px) { + .aksrt-homepage__stream-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .aksrt-homepage__stream-title { + font-size: 20px; + } + + .aksrt-homepage__stream-subtitle { + font-size: 13px; + } + + .aksrt-homepage__tabs, + .aksrt-homepage__titlebox { + margin-bottom: 8px; + } + + .aksrt-homepage__admin-link { + margin-left: 0; + margin-top: 12px; + } + + .aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-stuck, + .aksrt-homepage__tabs.home-tab-nav-box[data-sticky-tabs="true"].is-affixed { + padding: 8px 10px; + border-radius: 10px; + } + + .aksrt-homepage__sortbar { + gap: 10px; + margin-bottom: 12px; + padding-bottom: 12px; + font-size: 13px; + } + + /* Tab skeleton: adapt */ + .aksrt-homepage__tab-skeleton-thumb { + width: 96px; + min-width: 96px; + height: 67px; + } +} diff --git a/assets/css/build/frontend-base.min.css.disabled b/assets/css/build/public.css similarity index 53% rename from assets/css/build/frontend-base.min.css.disabled rename to assets/css/build/public.css index 1fd9b376..6da5fb19 100644 --- a/assets/css/build/frontend-base.min.css.disabled +++ b/assets/css/build/public.css @@ -1,3 +1,4 @@ +/* source: assets/css/frontend/base.css */ /** * AKSRT Main v5 — 博客/个人站完整前端 */ @@ -30,8 +31,40 @@ body { /* 折叠 body 内注入的 BOM / 空白文本节点,防止产生多余行高间距 */ body { - font-size: 0; - line-height: 0; + font-family: var(--font-body); + font-size: var(--text-base); + line-height: var(--leading-normal); + color: var(--color-ink-2); + background-color: var(--surface-page); +} + +/* legacy compat: global link composite transition */ +a[href] { + transition: color 0.2s, background 0.3s, opacity 0.3s, box-shadow 0.3s, transform 0.3s; +} + +/* Font Awesome 5: when legacy markup includes both `fa` and `fab`, the + * generic Solid rule must not override the Brands font face. */ +.fa.fab { + font-family: "Font Awesome 5 Brands"; + font-weight: 400; +} + +.skip-link { + position: absolute; + left: 16px; + top: 12px; + z-index: 1100; + padding: 10px 14px; + border-radius: 999px; + background: var(--color-primary); + color: #fff; + transform: translateY(-140%); + transition: transform 0.2s ease; +} + +.skip-link:focus { + transform: translateY(0); } /* ========== 站点外壳 ========== */ @@ -47,113 +80,722 @@ body { flex: 1; } -.container { +/* legacy dual-column float math expects a padding-free main.container. + * Bootstrap 5 also defines .container (max-width ladder + padding-x). + * Force legacy geometry after Bootstrap so negative-margin sidebars land correctly. */ +.container, +main.container, +.site-main.container { width: 100%; - max-width: var(--container); - margin: 0 auto; - padding: 0 16px; + max-width: var(--container, var(--mian-max-width, 1200px)); + margin-right: auto; + margin-left: auto; + padding-right: 0; + padding-left: 0; + /* Kill Bootstrap 5 --bs-gutter-x padding model on site shells */ + --bs-gutter-x: 0; + --bs-gutter-y: 0; +} + +/* Header multifunc icon cards need horizontal gutter (legacy content rows). + * Global .container padding is 0 for dual-column float math. */ +.header-slider-card.container { + padding-left: 15px; + padding-right: 15px; + box-sizing: border-box; +} + +/* Header multifunc icon cards: pin typography baseline so cards render + * identically on all pages. .px12-sm is only defined globally in + * legacy-utilities.css, which is @import'd by the single-post aggregator; + * vendor CSS pins it at <=640px / .sidebar / .mobile-nav-widget only. */ +.header-slider-card .icon-cover-card { + font-size: 12px; +} + +/* Keep the header multifunction search stable across route-specific CSS. + * Some routes load a later .abs-right utility, while the global input focus + * ring looks like an extra border against this transparent pill field. */ +.header-slider-search .line-form-input:focus, +.header-slider-search .line-form-input:focus-visible { + border: 0; + outline: 0; + box-shadow: none; +} + +.header-slider-search .line-form .abs-right { + position: absolute; + right: 0.7em; + top: 50%; + transform: translateY(-50%); + margin-top: 0; +} + +@media (min-width: 576px) { + .container, + main.container { + max-width: var(--container, var(--mian-max-width, 1200px)); + } } @media (min-width: 768px) { - .container { - padding: 0 24px; + .container, + main.container { + max-width: var(--container, var(--mian-max-width, 1200px)); + } +} + +@media (min-width: 992px) { + .container, + main.container { + max-width: var(--container, var(--mian-max-width, 1200px)); } } @media (min-width: 1200px) { - .container { - padding: 0 32px; + .container, + main.container { + max-width: var(--container, var(--mian-max-width, 1200px)); } } -/* ========== 通用空状态 ========== */ +@media (min-width: 1240px) { + .container, + main.container, + .fluid-widget-wrap .widget-container { + width: auto; + max-width: var(--container, var(--mian-max-width, 1200px)); + } +} + +@media (min-width: 1400px) { + .container, + main.container { + max-width: var(--container, var(--mian-max-width, 1200px)); + } +} + +/* Soft pad only when the float dual-column is collapsed. + * Match legacy bootstrap container pad (15px) rather than 10px. */ +@media (max-width: 991.98px) { + .container, + main.container { + padding-right: 15px; + padding-left: 15px; + } +} + +@media (max-width: 767px) { + .container, + main.container { + width: 100%; + max-width: 100%; + padding-right: 15px; + padding-left: 15px; + } +} + +/* ========== User identity inline icons ========== + * All compact identity marks share one line-height baseline: VIP, level, + * certification, and worn medals remain visually aligned in every surface. + */ +.aksrt-vip-badge, +.aksrt-user-level-badge, +.aksrt-user-level-icon, +.aksrt-identity-medal, +.aksrt-identity-certification-icon, +.aksrt-header-user-medal { + display: inline-flex; + align-items: center; + justify-content: center; + width: auto; + height: 1.1em; + max-width: 6em; + object-fit: contain; + vertical-align: -.15em; + flex: 0 0 auto; + box-sizing: border-box; +} +.aksrt-vip-badge__icon { + display: block; + width: auto; + height: 1.1em; + max-width: 6em; + object-fit: contain; +} +.aksrt-vip-badge > i { + font-size: .9em; + line-height: 1; +} +.aksrt-user-level-badge { + padding: 0 .35em; + font-size: .78em; + line-height: 1.1; + white-space: nowrap; +} +/* Comment context: keep all identity marks inside the compact author line. */ +.aksrt-identity-medal--comment, +.aksrt-user-level-icon--comment, +.aksrt-header-user-medal--comment { + height: 1em; + vertical-align: -.12em; +} +.aksrt-vip-badge--sm { + height: 1.1em; + padding: 0; + font-size: inherit; +} + +/* No-wrap inline name rows — keep the level icon on the same line as the name. + * The non-standard element (legacy compat) plus .user-name / author-hero / + * uc-header name rows must not flex-wrap, or the badge drops to a new line. + * The comment header may still wrap (avatar + name + like can overflow on + * narrow viewports), so it keeps flex-wrap but the inner never does. */ +.article-avatar name, +.article-avatar .name, +.user-name, +.aksrt-author-hero__name, +.uc-header__name, +.aksrt-header-badges { + flex-wrap: nowrap; + white-space: nowrap; +} +.aksrt-author-hero__name, +.uc-header__name { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.aksrt-author-hero__name-inner { + display: inline-flex; + align-items: center; + gap: 4px; + min-width: 0; +} + +.aksrt-ip-geo-badge { + display: inline-flex; + align-items: center; + min-width: 0; + margin: 2px 0; + padding: .3em .6em; + border: 1px solid var(--color-line-soft, var(--main-border-color, rgba(136, 136, 136, .18))); + border-radius: var(--mini-radius, 4px); + background: var(--surface-2, rgba(136, 136, 136, .1)); + box-shadow: var(--shadow-xs, 0 1px 3px rgba(0, 0, 0, .08)); + color: var(--color-ink-3, #666); + font-size: .92em; + line-height: 1.44; + vertical-align: middle; + box-sizing: border-box; +} + + .no-results { padding: 48px 20px; text-align: center; color: var(--color-ink-3); -}/** - * legacy Variable Compatibility Layer — Global Bridge +} + +/* source: assets/css/frontend/header-userbox.css */ +/** + * Header account popover. * - * Maps legacy CSS variable names to AKSRT design tokens. - * This file allows existing legacy-style markup and third-party components - * that reference legacy variables to work correctly in AKSRT. - * - * @package AKSRT + * The shared legacy navigation styles provide the popover shell. This module + * owns AKSRT-specific account content such as check-in, wallet and VIP rows. */ -/* ============================================================ - * Global bridge (all pages) - * Maps legacy variables → AKSRT tokens so any legacy-style markup works. - * ============================================================ */ -:root { - /* Layout (legacy typo preserved for compat) */ - --mian-max-width: var(--container, 1200px); - - /* Theme color — now matches legacy #f04494 */ - --theme-color: var(--color-primary, #f04494); - --focus-color: var(--color-primary, #f04494); - --focus-shadow-color: var(--color-primary-soft, rgba(253, 83, 161, 0.4)); - - /* Text hierarchy */ - --key-color: var(--color-ink); - --main-color: var(--color-ink-2); - --muted-color: var(--color-ink-3); - --muted-2-color: var(--color-ink-4); - --muted-3-color: #b1b1b1; - --muted-4-color: #d2d2d2; - - /* Surfaces & borders */ - --body-bg-color: var(--surface-page); - --main-bg-color: var(--surface-1); - --muted-bg-color: var(--surface-2); - --main-border-color: var(--color-line); - --muted-border-color: var(--color-line-soft); - --main-shadow: rgba(116, 116, 116, 0.08); - --main-radius: var(--radius, 8px); - - /* Radius variants */ - --mini-radius: calc(var(--main-radius) / 1.4); - - /* Blur / glass */ - --blur-bg: rgba(255, 255, 255, 0.8); - --float-btn-bg: rgba(200, 200, 200, 0.4); - - /* Header / footer composites */ - --header-bg: var(--blur-bg); - --header-color: var(--color-ink-2); - --footer-bg: var(--surface-1); - --footer-color: var(--color-ink-4); - - /* Cards — 对齐 legacy 70% */ - --posts-card-scale: 70%; - --posts-list-scale: 70%; - --single-cover-scale: 35%; +/* The desktop action cluster owns one horizontal alignment context. */ +.navbar-top .aksrt-navbar-actions { + display: flex; + align-items: center; + gap: 10px; + min-height: 50px; + margin-top: 0; + margin-bottom: 0; + padding: 0; } -/* ============================================================ - * Dark mode bridge — 对齐 legacy dark theme - * ============================================================ */ -html.dark { - --theme-color: var(--color-primary); - --focus-color: var(--color-primary); - --focus-shadow-color: rgba(253, 83, 161, 0.25); - --key-color: var(--color-ink); - --main-color: var(--color-ink-2); - --muted-color: var(--color-ink-3); - --muted-2-color: var(--color-ink-4); - --muted-3-color: #666; - --muted-4-color: #555; - --body-bg-color: var(--surface-page); - --main-bg-color: var(--surface-1); - --muted-bg-color: var(--surface-2); - --main-border-color: var(--color-line); - --muted-border-color: var(--color-line-soft); - --main-shadow: rgba(24, 24, 24, 0.1); - --blur-bg: rgba(50, 51, 53, 0.8); - --float-btn-bg: rgba(70, 70, 70, 0.4); - --header-color: var(--color-ink-2); - --footer-color: var(--color-ink-4); +.navbar-top .aksrt-navbar-actions > *, +.navbar-top .aksrt-navbar-account, +.navbar-top .aksrt-navbar-account > li { + display: inline-flex; + align-items: center; + flex: 0 0 auto; } + +.navbar-top .aksrt-navbar-account > li { + position: relative; + padding: 0; + overflow: visible; +} + +.navbar-top .aksrt-navbar-account > li > .sub-menu { + top: calc(100% + 5px); + right: 0; + left: auto; + z-index: 1002; + margin: 0; +} + +.navbar-top .aksrt-navbar-account { + gap: 4px; + min-height: 50px; + margin: 0; +} + +.navbar-top .aksrt-navbar-account.navbar-text { + float: none; +} + +.navbar-top .aksrt-navbar-actions .navbar-cta { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + min-height: 29px; + margin: 0; + padding: .3em 1em !important; + border-radius: var(--radius-btn, 4px); + font-size: .8em; + line-height: 1.44; +} + +.navbar-top .aksrt-navbar-actions .navbar-cta.radius { + border-radius: 50px; +} + +.navbar-top .aksrt-navbar-actions .navbar-cta > .icon { + width: 1.2em; + height: 1.2em; + flex: 0 0 auto; +} + +.navbar-top .aksrt-nav-icon, +.navbar-top .aksrt-nav-avatar { + display: inline-flex !important; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + padding: 0 !important; + overflow: visible !important; + border: 0 !important; + border-radius: 0; + background: transparent !important; + box-shadow: none !important; +} + +.navbar-top .aksrt-nav-icon .icon { + width: 1.2em; + height: 1.2em; +} + +.navbar-top .aksrt-nav-avatar { + margin-top: 0; +} + +.navbar-top .aksrt-nav-avatar .avatar { + display: block; + width: 29px; + height: 29px; + border-radius: 50%; + object-fit: cover; +} + +.navbar-top .aksrt-navbar-actions .toggle-radius { + flex: 0 0 29px; + margin: 0; + vertical-align: middle; +} + +.navbar-top .aksrt-navbar-language { + margin: 0; +} + +/* Anchor the VIP preview to the full navigation row instead of relying on + * the absolute panel's static position beside the compact CTA. */ +.navbar-top .aksrt-navbar-actions .navbar-vip-hover { + position: relative; + align-self: stretch; + align-items: center; + overflow: visible; +} + +.navbar-top .aksrt-navbar-actions .navbar-vip-hover > .sub-vip-card { + top: 100%; + right: 0; + left: auto; + width: max-content; + max-width: calc(100vw - 24px); + margin: 0; + z-index: 1002; +} + +/* Keep the drop-down search panel above a fixed header (z-index: 999). */ +.navbar-search { + z-index: 1001; + opacity: 0; + transform: translate3d(0, -24px, 0); + transition: transform .24s ease, opacity .18s ease, visibility 0s linear .24s; + will-change: transform, opacity; +} + +.navbar-search.show { + opacity: 1; + transform: translate3d(0, 0, 0); + transition-delay: 0s; +} + +.navbar-search .line-form-input:focus, +.navbar-search .line-form-input:focus-visible { + outline: 0; + box-shadow: none; +} + +.navbar-search .search-close-box { + display: flex; + justify-content: flex-end; + max-width: 660px; + margin: 16px auto 0; +} + +.navbar-search .search-close-box > .but { + float: none; +} + +.aksrt-header-userbox { + width: 260px; + color: var(--color-ink-2); +} + +.aksrt-header-userbox .user-info { + min-height: 44px; + padding: 2px; +} + +.aksrt-header-userbox .user-info > .avatar { + flex: 0 0 44px; + width: 44px; + height: 44px; + object-fit: cover; +} + +.aksrt-header-userbox .user-right, +.aksrt-header-userbox .user-right > .flex1 { + min-width: 0; +} + +.aksrt-header-userbox .user-right b { + display: flex; + align-items: center; + gap: 4px; + max-width: 100%; + overflow: hidden; + white-space: nowrap; +} + +.aksrt-header-userbox .display-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aksrt-header-userbox .user-right .aksrt-vip-badge, +.aksrt-header-userbox .user-right .aksrt-user-level-icon, +.aksrt-header-userbox .user-right .aksrt-identity-medal, +.aksrt-header-userbox .user-right .aksrt-header-user-medal, +.aksrt-header-userbox .user-right .aksrt-identity-certification-icon { + flex: 0 0 auto; + height: 1.1em; +} + +.aksrt-header-userbox .user-info > .abs-right { + top: 50%; + right: 2px; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 50%; + background: var(--surface-2); + color: var(--color-ink-3); +} + +.aksrt-header-userbox .aksrt-notification-badge { + position: absolute; + top: -4px; + right: -4px; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 17px; + height: 17px; + padding: 0 4px; + border: 2px solid var(--surface-1); + border-radius: 999px; + background: #e74c3c; + color: #fff; + font-size: 10px; + font-weight: 700; + line-height: 1; +} + +.aksrt-nav-notification .aksrt-notification-badge { + position: absolute; + top: 2px; + right: 2px; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 16px; + height: 16px; + padding: 0 4px; + border: 2px solid var(--surface-1); + border-radius: 999px; + background: #e74c3c; + color: #fff; + font-size: 9px; + font-weight: 700; + line-height: 1; +} + +.aksrt-header-wallet { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + width: 100%; + padding: 9px 10px; + border-radius: 8px; + background: var(--surface-2); + color: var(--color-ink-2); + font-size: 12px; + line-height: 1.35; +} + +.aksrt-header-wallet:hover { + color: var(--color-ink); +} + +.aksrt-header-wallet__item { + min-width: 0; + white-space: nowrap; +} + +.aksrt-header-wallet__item i { + margin-right: 2px; + color: var(--color-ink-3); +} + +.aksrt-header-wallet__item b { + margin-left: 2px; + color: var(--color-ink); + font-weight: 700; +} + +.aksrt-header-checkin { + display: inline-flex !important; + align-items: center; + justify-content: center; + gap: 6px; + width: 100% !important; + min-height: 36px; + margin: 0; + padding: 8px 10px !important; + border: 0 !important; + border-radius: 6px !important; + background: #fff6db !important; + color: #8a5a00 !important; + font-size: 13px !important; + font-weight: 600; + line-height: 1.3; + white-space: nowrap; +} + +.aksrt-header-checkin > i { + margin: 0 !important; + color: #b78103 !important; +} + +.aksrt-header-checkin:hover { + background: #fff0c2 !important; + color: #8a5a00 !important; +} + +.aksrt-header-checkin.is-done, +.aksrt-header-checkin:disabled, +.aksrt-header-checkin.is-done:hover, +.aksrt-header-checkin:disabled:hover { + cursor: default; + opacity: .85; + background: #f3f4f6 !important; + color: #555 !important; +} + +.aksrt-header-checkin.is-done > i, +.aksrt-header-checkin:disabled > i { + color: #777 !important; +} + +.aksrt-header-payvip__cta, +.aksrt-header-payvip:not(.is-vip) .payvip-icon, +.aksrt-header-payvip:not(.is-vip) .but { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + gap: 6px !important; + width: 100% !important; + min-width: 0 !important; + min-height: 36px; + margin: 0 !important; + padding: 8px 10px !important; + border: 0 !important; + border-radius: 6px !important; + background: linear-gradient(90deg, #ff6b6b, #f04494) !important; + color: #fff !important; + font-size: 13px !important; + font-weight: 600; + line-height: 1.3; + text-align: center; + white-space: nowrap; +} + +.aksrt-header-payvip__cta > i, +.aksrt-header-payvip:not(.is-vip) .payvip-icon > i, +.aksrt-header-payvip:not(.is-vip) .but > i { + margin: 0 !important; + color: inherit !important; +} + +.aksrt-header-payvip__cta:hover { + color: #fff !important; + opacity: .92; +} + +.aksrt-header-payvip__active { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px !important; + width: 100%; + min-height: 42px; + padding: 7px 10px !important; + white-space: nowrap; +} + +.aksrt-header-payvip__level { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; +} + +.aksrt-header-payvip__level > i { + margin: 0 !important; +} + +.aksrt-header-payvip__exp { + display: inline; + margin: 0; + padding: 0; + border: 0; + background: none !important; + box-shadow: none; + color: inherit; + font-size: .85em; + font-weight: 400; + line-height: 1.3; + opacity: .9; + white-space: nowrap; +} + +/* Navigation links are globally capped at 50px. Account actions are taller + * icon tiles and must opt out or their labels overlap the following row. */ +.navbar-top .aksrt-header-userbox .header-user-href { + align-items: flex-start; + gap: 4px; +} + +.navbar-top .aksrt-header-userbox .header-user-href > a, +.aksrt-header-userbox .header-user-href > a { + display: flex; + flex: 1 1 0; + flex-direction: column; + align-items: center; + min-width: 0; + max-height: none; + padding: 10px 2px 2px; + line-height: 1.35; +} + +.aksrt-header-userbox .header-user-href > a:hover { + opacity: .75; +} + +.aksrt-header-userbox .header-user-href .toggle-radius { + flex: 0 0 36px; + width: 36px; + height: 36px; +} + +.aksrt-header-userbox .header-user-href > a > div:last-child { + max-width: 100%; + overflow: hidden; + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mobile-navbar .aksrt-header-userbox { + width: 100%; + max-width: 400px; + margin-right: auto; + margin-left: auto; +} + +body.dark-theme .aksrt-header-checkin { + background: rgba(255, 193, 7, .18) !important; + color: #f0c14b !important; +} + +body.dark-theme .aksrt-header-checkin > i { + color: #f0c14b !important; +} + +body.dark-theme .aksrt-header-checkin.is-done, +body.dark-theme .aksrt-header-checkin:disabled { + background: rgba(255, 255, 255, .08) !important; + color: #bbb !important; +} + +body.dark-theme .aksrt-header-wallet { + background: rgba(255, 255, 255, .06); +} + +body.dark-theme .aksrt-header-wallet__item, +body.dark-theme .aksrt-header-wallet__item b { + color: inherit; +} + +@media (max-width: 360px) { + .aksrt-header-userbox .header-user-href { + gap: 2px; + } + + .aksrt-header-userbox .header-user-href .toggle-radius { + flex-basis: 34px; + width: 34px; + height: 34px; + } +} + +/* source: assets/css/frontend/card-primitives.css */ /** * AKSRT Card Primitives — 1:1 legacy Alignment * @@ -169,10 +811,10 @@ html.dark { * ============================================================ */ .aksrt-theme-box { border: none; - border-radius: var(--radius); + border-radius: var(--radius, 8px); background: var(--surface-1); box-shadow: var(--shadow-sm); - transition: box-shadow var(--dur) var(--ease); + transition: box-shadow 0.2s ease; } .aksrt-theme-box:hover { @@ -229,22 +871,17 @@ html.dark { /* ============================================================ * legacy 兼容别名 * ============================================================ */ -.theme-box { - background: var(--surface-1); - border: none; - border-radius: var(--radius); - box-shadow: var(--shadow-sm); +/* legacy .theme-box is ONLY a spacing utility (margin-bottom: 20px). + * Background/shadow live on .main-bg / .main-shadow / .box-bg / .aksrt-panel. + * Do NOT turn every theme-box into a card — article-header also uses it. */ +.theme-box, +.mb20 { margin-bottom: 20px; - transition: box-shadow var(--dur) var(--ease); -} - -.theme-box:hover { - box-shadow: var(--shadow); } .box-bg { - background-color: var(--surface-1); - box-shadow: var(--shadow-sm); + background-color: var(--main-bg-color, var(--surface-1)); + box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); } .box-body { @@ -265,7 +902,11 @@ html.dark { } .main-shadow { - box-shadow: var(--shadow); + box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); +} + +.main-bg { + background: var(--main-bg-color, var(--surface-1)); } .muted-box { @@ -278,40 +919,55 @@ html.dark { * 暗色模式 * ============================================================ */ html.dark .aksrt-theme-box, -html.dark .theme-box, -html.dark .box-bg { - box-shadow: var(--shadow-sm); +body.dark-theme .aksrt-theme-box, +html.dark .box-bg, +body.dark-theme .box-bg { + border-color: transparent; + box-shadow: 0 0 10px var(--main-shadow, rgba(0, 0, 0, 0.2)); } html.dark .aksrt-theme-box:hover, -html.dark .theme-box:hover { - box-shadow: var(--shadow); +body.dark-theme .aksrt-theme-box:hover { + box-shadow: 0 0 10px var(--main-shadow, rgba(0, 0, 0, 0.28)); } /* ============================================================ * 卡片 hover 动效 * ============================================================ */ -.aksrt-theme-box, -.theme-box { +.aksrt-theme-box { transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease); } .aksrt-card-hover:hover, .aksrt-card-hover:focus-within { - box-shadow: var(--shadow); - transform: translateY(-2px); + box-shadow: none; + transform: none; } .aksrt-card-hover-lg:hover, .aksrt-card-hover-lg:focus-within { box-shadow: var(--shadow-lg); - transform: translateY(-4px); + transform: none; } .aksrt-noshadow:hover { box-shadow: none !important; transform: none !important; } + +/* ============================================================ + * .aksrt-panel — legacy sidebar/widget surface + * ============================================================ */ +.aksrt-panel { + clear: both; + background-color: var(--surface-1); + padding: var(--card-padding); + box-shadow: var(--shadow-sm); + border-radius: var(--radius, 8px); + margin-bottom: 20px; +} + +/* source: assets/css/frontend/overlay-primitives.css */ /** * AKSRT Overlay / Glass Primitives * @@ -344,13 +1000,13 @@ html.dark .theme-box:hover { } .site-nav--glass.is-scrolled { - box-shadow: 0 4px 10px rgba(20, 28, 48, 0.06); + box-shadow: var(--shadow-sm); border-bottom-color: var(--color-line-soft); } html.dark .site-nav--glass.is-scrolled, html.dark .site-nav--glass.is-scrolled { - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25); + box-shadow: var(--shadow-sm); } /* ============================================================ @@ -361,12 +1017,12 @@ html.dark .site-nav--glass.is-scrolled { -webkit-backdrop-filter: saturate(5) blur(20px); backdrop-filter: saturate(5) blur(20px); background: var(--blur-bg); - box-shadow: 0 -4px 5px rgba(20, 28, 48, 0.06); + box-shadow: 0 -8px 24px rgba(20, 28, 48, 0.08); } html.dark .footer-tabbar--glass, html.dark .footer-tabbar--glass { - box-shadow: 0 -4px 5px rgba(0, 0, 0, 0.25); + box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.32); } /* ============================================================ @@ -390,1275 +1046,19 @@ html.dark body.has-scrolled .site-nav--glass, html.dark body.has-scrolled .site-nav--glass { box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25); } -/* ============================================================ - * AKSRT Navigation — Editorial v3.4 - * - * - 高度 60px (--nav-h) - * - 字体使用 --font-ui (Inter / 思源黑体) - * - 主色蓝调,hover 改为 primary-soft 浅底 - * - 永远实心:白底深字 + 1px 分隔线,滚动后追加 box-shadow - * - 移动端:≤768 显示汉堡菜单 - * ============================================================ */ -.site-nav { - position: sticky; - top: 0; - z-index: 1000; - min-height: var(--nav-height, 60px); - border-bottom: 1px solid var(--color-line); - background-color: var(--blur-bg); - backdrop-filter: saturate(5) blur(20px); - -webkit-backdrop-filter: saturate(5) blur(20px); - transition: background-color 0.25s ease, - border-color 0.25s ease, - color 0.25s ease, - box-shadow 0.25s ease; -} - -.site-nav--static { - position: relative; -} - -.site-nav--transparent { - background: var(--blur-bg); - border-bottom-color: var(--color-line-soft); -} - -.site-nav--transparent.is-scrolled { - background: var(--blur-bg); -} - -.site-nav--blur { - background: var(--blur-bg); - backdrop-filter: saturate(5) blur(20px); - -webkit-backdrop-filter: saturate(5) blur(20px); - border-bottom-color: transparent; -} - -html.dark .site-nav--transparent, -html.dark .site-nav--blur, -html.dark .site-nav--transparent, -html.dark .site-nav--blur { - background: var(--blur-bg); - border-bottom-color: var(--color-line-soft); -} - -/* 滚动后追加轻阴影作为状态反馈(颜色不变) */ -.site-nav.is-scrolled { - box-shadow: var(--shadow-sm, 0 1px 6px rgba(0, 0, 0, 0.06)); -} - -/* WordPress Admin Bar 已全局禁用,无需偏移规则 */ - -/* ============================================================ - * Inner / Row layout - * ============================================================ */ -.site-nav__inner { - max-width: var(--nav-max-width, var(--container)); - margin: 0 auto; - padding: 0 15px; - display: flex; - flex-direction: column; - gap: 0; -} - -.site-nav__row { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 12px; - width: 100%; - min-height: var(--nav-height, 60px); -} - -/* ============================================================ - * Brand - * ============================================================ */ -.site-brand { - display: inline-flex; - align-items: center; - gap: 10px; - min-width: 0; - max-width: min(48vw, 360px); - padding: 7px 10px; - color: var(--color-ink); - font-size: 1.0625rem; - font-weight: 700; - letter-spacing: -0.01em; - flex-shrink: 0; - text-decoration: none; -} - -.site-brand:hover { - color: var(--color-ink); - text-decoration: none; -} - -.site-brand--has-logo { - gap: 0; -} - -.site-brand__logo { - width: 36px; - height: 36px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - font-weight: 800; - font-size: 13px; - color: var(--color-on-primary, #fff); - background: linear-gradient(135deg, var(--color-primary), var(--color-primary-active, var(--color-primary-dark))); - letter-spacing: 0.02em; - overflow: hidden; -} - -.site-brand__media { - display: block; - flex: 0 1 auto; - max-width: 100%; - padding: 0 30px; - line-height: 0; - box-sizing: border-box; - background: transparent !important; - border: 0; - border-radius: 0; - box-shadow: none; - filter: none !important; - opacity: 1; -} - -.site-brand__img { - display: block; - height: 36px; - width: auto; - max-width: 100%; - object-fit: contain; - object-position: left center; - background: transparent !important; - border-radius: 0; - box-shadow: none; - filter: none !important; - mix-blend-mode: normal !important; - opacity: 1; - transform: none !important; -} - -.site-brand__name { - font-weight: 700; - letter-spacing: -0.01em; - white-space: nowrap; -} - -/* ============================================================ - * Desktop menu - * ============================================================ */ -ul.nav-desktop { - display: none; - list-style: none; - margin: 0; - padding: 0; - align-items: center; - flex: 1 1 auto; - min-width: 0; -} - -.nav-desktop--fallback { - display: none; - align-items: center; - flex: 1 1 auto; - min-width: 0; -} - -ul.nav-desktop a, -.nav-desktop--fallback a { - color: var(--color-ink-3); - font-size: 15px; - font-weight: 500; - padding: 8px 10px; - border-radius: 4px; - transition: color var(--dur-fast, 0.15s) var(--ease), - background var(--dur-fast, 0.15s) var(--ease); -} - -/* Hover — 对齐 legacy: 仅变色,无背景 */ -ul.nav-desktop a:hover, -.nav-desktop--fallback a:hover { - color: var(--color-primary); - background: transparent; -} - -/* Active page — 对齐 legacy: 顶部彩色条指示器 */ -ul.nav-desktop .current-menu-item > a, -ul.nav-desktop .current_page_item > a { - color: var(--color-primary); - background: transparent; - position: relative; -} - -ul.nav-desktop .current-menu-item > a::before, -ul.nav-desktop .current_page_item > a::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 3px; - background: var(--color-primary); - border-radius: 0 0 2px 2px; -} - -/* ============================================================ - * Desktop submenu (dropdown) - * ============================================================ */ -ul.nav-desktop .menu-item { - position: relative; -} - -ul.nav-desktop .sub-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 998; - min-width: 200px; - list-style: none; - margin: 4px 0 0; - padding: 8px; - background: var(--main-bg-color, #fff); - border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); - border-radius: var(--main-radius, 8px); - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); - opacity: 0; - visibility: hidden; - transform: translateY(6px); - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; -} - -ul.nav-desktop .menu-item:hover > .sub-menu { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -ul.nav-desktop .sub-menu .menu-item > a { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 15px; - font-size: 14px; - border-radius: 4px; - white-space: nowrap; -} - -ul.nav-desktop .sub-menu .menu-item > a:hover { - background: var(--color-primary-soft, rgba(24, 144, 255, 0.08)); - color: var(--color-primary); -} - -/* Nested submenus fly out to the right */ -ul.nav-desktop .sub-menu .sub-menu { - top: -6px; - left: 100%; - margin: 0 0 0 4px; -} - -/* Dark mode submenu */ -html.dark ul.nav-desktop .sub-menu, -html.dark ul.nav-desktop .sub-menu { - background: var(--surface-page); - border-color: var(--color-line, #2d2d3f); -} - -/* ============================================================ - * Mega Menu (图文超级菜单) - * ============================================================ */ - -/* Mega Menu 容器:全宽定位 */ -ul.nav-desktop .mega-menu-item { - position: static; -} - -ul.nav-desktop .mega-menu-item > .mega-menu__panel { - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - width: 100%; - max-width: var(--container); - min-width: auto; - padding: 16px; - margin-top: 4px; -} - -/* 列布局 */ -.mega-menu__columns-2 { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 8px; -} - -.mega-menu__columns-3 { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; -} - -.mega-menu__columns-4 { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 8px; -} - -.mega-menu__columns-5 { - display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 8px; -} - -/* Mega Menu 子项(卡片样式) */ -.mega-menu__panel > .menu-item { - border-radius: 8px; - transition: background 0.15s; -} - -.mega-menu__panel > .menu-item:hover { - background: var(--surface-2, #F4F5F7); -} - -.mega-menu__panel > .menu-item > a { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 12px; - font-size: 14px; - border-radius: 8px; - white-space: normal; - line-height: 1.4; -} - -.mega-menu__panel > .menu-item > a i { - width: 20px; - text-align: center; - color: var(--color-primary, var(--color-primary)); - flex-shrink: 0; -} - -/* 描述文字 */ -.mega-menu__item-desc { - display: block; - font-size: 12px; - color: var(--color-ink-4, #8A8F98); - margin-top: 2px; - line-height: 1.3; -} - -/* Mega Menu 标题分组 */ -.mega-menu__panel .mega-menu__group-title { - grid-column: 1 / -1; - padding: 8px 12px 4px; - font-size: 12px; - font-weight: 600; - color: var(--color-ink-3); - text-transform: uppercase; - letter-spacing: 0.05em; -} - -/* Mega Menu 底部区域(可放图片/CTA) */ -.mega-menu__panel .mega-menu__footer { - grid-column: 1 / -1; - padding: 12px 0 0; - margin-top: 8px; - border-top: 1px solid var(--color-line); -} - -/* Dark mode Mega Menu */ -html.dark ul.nav-desktop .mega-menu-item > .mega-menu__panel, -html.dark ul.nav-desktop .mega-menu-item > .mega-menu__panel { - background: var(--surface-page); - border-color: var(--color-line, #2d2d3f); -} - -html.dark .mega-menu__panel > .menu-item:hover, -html.dark .mega-menu__panel > .menu-item:hover { - background: var(--surface-2); -} - -/* Responsive Mega Menu */ -@media (max-width: 767px) { - ul.nav-desktop .mega-menu-item > .mega-menu__panel { - position: static; - transform: none; - width: 100%; - max-width: none; - } - - .mega-menu__columns-2, - .mega-menu__columns-3, - .mega-menu__columns-4, - .mega-menu__columns-5 { - grid-template-columns: 1fr; - } -} - -/* ============================================================ - * Right actions (icons + auth) - * ============================================================ */ -.nav-actions { - display: flex; - align-items: center; - gap: 4px; - flex-shrink: 0; - margin-left: auto; -} - -.nav-settings-link, -.nav-publish-btn { - flex-shrink: 0; -} - -.nav-settings-link svg { - width: 18px; - height: 18px; -} - -.nav-publish-btn { - display: inline-flex; - align-items: center; - gap: 8px; - height: 36px; - padding: 0 14px; - font-size: 0.875rem; - font-weight: 600; -} - -.nav-publish-btn i { - font-size: 0.8125rem; -} - -.theme-toggle.is-animating svg { - transform: rotate(360deg); -} - -/* ============================================================ - * Auth buttons (in nav, when logged out) - * ============================================================ */ -.nav-auth { - display: none; - align-items: center; - gap: 8px; -} - -/* btn-outline / btn-primary 复用 core/buttons.css 的定义, - * 这里仅做导航专用调整(高度对齐、字体) */ -.nav-auth .btn-outline, -.nav-auth .btn-primary { - height: 36px; - font-size: 0.875rem; - padding: 0 16px; -} - -/* Mobile toggle */ -.nav-toggle { - display: flex; -} - -@media (max-width: 991px) { - .site-brand { - max-width: min(46vw, 300px); - } - - .site-brand__media { - padding: 0 8px; - } - - .nav-publish-btn { - display: none; - } -} - -/* Desktop search icon: hide on mobile (use drawer search instead) */ -@media (max-width: 767px) { - .site-brand { - max-width: min(62vw, 260px); - padding: 7px 0; - } - - .site-brand__media { - padding: 0; - } - - #search-toggle { - display: none; - } - /* Search panel only makes sense alongside the desktop search icon */ - .nav-search-panel { - display: none !important; - } -} - -@media (min-width: 768px) { - ul.nav-desktop, - .nav-desktop--fallback { - display: flex; - } - - .nav-auth { - display: flex; - } - - .nav-toggle { - display: none; - } -} - -.nav-mobile-strip { - display: none; - width: calc(100% + 8px); - margin: 0 -4px; - padding: 0 0 12px; -} - -.nav-mobile-strip__menu, -.nav-mobile-strip__menu ul { - display: flex; - align-items: center; - gap: 8px; - overflow-x: auto; - margin: 0; - padding: 0; - list-style: none; - scrollbar-width: none; - -ms-overflow-style: none; -} - -.nav-mobile-strip__menu::-webkit-scrollbar, -.nav-mobile-strip__menu ul::-webkit-scrollbar { - display: none; -} - -.nav-mobile-strip__menu a { - display: inline-flex; - align-items: center; - min-height: 34px; - padding: 0 12px; - border-radius: var(--radius-pill, 999px); - background: var(--surface-2); - color: var(--color-ink-2); - white-space: nowrap; - text-decoration: none; - font-size: 0.875rem; - font-weight: 500; -} - -.nav-mobile-strip__menu .current-menu-item > a, -.nav-mobile-strip__menu .current_page_item > a { - background: var(--color-primary); - color: var(--color-on-primary, #fff); -} - -@media (max-width: 767px) { - .nav-mobile-strip { - display: block; - } -} - -/* ============================================================ - * User avatar + dropdown - * ============================================================ */ -.nav-user { - position: relative; -} - -.nav-user__toggle { - width: 40px; - height: 40px; - border-radius: var(--radius-avatar); - padding: 0; - border: 2px solid var(--color-line); - cursor: pointer; - overflow: hidden; - background: var(--surface-2); - transition: border-color var(--dur-fast, 0.15s) var(--ease); -} - -.nav-user__toggle:hover { - border-color: var(--color-primary); -} - -.nav-user__toggle img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.user-dropdown { - position: absolute; - top: calc(100% + 8px); - right: 0; - width: 260px; - max-width: calc(100vw - 32px); - padding: 12px 0; - background: var(--surface-1); - border: 1px solid var(--color-line); - border-radius: var(--radius-lg, 8px); - box-shadow: var(--shadow-lg); - z-index: 1100; - opacity: 0; - visibility: hidden; - transform: translateY(-6px); - transition: opacity 0.2s, transform 0.2s, visibility 0.2s; -} - -.user-dropdown.is-open { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.user-dropdown__profile { - display: flex; - align-items: center; - gap: 12px; - padding: 8px 16px 14px; -} - -.user-dropdown__profile img { - width: 44px; - height: 44px; - border-radius: var(--radius-avatar); - object-fit: cover; - flex-shrink: 0; -} - -.user-dropdown__name { - font-weight: 700; - font-size: 0.9375rem; - color: var(--color-ink); -} - -.user-dropdown__email { - font-size: 0.75rem; - color: var(--color-ink-4); - word-break: break-all; - margin-top: 2px; -} - -.user-dropdown__sep { - height: 1px; - background: var(--color-line); - margin: 6px 0; -} - -.user-dropdown a, -.user-dropdown button { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 0 16px; - height: 44px; - border: none; - background: none; - font-size: 0.875rem; - color: var(--color-ink); - text-align: left; - cursor: pointer; - transition: background var(--dur-fast, 0.15s) var(--ease), - color var(--dur-fast, 0.15s) var(--ease); -} - -.user-dropdown a:hover, -.user-dropdown button:hover { - background: var(--surface-3); - color: var(--color-primary-active); -} - -/* dropdown 项内的 SVG 图标 */ -.user-dropdown__item svg { - width: 16px; - height: 16px; - flex-shrink: 0; - opacity: 0.85; -} - -.user-dropdown__item span:first-of-type:not(.user-dropdown__pill) { - flex: 1; - min-width: 0; -} - -/* 未读消息数 badge */ -.user-dropdown__badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 18px; - height: 18px; - padding: 0 5px; - margin-left: auto; - font-size: 11px; - font-weight: 600; - color: #fff; - background: var(--color-error, #ef4444); - border-radius: 9px; - line-height: 1; -} - -.user-dropdown__credits { - display: flex; - align-items: center; - gap: 10px; - padding: 0 16px; - height: 44px; - font-size: 0.875rem; - color: var(--color-ink-2); -} - -.user-dropdown__credits i { - color: var(--color-warning); - flex-shrink: 0; -} - -.user-dropdown__credits-label { - flex: 1; -} - -.user-dropdown__credits-value { - font-weight: 600; - color: var(--color-primary); -} - -.user-dropdown__logout { - color: var(--color-error) !important; -} - -.user-dropdown__logout svg { - color: var(--color-error); -} - -.user-dropdown__logout:hover { - background: rgba(239, 68, 68, 0.08) !important; - color: var(--color-error) !important; -} - -.user-dropdown .badge-dot { - margin-left: auto; - min-width: 8px; - height: 8px; - border-radius: var(--radius-pill); - background: var(--color-error); -} - -/* ============================================================ - * Search panel - * ============================================================ */ -.nav-search-panel { - display: none; - padding: 12px 0 16px; - border-top: 1px solid var(--color-line); -} - -.nav-search-panel.is-open { - display: block; -} - -.nav-searchbox { - margin: 0; -} - -.nav-searchbox__field { - display: flex; - align-items: center; - gap: 10px; - min-height: 42px; - padding: 0 6px 0 14px; - border: 1px solid var(--color-line); - border-radius: var(--radius-pill, 999px); - background: var(--surface-3); - box-sizing: border-box; - transition: border-color var(--dur-fast, 0.15s) var(--ease), box-shadow var(--dur-fast, 0.15s) var(--ease), background var(--dur-fast, 0.15s) var(--ease); -} - -.nav-searchbox__icon { - order: 1; - flex: 0 0 16px; - width: 16px; - height: 16px; - color: var(--color-ink-4); - pointer-events: none; -} - -.nav-searchbox__input { - order: 2; - flex: 1; - min-width: 0; - width: auto; - height: 40px; - padding: 0; - border: 0; - background: transparent; - color: var(--color-ink); - font-size: 0.9rem; - line-height: 1.2; - box-sizing: border-box; -} - -.nav-searchbox__input:focus { - outline: none; -} - -.nav-searchbox__input::placeholder { - color: var(--color-ink-4); -} - -.nav-searchbox__submit { - order: 3; - flex: 0 0 auto; - height: 30px; - min-width: 64px; - padding: 0 14px; - border: 0; - border-radius: var(--radius-pill, 999px); - background: var(--color-primary); - color: #fff; - font-size: 0.8125rem; - font-weight: 600; - line-height: 1; - white-space: nowrap; - cursor: pointer; -} - -.nav-searchbox__field:focus-within { - border-color: var(--color-primary); - box-shadow: 0 0 0 3px var(--color-primary-soft); - background: var(--surface-1); -} - -.nav-searchbox__submit:hover { - background: var(--color-primary-active); -} - -/* ============================================================ - * Mobile drawer - * ============================================================ */ -.nav-drawer-backdrop { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.45); - z-index: 1200; - opacity: 0; - visibility: hidden; - transition: opacity 0.3s ease, visibility 0.3s; -} - -.nav-drawer-backdrop.is-open { - opacity: 1; - visibility: visible; -} - -.nav-drawer { - position: fixed; - top: 0; - right: 0; - width: min(100vw, 360px); - height: 100vh; - max-height: 100dvh; - background: var(--surface-page); - z-index: 1210; - box-shadow: var(--shadow-drawer); - transform: translateX(100%); - transition: transform 0.32s var(--ease-out, ease); - display: flex; - flex-direction: column; - overflow: auto; - pointer-events: none; - visibility: hidden; -} - -.nav-drawer.is-open { - transform: translateX(0); - pointer-events: auto; - visibility: visible; -} - -.nav-drawer__head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 16px 20px; - border-bottom: 1px solid var(--color-line); - font-weight: 700; - font-size: 1rem; -} - -.site-brand--drawer { - max-width: calc(100% - 52px); - padding: 7px 0; -} - -.site-brand--drawer .site-brand__media { - padding: 0; -} - -.nav-drawer__search { - padding: 16px 20px 0; -} - -.nav-drawer__search .nav-searchbox__field { - min-height: 40px; -} - -.nav-drawer__search .nav-searchbox__input { - height: 38px; -} - -.nav-drawer__search .nav-searchbox__submit { - height: 28px; - min-width: 58px; - padding: 0 12px; - font-size: 0.75rem; -} - -.nav-drawer__menu { - flex: 1; - padding: 16px 20px 32px; -} - -.nav-drawer__menu a { - display: block; - padding: 14px 0; - font-size: 1rem; - font-weight: 500; - color: var(--color-ink); - border-bottom: 1px solid var(--color-line-soft); -} - -.nav-drawer__menu a:hover { - color: var(--color-primary-active); -} - -.nav-drawer__menu ul { - list-style: none; - margin: 0; - padding: 0; -} - -.nav-drawer__auth { - padding: 20px; - display: flex; - flex-direction: column; - gap: 12px; - border-top: 1px solid var(--color-line); -} - -.nav-drawer__auth .btn-outline, -.nav-drawer__auth .btn-primary { - width: 100%; - height: 44px; -} - -.nav-drawer__publish { - padding: 0 20px 20px; -} - -.nav-drawer__publish .btn-primary { - width: 100%; - min-height: 44px; - justify-content: center; - gap: 8px; -} - -/* ============================================================ - * Menu Decoration — Icons, Badges, Hover Effects - * ============================================================ */ - -/* Menu item icon */ -.aksrt-nav__icon { - font-size: 0.85em; - width: 1.2em; - text-align: center; - margin-right: 4px; - opacity: 0.7; - transition: opacity 0.2s; -} - -.aksrt-nav__item--has-icon > a { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.aksrt-nav__item--has-icon:hover .aksrt-nav__icon { - opacity: 1; -} - -/* Menu item badge */ -.aksrt-nav__badge { - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: 600; - line-height: 1; - padding: 2px 5px; - border-radius: 3px; - margin-left: 4px; - white-space: nowrap; - vertical-align: middle; - transform: translateY(-1px); -} - -.aksrt-nav__badge--new { - color: #fff; - background: var(--color-error); -} - -.aksrt-nav__badge--hot { - color: #fff; - background: var(--color-warning); -} - -.aksrt-nav__badge--info { - color: #fff; - background: var(--color-info); -} - -.aksrt-nav__badge--success { - color: #fff; - background: var(--color-success); -} - -.aksrt-nav__badge--vip { - color: #fff; - background: linear-gradient(135deg, var(--color-warning), var(--color-error)); -} - -/* Hover effects */ -.aksrt-nav__item--hover-underline > a::after { - content: ''; - position: absolute; - bottom: 0; - left: 50%; - width: 0; - height: 2px; - background: var(--color-primary); - transition: width 0.2s ease, left 0.2s ease; - border-radius: 1px; -} - -.aksrt-nav__item--hover-underline:hover > a::after { - width: 60%; - left: 20%; -} - -.aksrt-nav__item--hover-background > a { - transition: background-color 0.2s; - border-radius: 4px; -} - -.aksrt-nav__item--hover-background:hover > a { - background-color: var(--color-primary-soft, rgba(24, 144, 255, 0.08)); -} - -.aksrt-nav__item--hover-color > a { - transition: color 0.2s; -} - -.aksrt-nav__item--hover-color:hover > a { - color: var(--color-primary); -} - -/* Mega submenu */ -.aksrt-nav__mega { - position: absolute; - top: 100%; - left: 0; - z-index: 1001; - background: var(--surface-1); - border: 1px solid var(--color-line); - border-radius: var(--radius); - box-shadow: var(--shadow-lg); - padding: 16px; - opacity: 0; - visibility: hidden; - transform: translateY(8px); - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; -} - -.aksrt-nav__item:hover > .aksrt-nav__mega { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.aksrt-nav__mega--columns { - display: grid; - gap: 16px; -} - -/* Cards submenu */ -.aksrt-nav__cards { - display: grid; - gap: 12px; - padding: 8px; -} - -.aksrt-nav__card { - display: flex; - align-items: center; - gap: 12px; - padding: 10px 12px; - border-radius: 6px; - text-decoration: none; - color: inherit; - transition: background 0.15s; -} - -.aksrt-nav__card:hover { - background: var(--color-primary-soft, rgba(24, 144, 255, 0.06)); -} - -.aksrt-nav__card-icon { - width: 36px; - height: 36px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - background: var(--color-primary-soft, #e6f4ff); - color: var(--color-primary); - font-size: 16px; - flex-shrink: 0; -} - -.aksrt-nav__card-text { - flex: 1; - min-width: 0; -} - -.aksrt-nav__card-title { - font-size: 13px; - font-weight: 600; - color: var(--color-ink); - margin: 0; -} - -.aksrt-nav__card-desc { - font-size: 11px; - color: var(--color-ink-4); - margin: 2px 0 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* ============================================================ - * 导航溢出「更多」下拉 - * ============================================================ */ -.aksrt-nav-more { - position: relative; -} - -.aksrt-nav-more__btn { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 6px 12px; - border: none; - background: none; - color: var(--color-ink-2); - font-size: 14px; - font-weight: 500; - cursor: pointer; - border-radius: var(--radius, 8px); - transition: background 0.15s, color 0.15s; -} - -.aksrt-nav-more__btn:hover { - background: var(--color-primary-soft, rgba(240, 68, 148, 0.08)); - color: var(--color-primary); -} - -.aksrt-nav-more__dropdown { - display: none; - position: absolute; - top: 100%; - right: 0; - min-width: 160px; - padding: 6px 0; - background: var(--surface-1, #fff); - border: 1px solid var(--color-line, rgba(50, 50, 50, 0.06)); - border-radius: var(--radius, 8px); - box-shadow: var(--shadow-lg, 0 0 10px rgba(116, 116, 116, 0.12)); - z-index: var(--z-dropdown, 100); - list-style: none; - margin: 4px 0 0; -} - -.aksrt-nav-more__dropdown.is-open { - display: block; -} - -.aksrt-nav-more__dropdown li { - display: block; -} - -.aksrt-nav-more__dropdown li a, -.aksrt-nav-more__dropdown li button { - display: block; - width: 100%; - padding: 8px 16px; - border: none; - background: none; - color: var(--color-ink-2); - font-size: 14px; - text-align: left; - text-decoration: none; - cursor: pointer; - transition: background 0.15s, color 0.15s; -} - -.aksrt-nav-more__dropdown li a:hover, -.aksrt-nav-more__dropdown li button:hover { - background: var(--color-primary-soft, rgba(240, 68, 148, 0.08)); - color: var(--color-primary); -} -/* ========== 面包屑 — 对齐 legacy .breadcrumb ========== */ -.breadcrumbs { - font-size: 13px; - color: var(--muted-2-color, #999); - margin-bottom: 12px; - opacity: 0.8; -} - -.breadcrumbs a { - color: var(--muted-2-color, #999); -} - -.breadcrumbs a:hover { - color: var(--focus-color, #f04494); -} - -.breadcrumbs__sep { - margin: 0 0 0 6px; - opacity: 0.6; -} - -@media (max-width: 767px) { - .breadcrumbs { - font-size: 12px; - } -} - -/* 封面内面包屑 */ -.single-cover-con .breadcrumbs { - font-size: 13px; - margin: 5px 0 0 -6px; - opacity: unset; -} +/* source: assets/css/frontend/layout.css */ +/* Breadcrumb chrome lives in breadcrumb.css (single source). */ /* ========== 主布局 ========== */ +.content-main { + width: 100%; +} + +.content-main--single { + max-width: 100%; +} + .content-shell { width: 100%; margin: 0 auto; @@ -1700,126 +1100,177 @@ html.dark .mega-menu__panel > .menu-item:hover { font-weight: 600; } -.content-layout { - display: grid; - grid-template-columns: 1fr; - gap: 20px; - align-items: start; +/* ============================================================ + * Site shell — site-layout-1/2/3 dual-column + * DOM: main.container > .content-wrap + .sidebar (siblings) + * body.site-layout-1 = no sidebar + * body.site-layout-2 = sidebar right + * body.site-layout-3 = sidebar left + * + * This intentionally uses legacy's float/negative-margin ownership model. + * Below 992px the sidebar is hidden and the content margins collapse. + * ============================================================ */ + +main.container, +main.content-shell, +.site-main.container { + position: relative; } -@media (min-width: 992px) { - .content-layout { - grid-template-columns: minmax(0, 1fr) 300px; - gap: 20px; - } +/* content-shell is a padding shell for non-dual pages; when it also hosts + * the float dual-column, kill horizontal padding so negative-margin math matches legacy. */ +main.container.content-shell, +main.content-shell.container, +.site-main.container.content-shell { + padding-left: 0; + padding-right: 0; + max-width: var(--container, var(--mian-max-width, 1200px)); + width: 100%; + box-sizing: border-box; +} + +main.container::after, +main.content-shell::after, +.site-main.container::after { + content: ""; + display: table; + clear: both; +} + +/* Float/negative-margin compatibility fallback. + * + * Critical: .content-layout must NOT set width:100%. Under box-sizing:border-box + * a 100% width + margin-right gutter keeps the border-box at full container + * width, so the gutter never shrinks the content box and .sidebar (margin-left:-311px) + * sits under the main column (overlap). legacy only sets margin on .content-layout; + * width stays auto so margin carves out the column. */ +.content-wrap { + float: left; + width: 100%; + min-width: 0; + display: block; + box-sizing: border-box; +} + +.content-layout { + display: block; + /* width:auto — never 100%; margin must shrink the used width */ + width: auto; + min-width: 0; + max-width: none; + margin-right: 0; + margin-left: 0; + box-sizing: border-box; } .content-layout__primary { min-width: 0; + max-width: 100%; +} + +/* Base sidebar column (legacy .sidebar defaults) — layout variants override below */ +.sidebar { + float: left; + width: 311px; /* legacy hard 311px; --sidebar-width kept in sync via runtime vars */ + width: var(--sidebar-width, 311px); + min-width: 0; + position: relative; + box-sizing: border-box; + display: block; } .content-layout__aside { min-width: 0; } +/* No sidebar */ +body.site-layout-1 .sidebar, +.site-layout-1 .sidebar { + display: none; +} + +body.site-layout-1 .content-layout, +body.site-layout-1 .content-layout--no-sidebar, +.site-layout-1 .content-layout { + margin-left: 0; + margin-right: 0; +} + +/* Compat: templates may still stamp content-layout--no-sidebar */ +.content-layout--no-sidebar { + margin-left: 0 !important; + margin-right: 0 !important; +} + +/* Sidebar RIGHT (layout 2) — legacy: margin-right: calc(311px + 15px) only */ +body.site-layout-2 .content-layout, +.site-layout-2 .content-layout { + margin-right: calc(311px + 15px); + margin-right: calc(var(--sidebar-width, 311px) + 15px); + margin-left: 0; + width: auto; +} + +body.site-layout-2 .sidebar, +.site-layout-2 .sidebar { + float: left; + width: 311px; + width: var(--sidebar-width, 311px); + margin-left: -311px; + margin-left: calc(0px - var(--sidebar-width, 311px)); + position: unset; +} + +/* Sidebar LEFT (layout 3) */ +body.site-layout-3 .content-layout, +.site-layout-3 .content-layout { + margin-left: calc(311px + 15px); + margin-left: calc(var(--sidebar-width, 311px) + 15px); + margin-right: unset; + width: auto; +} + +body.site-layout-3 .sidebar, +.site-layout-3 .sidebar { + float: left; + width: 311px; + width: var(--sidebar-width, 311px); + margin-left: -100%; + position: unset; +} + +/* Sticky widgets inside sidebar */ @media (min-width: 992px) { + .sidebar .content-layout__aside, .content-layout__aside { position: sticky; top: calc(var(--nav-height, 60px) + 16px); - align-self: start; } - /* WordPress Admin Bar 已全局禁用,无需偏移规则 */ -} - -@media (min-width: 992px) { .has-static-nav .content-layout__aside { top: 16px; } } -.content-layout--no-sidebar { - grid-template-columns: 1fr; -} - -.content-layout--no-sidebar .content-layout__primary { - width: 100%; - max-width: var(--content-no-sidebar-max, 1080px); - margin: 0 auto; -} - -/* 三栏布局变体:侧边栏在左侧 */ -@media (min-width: 992px) { - .content-layout--sidebar-left { - grid-template-columns: 300px minmax(0, 1fr); +/* Mobile collapse (legacy max-width: 991px) */ +@media (max-width: 991.98px) { + body.site-layout-2 .content-layout, + body.site-layout-3 .content-layout, + .site-layout-2 .content-layout, + .site-layout-3 .content-layout { + margin-left: auto; + margin-right: auto; } - .content-layout--sidebar-left .content-layout__aside { - order: -1; + body.site-layout-2 .sidebar, + body.site-layout-3 .sidebar, + .site-layout-2 .sidebar, + .site-layout-3 .sidebar, + .sidebar { + display: none; } } -/* ========== 分页 ========== */ -.pagination, -.navigation.pagination .nav-links { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - gap: 8px; - margin: 40px 0 8px; -} - -.pagination a, -.pagination span, -.navigation.pagination a, -.navigation.pagination span { - min-width: 40px; - height: 40px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-btn); - border: 1px solid var(--color-line); - color: var(--color-ink); - font-size: 14px; - font-weight: 500; - transition: border-color 0.2s, color 0.2s, background 0.2s; -} - -.pagination a:hover { - border-color: var(--color-primary); - color: var(--color-primary); -} - -.pagination .current, -.navigation.pagination span.current { - background: var(--color-primary); - border-color: var(--color-primary); - color: #fff; -} - -.pagination .dots, -.navigation.pagination .dots { - border: none; - color: var(--color-ink-4); - min-width: auto; - padding: 0 4px; -} - -.pagination .prev, -.pagination .next, -.navigation.pagination a.prev, -.navigation.pagination a.next { - min-width: 44px; -} - -.pagination .disabled, -.navigation.pagination span.disabled { - opacity: 0.4; - pointer-events: none; -} - /* ========== 页面标题区 ========== */ .page-header { margin-bottom: 28px; @@ -1878,6 +1329,63 @@ html.dark .mega-menu__panel > .menu-item:hover { display: none; } +/* Card style: accent top border, slightly more padding */ +.term-page-header--card { + border-top: 3px solid var(--color-primary, #f04494); +} + +.term-page-header--card .term-page-header__body { + padding: 28px 24px; +} + +/* Cover style: full-width hero with background image */ +.term-page-header--cover { + border: none; + border-radius: var(--radius-lg); + background: transparent; + box-shadow: none; + min-height: 220px; + display: flex; + flex-direction: column; +} + +.term-page-header__cover-bg { + position: absolute; + inset: 0; + background-position: center; + background-size: cover; + border-radius: var(--radius-lg); +} + +.term-page-header__cover-overlay { + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.25) 100%); + border-radius: var(--radius-lg); +} + +.term-page-header--cover .term-page-header__body { + flex: 1; + align-items: flex-end; + z-index: 2; +} + +.term-page-header__body--overlay .entry-title, +.term-page-header__body--overlay .term-page-header__desc, +.term-page-header__body--overlay .term-page-header__eyebrow { + color: #fff; +} + +.term-page-header__body--overlay .term-page-header__eyebrow { + color: rgba(255, 255, 255, 0.8); +} + +@media (max-width: 767px) { + .term-page-header--cover { + min-height: 160px; + } +} + .aksrt-admin-quicklink { display: inline-flex; align-items: center; @@ -1958,6 +1466,104 @@ html.dark .mega-menu__panel > .menu-item:hover { font-weight: 600; } +.archive-filter-bar__sort { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; +} + +.archive-filter-bar__main--tags { + border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06)); + padding-top: 10px; + margin-top: 10px; +} + +/* §3.4 custom_filter dimension rows */ +.archive-filter-bar__main--custom { + border-top: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.06)); + padding-top: 10px; + margin-top: 10px; +} + + +.content-stream { + display: grid; + gap: 20px; +} + +.content-main--archive, +.content-main--tag, +.content-main--category, +.content-main--search, +.content-main--single { + display: grid; + gap: 20px; +} + +.content-stream__list { + width: 100%; +} + +.content-stream__footer { + margin-top: 4px; +} + +.content-stream__footer > .content-stream__footer-nav, +.archive__ajax-wrap > .content-stream__footer-nav { + margin-top: 0; +} + +.content-stream__empty { + padding: 28px 24px; + border-radius: var(--radius-lg); + background: var(--surface-1); + border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.08)); +} + +.article-shell { + overflow: hidden; +} + +/* AJAX 筛选加载状态 */ +.archive__ajax-wrap { + position: relative; + transition: opacity 0.2s ease; +} + +.archive__ajax-wrap.is-loading { + opacity: 0.5; + pointer-events: none; +} + +.archive__ajax-loading { + display: none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + align-items: center; + justify-content: center; + z-index: 5; + background: rgba(255, 255, 255, 0.6); + backdrop-filter: blur(1px); +} + +.archive__ajax-loading.is-loading { + display: flex; +} + +.archive__ajax-spinner { + display: inline-block; + width: 28px; + height: 28px; + border: 3px solid var(--surface-3, #e8e8e8); + border-top-color: var(--color-primary, #f04494); + border-radius: 50%; + animation: aksrt-spin 0.6s linear infinite; +} + @media (max-width: 767px) { .term-page-header__body { flex-direction: column; @@ -2032,38 +1638,10 @@ html.dark .mega-menu__panel > .menu-item:hover { } } -/* CMS 模块在侧栏布局中的适配 */ .content-layout__primary--cms { min-width: 0; } -.content-layout__primary--cms .home-modules { - gap: var(--space-7, 32px); -} - -.content-layout__primary--cms .home-modules > .home-module { - max-width: 100%; - padding-left: 0; - padding-right: 0; -} - -/* ============================================================ - * Phase 2 — 背景遮罩层级修正 - * body.aksrt-bg-overlay 的 ::before 需要 z-index: -1 让内容在其上方 - * ============================================================ */ -body.aksrt-bg-overlay { - position: relative; -} - -body.aksrt-bg-overlay::before { - z-index: -1; -} - -body.aksrt-bg-overlay .site-wrapper { - position: relative; - z-index: 0; -} - /* ============================================================ * Phase 2 — 侧栏 widget 内链接增强 * ============================================================ */ @@ -2085,175 +1663,26 @@ body.aksrt-bg-overlay .site-wrapper { .comments-card { overflow: hidden; } + +/* source: assets/css/frontend/footer.css */ /* ============================================================ - * AKSRT Footer — v4.0 legacy-Inspired - * - * 包含: - * - 页脚主体(Bootstrap Grid) - * - 浮动右侧按钮组 - * - 移动端底部 Tab 栏 + * AKSRT Footer utilities — float buttons start + device helpers + * Main template_1 shell: shell.css + * Tabbar / float details continue below. * ============================================================ */ -/* ============================================================ - * 页脚主体 - * ============================================================ */ -.site-footer { - margin-top: auto; - background: var(--surface-1); - border-top: 1px solid var(--color-line); - padding: 30px 0 0; +:root { + --aksrt-footer-tabbar-height: 49px; } -.site-footer__inner { - max-width: var(--container); - margin: 0 auto; - padding: 0 16px; +/* Device visibility utility classes */ +@media (max-width: 767px) { + .hide-on-mobile { display: none !important; } + .hidden-xs { display: none !important; } } - @media (min-width: 768px) { - .site-footer__inner { - padding: 0 24px; - } -} - -.site-footer__grid { - padding-bottom: 32px; -} - -.site-footer__brand { - margin-bottom: 24px; -} - -.site-footer__title { - font-size: 1rem; - font-weight: 700; - margin: 0 0 12px; - color: var(--color-ink); -} - -.site-footer__desc { - font-size: 0.875rem; - color: var(--color-ink-4); - line-height: 1.6; - margin: 0 0 16px; - max-width: 360px; -} - -.site-footer__col { - margin-bottom: 24px; -} - -.site-footer__col ul { - list-style: none; - margin: 0; - padding: 0; -} - -.site-footer__col li { - margin-bottom: 8px; -} - -.site-footer__col a { - font-size: 0.875rem; - color: var(--color-ink-3); - transition: color 0.15s ease; -} - -.site-footer__col a:hover { - color: var(--color-primary); -} - -/* 社交链接行 */ -.social-row { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-bottom: 16px; -} - -.social-row a { - display: inline-flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - border-radius: 50%; - background: var(--surface-3); - color: var(--color-ink-3); - transition: background 0.2s, color 0.2s, transform 0.2s; -} - -.social-row a:hover { - background: var(--color-primary); - color: #fff; - transform: translateY(-2px); -} - -.social-row .social-icon-link { - background: var(--surface-3); - color: var(--color-ink-3); -} - -.social-row .social-icon-link:hover { - background: var(--color-primary); - color: #fff; -} - -/* 友情链接 */ -.site-footer__friends { - margin-top: 16px; -} - -.site-footer__friends-title { - font-size: 0.8125rem; - font-weight: 600; - color: var(--color-ink-4); - margin: 0 0 8px; -} - -.site-footer__friends-list { - display: flex; - flex-wrap: wrap; - gap: 6px 12px; -} - -.site-footer__friends-list a { - font-size: 0.8125rem; - color: var(--color-ink-4); -} - -.site-footer__friends-list a:hover { - color: var(--color-primary); -} - -/* 底部栏 */ -.site-footer__bottom { - border-top: 1px solid var(--color-line); - padding: 20px 0; - text-align: center; -} - -.site-footer__filings { - display: flex; - justify-content: center; - gap: 16px; - margin-bottom: 8px; - flex-wrap: wrap; -} - -.site-footer__filings a { - font-size: 0.75rem; - color: var(--color-ink-4); -} - -.site-footer__filings a:hover { - color: var(--color-primary); -} - -.site-footer__copyright { - font-size: 0.75rem; - color: var(--color-ink-4); - margin: 0; + .hide-on-desktop { display: none !important; } + .hide-on-pc { display: none !important; } } /* ============================================================ @@ -2325,13 +1754,15 @@ body.aksrt-bg-overlay .site-wrapper { #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 { +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 { +html.dark #float-theme-toggle .float-btn__icon-sun, +body.dark-theme #float-theme-toggle .float-btn__icon-sun { display: none !important; } @@ -2359,20 +1790,43 @@ html.dark #float-theme-toggle .float-btn__icon-sun { left: 0; right: 0; z-index: 1035; - min-height: 49px; + min-height: var(--aksrt-footer-tabbar-height); background: var(--blur-bg); border-top: 1px solid var(--color-line-soft); padding: 0 0 env(safe-area-inset-bottom, 0px); justify-content: space-around; align-items: center; - box-shadow: var(--shadow-sm); + box-shadow: 0 -4px 5px var(--main-shadow, rgba(0, 0, 0, 0.06)); -webkit-backdrop-filter: saturate(5) blur(20px); backdrop-filter: saturate(5) blur(20px); + --tabbar-active-color: #f04494; + --tabbar-icon-size: 24px; + --tabbar-text-size: 10px; +} + +.footer-tabbar.no-border { + border-top: none; +} + +.footer-tabbar.no-shadow { + box-shadow: none; +} + +.footer-tabbar.hide-text .tabbar-item span { + display: none; +} + +.footer-tabbar.has-animation .tabbar-item { + transition: color 0.15s ease, transform 0.15s ease; +} + +.footer-tabbar.has-animation .tabbar-item:active { + transform: scale(0.9); } .footer-tabbar-placeholder { display: none; - height: calc(49px + env(safe-area-inset-bottom, 0px)); + height: calc(var(--aksrt-footer-tabbar-height) + env(safe-area-inset-bottom, 0px)); } @media (max-width: 767px) { @@ -2385,8 +1839,9 @@ html.dark #float-theme-toggle .float-btn__icon-sun { } /* 有 tabbar 时页脚需要额外底部间距 */ + .footer, .site-footer { - padding-bottom: calc(49px + env(safe-area-inset-bottom, 0px)); + padding-bottom: calc(var(--aksrt-footer-tabbar-height) + env(safe-area-inset-bottom, 0px)); } } @@ -2400,7 +1855,7 @@ html.dark #float-theme-toggle .float-btn__icon-sun { gap: 1px; min-height: 49px; padding: 4px 0 3px; - color: var(--color-ink-4); + color: var(--muted-color, var(--color-ink-3)); font-size: 13px; text-decoration: none; border: none; @@ -2413,12 +1868,13 @@ html.dark #float-theme-toggle .float-btn__icon-sun { /* 对齐 legacy: 24px 图标 */ .tabbar-item i { - font-size: 24px; + font-size: var(--tabbar-icon-size, 24px); line-height: 1; } .tabbar-item span { display: block; + font-size: var(--tabbar-text-size, 10px); font-weight: 500; line-height: 1; transform: scale(0.8); @@ -2427,7 +1883,7 @@ html.dark #float-theme-toggle .float-btn__icon-sun { .tabbar-item:hover, .tabbar-item.active { - color: var(--color-primary); + color: var(--tabbar-active-color, var(--color-primary)); text-decoration: none; } @@ -2453,6 +1909,18 @@ html.dark #float-theme-toggle .float-btn__icon-sun { box-shadow: 0 2px 8px rgba(239, 68, 68, 0.20); } +.tabbar-badge--dot { + min-width: 0; + width: 8px; + height: 8px; + padding: 0; + top: 4px; + right: 10px; + font-size: 0; + line-height: 1; + box-shadow: 0 0 0 2px var(--blur-bg); +} + .tabbar-item--add { margin-top: -22px; width: 62px; @@ -2497,8 +1965,9 @@ html.dark #float-theme-toggle .float-btn__icon-sun { .tabbar-add-panel__sheet { border-radius: 20px; - background: var(--surface-page); - box-shadow: 0 18px 50px rgba(15, 23, 42, 0.18); + background: var(--surface-1); + border: none; + box-shadow: var(--shadow-drawer); overflow: hidden; } @@ -2531,6 +2000,7 @@ html.dark #float-theme-toggle .float-btn__icon-sun { flex-direction: column; align-items: center; justify-content: center; + gap: 8px; min-height: 88px; border-radius: 18px; @@ -2562,7 +2032,7 @@ html.dark #float-theme-toggle .float-btn__icon-sun { .tabbar-search-overlay__dialog { width: 100%; min-height: 100%; - background: var(--surface-page); + background: var(--surface-1); padding: 18px 16px calc(24px + env(safe-area-inset-bottom, 0px)); } @@ -2589,7 +2059,9 @@ html.dark #float-theme-toggle .float-btn__icon-sun { align-items: center; padding: 14px 14px 14px 12px; border-radius: 18px; - background: var(--surface-2); + border: none; + box-shadow: var(--shadow-sm); + background: var(--surface-4); } .tabbar-search-overlay__icon { @@ -2674,10 +2146,11 @@ html.dark #float-theme-toggle .float-btn__icon-sun { justify-content: space-between; gap: 12px; width: 100%; - border: 0; + border: none; border-radius: 16px; padding: 12px 14px; - background: var(--surface-2); + background: var(--surface-4); + box-shadow: var(--shadow-sm); color: var(--color-ink-3); text-decoration: none; text-align: left; @@ -2832,6 +2305,48 @@ html.dark .footer-tabbar { flex: 1 1 100%; } + +/* Full-viewport search panel open state (desktop + mobile) */ +.tabbar-search-overlay[hidden] { + display: none !important; +} +.tabbar-search-overlay.is-open, +.tabbar-search-overlay.show:not([hidden]) { + display: flex; +} +.tabbar-search-overlay__dialog { + max-width: 720px; + margin: 0 auto; +} +@media (min-width: 768px) { + .tabbar-search-overlay { + align-items: flex-start; + padding-top: 8vh; + background: rgba(15, 23, 42, 0.45); + } + .tabbar-search-overlay__dialog { + min-height: auto; + max-height: min(84vh, 860px); + overflow: auto; + border-radius: 20px; + box-shadow: var(--shadow-lg, 0 20px 50px rgba(15, 23, 42, 0.22)); + } + .nav-searchbox--desktop { + display: none; + } + .main-search-btn.nav-search-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + } +} +@media (max-width: 767.98px) { + .main-search-btn.nav-search-toggle { + display: none; + } +} + + .site-footer--compact .site-footer__brand .site-footer__desc, .site-footer--compact .site-footer__brand .social-row, .site-footer--compact .site-footer__brand .site-footer__friends { @@ -2882,24 +2397,312 @@ html.dark .footer-tabbar { justify-content: center; gap: 16px; } +/** + * Footer — legacy template_1 shell + * Contract: legacy/css/main.css footer block + legacy markup + */ +.footer { + color: var(--footer-color, var(--color-ink-4)); + background: var(--footer-bg, var(--surface-1)); + padding: 30px; + font-size: 13px; + margin-top: auto; +} + +.footer a:not(.but):not(.focus-color) { + color: var(--footer-color, var(--color-ink-4)); +} + +.footer a:hover { + color: var(--focus-color, var(--color-primary)); +} + +.footer .container-footer { + max-width: calc(var(--mian-max-width, var(--container, 1200px)) + 180px); + margin: 0 auto; + width: 100%; +} + +.footer .list-inline { + display: flex; + align-items: center; + gap: 20px; + list-style: none; + margin: 0 0 12px; + padding: 0; +} + +.footer .list-inline > li { + flex: 1; + margin: 5px 15px; +} + +.footer .list-inline > li + li + li { + float: right; + display: flex; + align-items: flex-end; + flex-direction: column; + gap: 10px; +} + +.footer a.footer-logo img, +.footer .footer-logo img { + height: 40px; + width: auto; + max-width: 180px; + vertical-align: middle; + display: inline-block; +} + +/* title-h-left underline bar (legacy) */ +.footer .title-h-left { + position: relative; + margin: 8px 0 10px; + padding-bottom: 8px; + font-size: 15px; + font-weight: 700; + color: var(--color-ink, #333); +} + +.footer .title-h-left:before { + transition: 0.4s; + transform-origin: left; + position: absolute; + content: ""; + width: 40px; + height: 3px; + background: var(--theme-color, var(--color-primary)); + left: 0; + bottom: 3px; + border-radius: 5px; + box-shadow: 1px 1px 3px -1px var(--theme-color, var(--color-primary)); +} + +.footer .footer-muted, +.footer .em09 { + font-size: 0.92em; + color: var(--muted-2-color, var(--color-ink-4)); + line-height: 1.7; +} + +.footer .opacity8 { + opacity: 0.8; +} + +.footer .fcode-links { + margin: 0 0 8px; + line-height: 1.9; +} + +.footer .fcode-links > a + a:before { + content: ""; + width: 4px; + height: 4px; + margin: 0 0.5em; + border-radius: 50%; + display: inline-block; + vertical-align: middle; + background: var(--main-color, var(--color-ink-2)); + opacity: 0.3; + vertical-align: 0.2em; +} + +/* Circular contact buttons — legacy .toggle-radius + .footer-contact > a */ +.footer .toggle-radius { + background: var(--this-bg, var(--main-border-color, var(--surface-2, #f0f0f0))); + width: 29px; + height: 29px; + display: inline-block; + text-align: center; + border-radius: 100%; + position: relative; + vertical-align: text-top; + line-height: 29px; + color: var(--footer-color, var(--color-ink-3)); + text-decoration: none; +} + +.footer .footer-contact { + margin-top: 10px; +} + +.footer .footer-contact > a { + font-size: 20px; + margin: 5px; + width: 35px; + height: 35px; + line-height: 35px; + display: inline-block; + text-align: center; + border-radius: 100%; + background: var(--this-bg, var(--main-border-color, var(--surface-2, #f0f0f0))); + color: var(--footer-color, var(--color-ink-3)); + text-decoration: none; + vertical-align: text-top; + position: relative; + transition: color 0.15s ease, background 0.15s ease; +} + +.footer .footer-contact > a:hover { + color: #fff; + background: var(--focus-color, var(--color-primary)); +} + +.footer .footer-contact > a i { + vertical-align: middle; +} + +.footer .footer-wechat-img { + display: none; + position: absolute; + width: 140px; + bottom: 90%; + left: -55px; + padding-bottom: 20px; + z-index: 5; +} + +.footer .hover-show { + position: relative; +} + +.footer .hover-show:hover .footer-wechat-img, +.footer .hover-show:focus .footer-wechat-img { + display: block; +} + +.footer .footer-wechat-img img { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + border-radius: 4px; + width: 100%; + height: auto; + background: #fff; +} + +.footer .footer-miniimg img { + width: 90px; + margin: 0 6px; + height: auto; + max-width: 100%; + display: block; + border-radius: 4px; +} + +.footer .footer-miniimg-2 { + justify-content: flex-end; +} + +.footer .footer-miniimg-2 img { + height: 36px; + border-radius: var(--main-radius); +} + +.footer .footer-miniimg-2 .opacity8 { + display: none; +} + +.footer .footer-conter { + margin: 8px 0 0; + line-height: 1.8; + color: var(--footer-color, var(--color-ink-4)); + font-size: 13px; +} + +.footer .mt10 { + margin-top: 10px; +} + +/* Device visibility — legacy hidden-xs */ +@media (max-width: 767px) { + .footer .hidden-xs, + .hidden-xs { + display: none !important; + } + + .footer { + padding: 10px 0; + } + + .footer .container-footer { + text-align: center; + padding: 0 16px; + } + + .footer .list-inline { + gap: 10px; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .footer .list-inline > li + li + li { + float: none; + align-items: center; + } + + .footer .list-inline > li { + margin: 12px 0; + max-width: none !important; + } + + .footer .footer-miniimg-2 { + justify-content: center; + } + + .footer .title-h-left:before { + left: 50%; + 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; +} + +/* source: assets/css/frontend/floating-buttons.css */ /* ============================================================ * Floating Buttons * ============================================================ */ .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; right: 20px; bottom: 96px; z-index: 900; display: flex; flex-direction: column; - gap: 8px; + 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); } +.float-right.no-shadow .float-btn { + box-shadow: none; +} + +.float-right.no-animation .float-btn { + transition: none; +} + +.float-right.no-animation .float-btn:hover { + transform: none; +} + .float-right.position-center { top: 50%; bottom: auto; @@ -2947,7 +2750,7 @@ html.dark .footer-tabbar { .float-right .float-btn:hover { transform: translateY(-2px); - color: var(--color-primary, var(--color-primary)); + 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; @@ -3043,6 +2846,7 @@ html.dark .footer-tabbar { font-size: 13px; line-height: 1.6; color: var(--color-ink-3); + text-align: center; } .float-btn-qrcode { @@ -3060,8 +2864,32 @@ html.dark .footer-tabbar { .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; @@ -3102,13 +2930,13 @@ html.dark .footer-tabbar { } .float-add-menu__item:hover { - background: var(--color-primary-soft, rgba(136, 171, 218, 0.1)); + background: var(--color-primary-soft, rgba(240, 68, 148, 0.1)); color: var(--color-primary, var(--color-primary)); transform: translateX(-2px); } -.float-btn-qrcode :deep(canvas), -.float-btn-qrcode :deep(img), +.float-btn-qrcode canvas, +.float-btn-qrcode img, .float-btn-qrcode svg { display: block; margin: 0 auto; @@ -3156,31 +2984,64 @@ html.dark .footer-tabbar { } } -.float-btn__icon-moon { - display: none; +/* + * 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-btn__icon-sun { - display: inline-block; +.float-right .float-btn__icon-moon { + display: inline-block !important; } -.theme-dark .float-btn__icon-moon, -html.dark .float-btn__icon-moon { - display: inline-block; +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; } -.theme-dark .float-btn__icon-sun, -html.dark .float-btn__icon-sun { - display: none; +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; } -.theme-dark .float-right, -html.dark .float-right { - --float-btn-color: var(--color-ink-4); +/* VIP float rail entry */ +.float-right .float-btn.pay-vip { + color: var(--this-color, #f5a623); } -.theme-dark .float-right .hover-show-con, -html.dark .float-right .hover-show-con { +.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); } @@ -3200,359 +3061,19 @@ html.dark .float-right .hover-show-con { .float-right .hover-show-con { max-width: min(240px, calc(100vw - 84px)); } -} -/** - * AKSRT Theme Background System - * - * 全局背景图、夜间背景图、页面背景色/背景图。 - * 所有选择器均带 aksrt- 命名空间。 - */ -/* ========== 全局背景 ========== */ -.aksrt-bg-body { - background-color: var(--aksrt-bg-color, transparent); - background-image: var(--aksrt-bg-image, none); - background-size: var(--aksrt-bg-size, cover); - background-position: var(--aksrt-bg-position, center center); - background-repeat: var(--aksrt-bg-repeat, no-repeat); - background-attachment: var(--aksrt-bg-attachment, fixed); + .float-right .float-btn.hide-on-mobile { + display: none; + } } -/* ========== 夜间模式背景 ========== */ -.aksrt-bg-dark { - background-color: var(--aksrt-bg-dark-color, transparent); - background-image: var(--aksrt-bg-dark-image, none); +@media (min-width: 768px) { + .float-right .float-btn.hide-on-pc { + display: none; + } } -/* ========== 页面级背景 ========== */ -.aksrt-bg-page { - background-color: var(--aksrt-page-bg-color, transparent); - background-image: var(--aksrt-page-bg-image, none); - background-size: cover; - background-position: center center; - background-repeat: no-repeat; -} - -/* ========== 模块级背景 ========== */ -.aksrt-bg-module { - background-color: var(--aksrt-module-bg-color, transparent); - background-image: var(--aksrt-module-bg-image, none); - background-size: cover; - background-position: center center; - background-repeat: no-repeat; -} - -/* ========== 背景遮罩层(提升文字可读性)========== */ -.aksrt-bg-overlay { - position: relative; -} - -.aksrt-bg-overlay::before { - content: ''; - position: absolute; - inset: 0; - background: var(--aksrt-bg-overlay-color, rgba(0, 0, 0, 0.3)); - pointer-events: none; - z-index: 0; -} - -.aksrt-bg-overlay > * { - position: relative; - z-index: 1; -} - -/* ========== 暗色模式自动切换 ========== */ -@media (prefers-color-scheme: dark) { - :root:not(.aksrt-light-mode) .aksrt-bg-body { - background-color: var(--aksrt-bg-dark-color, var(--aksrt-bg-color, transparent)); - background-image: var(--aksrt-bg-dark-image, var(--aksrt-bg-image, none)); - } -} - -html.aksrt-dark-mode .aksrt-bg-body { - background-color: var(--aksrt-bg-dark-color, var(--aksrt-bg-color, transparent)); - background-image: var(--aksrt-bg-dark-image, var(--aksrt-bg-image, none)); -} -/** - * AKSRT Menu Decorations - * - * 菜单图标、徽章、多列下拉、图文卡片子菜单样式。 - * 所有选择器均带 aksrt- 或 .site-nav 命名空间。 - */ - -/* ========== 菜单图标 ========== */ -.nav-desktop .menu-item > a .menu-icon, -.nav-desktop .menu-item > a > i:first-child { - display: inline-flex; - align-items: center; - margin-right: 6px; - font-size: 14px; - opacity: 0.75; - transition: opacity 0.2s ease; -} - -.nav-desktop .menu-item:hover > a .menu-icon, -.nav-desktop .menu-item:hover > a > i:first-child { - opacity: 1; -} - -/* ========== 菜单徽章 ========== */ -.nav-desktop .menu-item > a .menu-badge, -.nav-desktop .menu-item > a .aksrt-menu-badge { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 6px; - padding: 1px 6px; - font-size: 10px; - font-weight: 700; - line-height: 1.4; - letter-spacing: 0.02em; - border-radius: 8px; - white-space: nowrap; - vertical-align: middle; - transform: translateY(-1px); -} - -.aksrt-menu-badge--new { - background: var(--color-primary); - color: #fff; -} - -.aksrt-menu-badge--hot { - background: #ff4d4f; - color: #fff; -} - -.aksrt-menu-badge--info { - background: var(--color-info, #1890ff); - color: #fff; -} - -.aksrt-menu-badge--success { - background: var(--color-success, #52c41a); - color: #fff; -} - -.aksrt-menu-badge--warning { - background: var(--color-warning, #faad14); - color: #fff; -} - -/* ========== Hover 效果 ========== */ -/* 下划线效果 */ -.aksrt-menu-hover--underline .nav-desktop .menu-item > a { - position: relative; -} - -.aksrt-menu-hover--underline .nav-desktop .menu-item > a::after { - content: ''; - position: absolute; - bottom: 0; - left: 50%; - width: 0; - height: 2px; - background: var(--color-primary); - border-radius: 1px; - transform: translateX(-50%); - transition: width 0.25s ease; -} - -.aksrt-menu-hover--underline .nav-desktop .menu-item:hover > a::after, -.aksrt-menu-hover--underline .nav-desktop .menu-item.current-menu-item > a::after, -.aksrt-menu-hover--underline .nav-desktop .menu-item.current_page_item > a::after { - width: 70%; -} - -/* 背景色效果 */ -.aksrt-menu-hover--background .nav-desktop .menu-item > a { - border-radius: 6px; - transition: background-color 0.2s ease; -} - -.aksrt-menu-hover--background .nav-desktop .menu-item:hover > a { - background-color: var(--color-primary-soft, rgba(24, 144, 255, 0.08)); -} - -/* 文字变色效果 */ -.aksrt-menu-hover--color .nav-desktop .menu-item > a { - transition: color 0.2s ease; -} - -.aksrt-menu-hover--color .nav-desktop .menu-item:hover > a { - color: var(--color-primary); -} - -/* ========== 多列 Mega 下拉 ========== */ -.nav-desktop .menu-item .aksrt-mega-submenu { - position: absolute; - top: 100%; - left: 50%; - z-index: 999; - min-width: var(--aksrt-mega-width, 480px); - padding: 16px 20px; - background: var(--surface-page, #fff); - border: 1px solid var(--color-line, #e8e8e8); - border-radius: 8px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); - opacity: 0; - visibility: hidden; - transform: translateX(-50%) translateY(8px); - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; -} - -.nav-desktop .menu-item:hover .aksrt-mega-submenu { - opacity: 1; - visibility: visible; - transform: translateX(-50%) translateY(0); -} - -.aksrt-mega-submenu__grid { - display: grid; - gap: 12px; -} - -.aksrt-mega-submenu__grid--cols-2 { grid-template-columns: repeat(2, 1fr); } -.aksrt-mega-submenu__grid--cols-3 { grid-template-columns: repeat(3, 1fr); } -.aksrt-mega-submenu__grid--cols-4 { grid-template-columns: repeat(4, 1fr); } -.aksrt-mega-submenu__grid--cols-5 { grid-template-columns: repeat(5, 1fr); } - -.aksrt-mega-submenu__item { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 10px 12px; - border-radius: 6px; - text-decoration: none; - color: var(--color-ink); - transition: background-color 0.15s ease; -} - -.aksrt-mega-submenu__item:hover { - background-color: var(--color-primary-soft, rgba(24, 144, 255, 0.08)); -} - -.aksrt-mega-submenu__item-icon { - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 6px; - background: var(--color-primary-soft, rgba(24, 144, 255, 0.08)); - color: var(--color-primary); - font-size: 14px; -} - -.aksrt-mega-submenu__item-content { - flex: 1; - min-width: 0; -} - -.aksrt-mega-submenu__item-title { - font-size: 13px; - font-weight: 500; - line-height: 1.4; - color: var(--color-ink); -} - -.aksrt-mega-submenu__item-desc { - margin-top: 2px; - font-size: 12px; - line-height: 1.4; - color: var(--text-tertiary, #8a909c); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* ========== 图文卡片子菜单 ========== */ -.nav-desktop .menu-item .aksrt-card-submenu { - position: absolute; - top: 100%; - left: 50%; - z-index: 999; - display: flex; - gap: 12px; - padding: 16px; - background: var(--surface-page, #fff); - border: 1px solid var(--color-line, #e8e8e8); - border-radius: 8px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); - opacity: 0; - visibility: hidden; - transform: translateX(-50%) translateY(8px); - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; -} - -.nav-desktop .menu-item:hover .aksrt-card-submenu { - opacity: 1; - visibility: visible; - transform: translateX(-50%) translateY(0); -} - -.aksrt-card-submenu__item { - display: flex; - flex-direction: column; - width: var(--aksrt-card-width, 160px); - text-decoration: none; - border-radius: 8px; - overflow: hidden; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.aksrt-card-submenu__item:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.aksrt-card-submenu__thumb { - width: 100%; - aspect-ratio: 16 / 10; - object-fit: cover; - background: var(--color-fill, #f5f5f5); -} - -.aksrt-card-submenu__body { - padding: 8px 10px; -} - -.aksrt-card-submenu__title { - font-size: 13px; - font-weight: 500; - line-height: 1.4; - color: var(--color-ink); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.aksrt-card-submenu__desc { - margin-top: 2px; - font-size: 11px; - line-height: 1.4; - color: var(--text-tertiary, #8a909c); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* ========== 暗色模式适配 ========== */ -html.dark .aksrt-mega-submenu, -html.dark .aksrt-card-submenu, -html.dark .aksrt-mega-submenu, -html.dark .aksrt-card-submenu { - background: var(--surface-page); - border-color: var(--color-line, #2d2d3f); -} - -html.dark .aksrt-mega-submenu__item-title, -html.dark .aksrt-card-submenu__title, -html.dark .aksrt-mega-submenu__item-title, -html.dark .aksrt-card-submenu__title { - color: var(--color-ink); -} +/* source: assets/css/frontend/enhancements.css */ /** * AKSRT Enhancements — 全局 UI 增强 * @@ -3712,7 +3233,7 @@ html.dark .aksrt-card-submenu__title { } .aksrt-hover-lift:hover { - transform: translateY(-2px); + transform: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } @@ -3725,11 +3246,13 @@ html.dark .aksrt-card-submenu__title { mix-blend-mode: normal; } -.dark::view-transition-old(root) { +.dark::view-transition-old(root), +body.dark-theme::view-transition-old(root) { z-index: 1; } -.dark::view-transition-new(root) { +.dark::view-transition-new(root), +body.dark-theme::view-transition-new(root) { z-index: 2147483646; } @@ -3788,7 +3311,7 @@ body.aksrt-body-scroll .aksrt-show-on-scroll { } /* ============================================================ - * 6. 图片灯箱 + * 5. 图片灯箱 * ============================================================ */ .aksrt-lightbox { position: fixed; @@ -3920,7 +3443,6 @@ body.aksrt-body-scroll .aksrt-show-on-scroll { .aksrt-cursor-zoom-in { cursor: zoom-in; } - /* ============================================================ * 7. 文章封面轮播 * ============================================================ */ @@ -4120,6 +3642,162 @@ body.aksrt-body-scroll .aksrt-show-on-scroll { background: var(--surface-3, #ECEEF1); } + + +.reward-modal__content > p { + margin: -4px 0 18px; +} + +.reward-amounts { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + margin: 18px 0; +} + +.reward-amount { + min-height: 42px; + border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.1)); + border-radius: 10px; + background: var(--surface-2); + color: var(--color-ink-2); + cursor: pointer; + font-weight: 700; +} + +.reward-amount.is-active { + border-color: var(--color-primary); + background: var(--color-primary-soft); + color: var(--color-primary); +} + +.reward-custom { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; + color: var(--color-ink-3); + font-size: 13px; +} + +.reward-custom input { + width: 140px; + min-height: 40px; + border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.1)); + border-radius: 9px; + background: var(--surface-1); + color: var(--color-ink); + padding: 0 12px; +} + +.reward-modal__submit { + width: 100%; + min-height: 42px; +} + +.reward-modal__message { + min-height: 20px; + margin-top: 10px; + color: var(--color-primary); + font-size: 12px; + text-align: center; +} +/* ---- reward amounts ---- */ +.reward-amounts { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + margin-bottom: 16px; +} + +.reward-amounts__btn { + display: inline-flex; + align-items: baseline; + gap: 2px; + padding: 8px 16px; + border: 1px solid var(--color-line, #e5e7eb); + border-radius: var(--radius, 10px); + background: var(--surface-2, #F4F5F7); + color: var(--color-ink); + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; +} + +.reward-amounts__btn:hover { + border-color: var(--color-primary, #f04494); + background: rgba(240, 68, 148, 0.06); +} + +.reward-amounts__btn.is-active { + border-color: var(--color-primary, #f04494); + background: var(--color-primary, #f04494); + color: #fff; +} + +.reward-amounts__currency { + font-size: 13px; + font-weight: 400; +} + +.reward-amounts__value { + font-size: 15px; +} +/* ---- custom amount ---- */ +.reward-custom { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; +} + +.reward-custom__input { + flex: 1; + height: 40px; + padding: 0 12px; + border: 1px solid var(--color-line, #e5e7eb); + border-radius: var(--radius, 10px); + background: var(--surface-2, #F4F5F7); + color: var(--color-ink); + font-size: 14px; + text-align: center; + outline: none; + transition: border-color 0.2s; + -moz-appearance: textfield; +} + +.reward-custom__input::-webkit-inner-spin-button, +.reward-custom__input::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.reward-custom__input:focus { + border-color: var(--color-primary, #f04494); +} + +.reward-custom__confirm { + flex-shrink: 0; + height: 40px; + padding: 0 20px; + border: none; + border-radius: var(--radius, 10px); + background: var(--color-primary, #f04494); + color: #fff; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: opacity 0.2s; +} + +.reward-custom__confirm:hover { + opacity: 0.85; +} + /* ============================================================ * 9. 公告跑马灯 Widget * ============================================================ */ @@ -4181,116 +3859,6 @@ body.aksrt-body-scroll .aksrt-show-on-scroll { animation-play-state: paused; } -/* ============================================================ - * 10. 用户卡片 Widget - * ============================================================ */ -.aksrt-user-card { - text-align: center; - padding: 20px; -} - -.aksrt-user-card__avatar { - width: 72px; - height: 72px; - border-radius: 50%; - margin: 0 auto 12px; - overflow: hidden; - border: 3px solid var(--color-primary-soft, rgba(136,171,218,0.1)); -} - -.aksrt-user-card__avatar img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.aksrt-user-card__name { - margin: 0 0 4px; - font-size: 16px; - font-weight: 600; - color: var(--color-ink); -} - -.aksrt-user-card__name a { - color: inherit; - text-decoration: none; -} - -.aksrt-user-card__name a:hover { - color: var(--color-primary, var(--color-primary)); -} - -.aksrt-user-card__bio { - margin: 0 0 12px; - font-size: 13px; - color: var(--color-ink-3); - line-height: 1.5; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.aksrt-user-card__stats { - display: flex; - justify-content: center; - gap: 20px; - margin-bottom: 16px; -} - -.aksrt-user-card__stat { - text-align: center; -} - -.aksrt-user-card__stat-value { - display: block; - font-size: 18px; - font-weight: 600; - color: var(--color-ink); -} - -.aksrt-user-card__stat-label { - font-size: 12px; - color: var(--color-ink-4, #8A8F98); -} - -.aksrt-user-card__actions { - display: flex; - gap: 8px; - justify-content: center; -} - -.aksrt-user-card__btn { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 6px 16px; - border-radius: var(--radius, 10px); - font-size: 13px; - text-decoration: none; - transition: all 0.15s; -} - -.aksrt-user-card__btn--primary { - background: var(--color-primary, var(--color-primary)); - color: #fff; -} - -.aksrt-user-card__btn--primary:hover { - opacity: 0.85; - color: #fff; -} - -.aksrt-user-card__btn--ghost { - border: 1px solid var(--color-line); - color: var(--color-ink-2); -} - -.aksrt-user-card__btn--ghost:hover { - border-color: var(--color-primary, var(--color-primary)); - color: var(--color-primary, var(--color-primary)); -} - /* ============================================================ * 11. 滚动触发动画初始状态 * ============================================================ */ @@ -4639,952 +4207,1118 @@ body.aksrt-body-scroll .aksrt-show-on-scroll { color: var(--color-ink-4, #8A8F98); } -/* ============================================================ - * 21. 弹窗登录/注册 - * ============================================================ */ -.aksrt-modal-auth { - position: fixed; - inset: 0; - z-index: 99999; - display: flex; - align-items: center; - justify-content: center; - opacity: 0; - visibility: hidden; - transition: opacity 0.3s, visibility 0.3s; +/* source: assets/css/frontend/skeleton.css */ +/* ========== 骨架屏加载 — 对齐 legacy skeleton loading ========== */ +.skeleton { + position: relative; + overflow: hidden; + background: var(--surface-2, #eee); + border-radius: var(--radius, 8px); } -.aksrt-modal-auth.is-open { - opacity: 1; - visibility: visible; +.skeleton::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.4) 50%, + transparent 100% + ); + animation: skeleton-shimmer 1.5s infinite; } -.aksrt-modal-auth__overlay { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); +@keyframes skeleton-shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } } -.aksrt-modal-auth__card { - position: relative; - background: var(--main-bg-color, #fff); - border-radius: var(--main-radius, 8px); - padding: 32px; - max-width: 400px; - width: 90%; - max-height: 90vh; - overflow-y: auto; - box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); - z-index: 1; - transform: scale(0.9); - transition: transform 0.5s cubic-bezier(0.32, 0.85, 0.45, 1.18); +/* 骨架屏变体 */ +.skeleton-text { + height: 14px; + border-radius: 4px; + margin-bottom: 8px; } -.aksrt-modal-auth.is-open .aksrt-modal-auth__card { - transform: scale(1); +.skeleton-text--title { + height: 20px; + width: 60%; + margin-bottom: 12px; } -.aksrt-modal-auth__close { - position: absolute; - top: 12px; - right: 12px; - width: 36px; - height: 36px; - border: none; - border-radius: 50%; - background: var(--surface-2, #F4F5F7); - color: var(--color-ink-3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background 0.2s; - font-size: 16px; +.skeleton-text--short { + width: 40%; } -.aksrt-modal-auth__close:hover { - background: var(--surface-3, #ECEEF1); +.skeleton-text--medium { + width: 70%; } -.aksrt-modal-auth__header { - text-align: center; - margin-bottom: 24px; +.skeleton-avatar { + width: 48px; + height: 48px; + border-radius: 50%; + flex-shrink: 0; } -.aksrt-modal-auth__logo { - width: 56px; - height: 56px; - border-radius: var(--radius); - background: linear-gradient(135deg, var(--color-primary, var(--color-primary)), var(--color-primary-active, #5A85BD)); - color: #fff; - font-size: 22px; - font-weight: 700; - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto 12px; +.skeleton-avatar--sm { + width: 32px; + height: 32px; } -.aksrt-modal-auth__title { - margin: 0; - font-size: 20px; - font-weight: 600; - color: var(--color-ink); +.skeleton-avatar--lg { + width: 64px; + height: 64px; } -/* Tabs */ -.aksrt-modal-auth__tabs { - display: flex; - border-bottom: 1px solid var(--color-line); - margin-bottom: 20px; +.skeleton-thumb { + width: 100%; + aspect-ratio: 16 / 9; + border-radius: var(--radius, 8px); } -.aksrt-modal-auth__tab { - flex: 1; - padding: 10px; - border: none; - background: none; - font-size: 15px; - font-weight: 500; - color: var(--color-ink-3); - cursor: pointer; - position: relative; - transition: color 0.2s; +.skeleton-thumb--square { + aspect-ratio: 1; } -.aksrt-modal-auth__tab.is-active { - color: var(--color-primary, var(--color-primary)); +.skeleton-thumb--card { + aspect-ratio: 16 / 10; + border-radius: var(--radius, 8px) var(--radius, 8px) 0 0; } -.aksrt-modal-auth__tab.is-active::after { - content: ''; - position: absolute; - bottom: -1px; - left: 0; - right: 0; - height: 2px; - background: var(--color-primary, var(--color-primary)); +/* 卡片骨架屏 */ +.skeleton-card { + background: var(--surface-1, #fff); + border-radius: var(--radius, 8px); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); + overflow: hidden; } -/* Panels */ -.aksrt-modal-auth__panel { - display: none; +.skeleton-card__thumb { + width: 100%; + aspect-ratio: 16 / 10; } -.aksrt-modal-auth__panel.is-active { - display: block; +.skeleton-card__body { + padding: 14px 16px; } -/* Form */ -.aksrt-modal-auth__form { - display: flex; - flex-direction: column; - gap: 16px; +.skeleton-card__meta { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; } -.aksrt-modal-auth__error { - padding: 10px 14px; - border-radius: 8px; - background: rgba(239, 68, 68, 0.08); - color: var(--color-error, #EF4444); - font-size: 13px; - line-height: 1.5; +/* 评论骨架屏 */ +.skeleton-comment { + display: flex; + gap: 12px; + padding: 16px 0; + border-bottom: 1px solid var(--color-line-soft, rgba(0, 0, 0, 0.03)); } -.aksrt-modal-auth__error a { - color: var(--color-primary, var(--color-primary)); - text-decoration: underline; +.skeleton-comment__body { + flex: 1; } -.aksrt-modal-auth__field label { - display: block; - font-size: 13px; - font-weight: 500; - color: var(--color-ink-2); - margin-bottom: 6px; +.skeleton-comment__header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; } -.aksrt-modal-auth__field input { - width: 100%; - padding: 10px 14px; - border: 1px solid var(--color-line); - border-radius: 10px; - font-size: 14px; - color: var(--color-ink); - background: var(--surface-1, #fff); - transition: border-color 0.2s; - box-sizing: border-box; +/* 通用骨架屏容器 */ +.skeleton-list .skeleton-item { + padding: 12px 0; + border-bottom: 1px solid var(--color-line-soft, rgba(0, 0, 0, 0.03)); } -.aksrt-modal-auth__field input:focus { - outline: none; - border-color: var(--color-primary, var(--color-primary)); +.skeleton-list .skeleton-item:last-child { + border-bottom: none; } -.aksrt-modal-auth__field input::placeholder { - color: var(--color-ink-4, #8A8F98); -} - -.aksrt-modal-auth__pass-wrap { - position: relative; -} - -.aksrt-modal-auth__pass-wrap input { - padding-right: 44px; -} - -.aksrt-modal-auth__pass-toggle { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - width: 32px; - height: 32px; - border: none; - background: none; - color: var(--color-ink-4, #8A8F98); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border-radius: 6px; -} - -.aksrt-modal-auth__pass-toggle:hover { - background: var(--surface-2, #F4F5F7); -} - -.aksrt-modal-auth__actions { - display: flex; - justify-content: flex-end; -} - -.aksrt-modal-auth__link { - font-size: 13px; - color: var(--color-primary, var(--color-primary)); - text-decoration: none; -} - -.aksrt-modal-auth__link:hover { - text-decoration: underline; -} - -.aksrt-modal-auth__submit { - width: 100%; - padding: 12px; - border: none; - border-radius: 10px; - background: var(--color-primary, var(--color-primary)); - color: #fff; - font-size: 15px; - font-weight: 600; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - transition: opacity 0.2s; -} - -.aksrt-modal-auth__submit:hover { - opacity: 0.85; -} - -.aksrt-modal-auth__submit:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Divider */ -.aksrt-modal-auth__divider { - display: flex; - align-items: center; - gap: 12px; - margin: 20px 0; - color: var(--color-ink-4, #8A8F98); - font-size: 12px; -} - -.aksrt-modal-auth__divider::before, -.aksrt-modal-auth__divider::after { - content: ''; - flex: 1; - height: 1px; - background: var(--color-line); -} - -/* OAuth */ -.aksrt-modal-auth__oauth { - display: flex; - justify-content: center; - gap: 12px; -} - -.aksrt-modal-auth__oauth-btn { - width: 44px; - height: 44px; - border: 1px solid var(--color-line); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - color: var(--color-ink-2); - font-size: 18px; - text-decoration: none; - transition: all 0.2s; -} - -.aksrt-modal-auth__oauth-btn:hover { - border-color: var(--color-primary, var(--color-primary)); - color: var(--color-primary, var(--color-primary)); - background: var(--color-primary-soft, rgba(136, 171, 218, 0.1)); -} - -/* Footer */ -.aksrt-modal-auth__footer { - text-align: center; - margin-top: 20px; - font-size: 13px; - color: var(--color-ink-3); -} - -.aksrt-modal-auth__switch { - border: none; - background: none; - color: var(--color-primary, var(--color-primary)); - cursor: pointer; - font-size: 13px; - font-weight: 500; - padding: 0; -} - -.aksrt-modal-auth__switch:hover { - text-decoration: underline; -} - -/* Dark mode */ -.theme-dark .aksrt-modal-auth__card, -html.dark .aksrt-modal-auth__card { - background: #1e1e2e; - border: 1px solid rgba(255, 255, 255, 0.1); -} - -.theme-dark .aksrt-modal-auth__field input, -html.dark .aksrt-modal-auth__field input { - background: #2a2a3a; - border-color: rgba(255, 255, 255, 0.15); - color: #e0e0e0; -} - -.theme-dark .aksrt-modal-auth__close, -html.dark .aksrt-modal-auth__close { - background: #2a2a3a; -} - -/* Responsive */ -@media (max-width: 480px) { - .aksrt-modal-auth__card { - padding: 24px 20px; - border-radius: 12px; - } - - .aksrt-modal-auth__logo { - width: 48px; - height: 48px; - font-size: 18px; - } - - .aksrt-modal-auth__title { - font-size: 18px; - } -} - -/* ============================================================ - * 22. 语言切换器 - * ============================================================ */ -.aksrt-lang-switch { - position: relative; -} - -.aksrt-lang-switch__toggle { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 12px; - border: 1px solid var(--color-line); - border-radius: var(--radius, 10px); - background: var(--surface-1, #fff); - color: var(--color-ink-2); - font-size: 13px; - cursor: pointer; - transition: border-color 0.2s; - width: 100%; -} - -.aksrt-lang-switch__toggle:hover { - border-color: var(--color-primary, var(--color-primary)); -} - -.aksrt-lang-switch__toggle img { - border-radius: 2px; -} - -.aksrt-lang-switch__dropdown { - position: absolute; - top: 100%; - left: 0; - right: 0; - margin: 4px 0 0; - padding: 4px; - list-style: none; - background: var(--surface-1, #fff); - border: 1px solid var(--color-line); - border-radius: var(--radius, 10px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - z-index: 100; - opacity: 0; - visibility: hidden; - transform: translateY(4px); - transition: all 0.2s; -} - -.aksrt-lang-switch:hover .aksrt-lang-switch__dropdown, -.aksrt-lang-switch__toggle:focus + .aksrt-lang-switch__dropdown { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.aksrt-lang-switch__item { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 10px; - border-radius: 6px; - font-size: 13px; - color: var(--color-ink-2); - text-decoration: none; - transition: background 0.15s; -} - -.aksrt-lang-switch__item:hover { - background: var(--surface-2, #F4F5F7); -} - -.aksrt-lang-switch__item.is-active { - color: var(--color-primary, var(--color-primary)); - font-weight: 600; -} - -.aksrt-lang-switch__item img { - border-radius: 2px; -} - -/* 国旗模式 */ -.aksrt-lang-switch--flags { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.aksrt-lang-switch__flag { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 24px; - border: 2px solid transparent; - border-radius: 4px; - overflow: hidden; - transition: border-color 0.2s; - text-decoration: none; -} - -.aksrt-lang-switch__flag:hover { - border-color: var(--color-primary, var(--color-primary)); -} - -.aksrt-lang-switch__flag.is-active { - border-color: var(--color-primary, var(--color-primary)); -} - -.aksrt-lang-switch__flag img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.aksrt-lang-switch__flag span { - font-size: 10px; - font-weight: 600; - color: var(--color-ink-3); -} - -/* Dark mode */ -.theme-dark .aksrt-lang-switch__toggle, -html.dark .aksrt-lang-switch__toggle { - background: #2a2a3a; - border-color: rgba(255, 255, 255, 0.15); -} - -.theme-dark .aksrt-lang-switch__dropdown, -html.dark .aksrt-lang-switch__dropdown { - background: #2a2a3a; - border-color: rgba(255, 255, 255, 0.15); -} - -/* ============================================================ - * 响应式 - * ============================================================ */ -@media (max-width: 640px) { - .aksrt-lightbox__nav { - width: 36px; - height: 36px; - font-size: 18px; - } - - .single-cover-slider__img { - height: 240px; - } - - .single-cover-slider__title { - font-size: 18px; - } - - .reward-modal__card { - padding: 24px; - } - - .reward-modal__qr { - width: 160px; - height: 160px; - } - - .aksrt-cover-cards { - grid-template-columns: 1fr; - } - - .aksrt-cover-card { - min-height: 140px; - } -} - -/* ============================================================ - * 13. 无障碍:prefers-reduced-motion - * 用户系统设置"减少动画"时,禁用所有非必要动效 - * ============================================================ */ +/* 减弱动画偏好 */ @media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } - - .aksrt-skeleton { - animation: none !important; - } - - [data-aksrt-animate] { - opacity: 1 !important; - transform: none !important; - } + .skeleton::after { + animation: none; + } } -/* ============================================================ - * 14. 图片懒加载 blur-to-sharp 过渡 - * 模仿 legacy 的 lazy_blur 效果 - * ============================================================ */ -img.lazyload, -img.lazyloaded, -img[data-src] { - transition: filter 0.6s var(--ease), opacity 0.4s var(--ease); +/* source: assets/css/frontend/empty-state.css */ +/* ========== 空状态 — 对齐 legacy empty state ========== */ +.empty-state { + text-align: center; + padding: 60px 20px; + color: var(--color-ink-3, #777); } -img.lazyload, -img[data-src] { - filter: blur(5px); - opacity: 0.7; +.empty-state__icon { + font-size: 56px; + color: var(--surface-3, #e8e8e8); + margin-bottom: 16px; + line-height: 1; } -img.lazyloaded { - filter: blur(0); - opacity: 1; +.empty-state__icon i { + display: inline-block; } -/* ============================================================ - * 15. 错误反馈 shake 动画 - * 表单验证失败时的水平抖动效果 - * ============================================================ */ -@keyframes aksrtShake { - 0%, 100% { transform: translateX(0); } - 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } - 20%, 40%, 60%, 80% { transform: translateX(4px); } +.empty-state__title { + font-size: 16px; + font-weight: 500; + color: var(--color-ink-2, #4e5358); + margin-bottom: 8px; } -.aksrt-shake { - animation: aksrtShake 0.4s ease-in-out; +.empty-state__desc { + font-size: 13px; + color: var(--color-ink-4, #999); + margin-bottom: 20px; + max-width: 320px; + margin-left: auto; + margin-right: auto; + line-height: 1.6; } -/* 应用于 Naive UI 表单验证 */ -.n-form-item--error .n-input, -.n-form-item--error .n-select, -.n-form-item--error .n-input-number { - animation: aksrtShake 0.3s ease-in-out; +.empty-state__action { + display: inline-flex; + align-items: center; + gap: 6px; } -/* ============================================================ - * 16. FPS 加速模式:低端设备禁用 backdrop-filter - * 模仿 legacy 的 body.fps-accelerat 模式 - * ============================================================ */ -body.fps-accelerat .site-nav, -body.fps-accelerat .footer-tabbar, -body.fps-accelerat .float-right .float-btn, -body.fps-accelerat .aksrt-blur-bg { - backdrop-filter: none !important; - -webkit-backdrop-filter: none !important; +/* 空状态变体 */ +.empty-state--sm { + padding: 30px 16px; } -/* 模态框内禁用 backdrop-filter 避免渲染冲突 */ -body.modal-open .site-nav, -body.dialog-open .site-nav { - backdrop-filter: none !important; - -webkit-backdrop-filter: none !important; +.empty-state--sm .empty-state__icon { + font-size: 40px; } -/* ============================================================ - * 17. .blur-bg 通用工具类 - * 模仿 legacy 的 .blur-bg,可在任意元素上应用毛玻璃效果 - * ============================================================ */ -.aksrt-blur-bg { - background: var(--blur-bg); - backdrop-filter: saturate(5) blur(20px); - -webkit-backdrop-filter: saturate(5) blur(20px); +.empty-state--sm .empty-state__title { + font-size: 14px; } -/* ============================================================ - * 18. 滚动触发动画延迟支持 - * data-aksrt-delay 属性控制入场延迟 - * ============================================================ */ -[data-aksrt-animate][data-aksrt-delay="1"] { transition-delay: 0.1s; } -[data-aksrt-animate][data-aksrt-delay="2"] { transition-delay: 0.2s; } -[data-aksrt-animate][data-aksrt-delay="3"] { transition-delay: 0.3s; } -[data-aksrt-animate][data-aksrt-delay="4"] { transition-delay: 0.4s; } -[data-aksrt-animate][data-aksrt-delay="5"] { transition-delay: 0.5s; } - -/* ============================================================ - * 19. 全站动效节奏统一 - * 模仿 legacy 的统一 hover / slide / blur / drawer 语言 - * ============================================================ */ - -/* --- 全局过渡基线 --- */ -a, button, input, select, textarea { - transition: border-color var(--dur-fast) var(--ease), - box-shadow var(--dur-fast) var(--ease), - background-color var(--dur-fast) var(--ease), - color var(--dur-fast) var(--ease); +.empty-state--sm .empty-state__desc { + font-size: 12px; } -/* --- 卡片 hover 统一 --- */ -.post-card, .widget, .aksrt-theme-box, .theme-box, -.comment-body, .friend-card, .special-card, -.resource-download-box, .resource-meta-box { - transition: box-shadow var(--dur) var(--ease), - border-color var(--dur) var(--ease), - transform var(--dur) var(--ease); +/* 搜索空状态 */ +.search-empty { + text-align: center; + padding: 80px 20px; } -.post-card:hover, .widget:hover, .friend-card:hover, .special-card:hover { - box-shadow: var(--shadow); - transform: translateY(-2px); +.search-empty__icon { + font-size: 64px; + color: var(--surface-3, #e8e8e8); + margin-bottom: 16px; } -/* --- 按钮 hover 统一 --- */ -.btn-primary, .btn-outline, .btn-lg, .btn-sm, -.n-button, .aksrt-button { - transition: all var(--dur-fast) var(--ease); +.search-empty__title { + font-size: 18px; + font-weight: 500; + color: var(--color-ink-2, #4e5358); + margin-bottom: 8px; } -.btn-primary:hover, .n-button--primary-type:hover { - opacity: 0.85; +.search-empty__suggestions { + font-size: 13px; + color: var(--color-ink-4, #999); } -.btn-primary:active, .n-button--primary-type:active { - opacity: 1; +.search-empty__suggestions a { + color: var(--color-primary, #f04494); + text-decoration: none; } -/* --- 输入框 focus 统一 --- */ -input:focus, textarea:focus, select:focus, -.n-input--focus, .n-select--focus { - box-shadow: var(--focus-ring); - transition: box-shadow var(--dur-fast) var(--ease); +.search-empty__suggestions a:hover { + text-decoration: underline; } -/* --- 抽屉/模态框动效统一 --- */ -.n-drawer, .n-modal, .n-dialog { - transition: all var(--dur) var(--ease-out); -} - -.n-drawer-mask, .n-modal-mask, .n-dialog-mask { - transition: opacity var(--dur) var(--ease); -} - -/* --- Tab 切换动效 --- */ -.n-tabs-tab, .nav-tab, .aksrt-tab { - transition: color var(--dur-fast) var(--ease), - border-color var(--dur-fast) var(--ease), - background-color var(--dur-fast) var(--ease); -} - -/* --- 下拉菜单动效 --- */ -.n-dropdown-menu, .aksrt-dropdown { - transition: opacity var(--dur-fast) var(--ease), - transform var(--dur-fast) var(--ease-out); - transform-origin: top; -} - -/* --- 骨架屏动效 --- */ -.aksrt-skeleton { - animation: aksrtSkeletonShimmer 1.5s infinite linear; - background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-shine) 50%, var(--skeleton-base) 75%); - background-size: 200% 100%; -} - -/* --- 页面切换动效 --- */ -.fade-slide-enter-active, -.fade-slide-leave-active { - transition: opacity var(--dur) var(--ease), - transform var(--dur) var(--ease); -} - -.fade-slide-enter-from { - opacity: 0; - transform: translateY(8px); -} - -.fade-slide-leave-to { - opacity: 0; - transform: translateY(-8px); -} - -/* --- 导航栏滚动动效 --- */ -.site-nav { - transition: transform var(--dur) var(--ease), - background-color var(--dur) var(--ease), - box-shadow var(--dur) var(--ease); -} - -.site-nav.aksrt-nav--hidden { - transform: translateY(-100%); -} - -/* --- 浮层弹出动效 --- */ -@keyframes aksrtPopIn { - 0% { opacity: 0; transform: scale(0.95) translateY(-4px); } - 100% { opacity: 1; transform: scale(1) translateY(0); } -} - -@keyframes aksrtPopOut { - 0% { opacity: 1; transform: scale(1) translateY(0); } - 100% { opacity: 0; transform: scale(0.95) translateY(-4px); } -} - -.aksrt-popover, .aksrt-tooltip, .aksrt-dropdown-panel { - animation: aksrtPopIn var(--dur-fast) var(--ease-out) forwards; -} - -/* --- 图片懒加载过渡 --- */ -img.lazyload, img.lazyloaded, img[data-src] { - transition: filter 0.6s var(--ease), opacity 0.4s var(--ease); -} - -img.lazyload, img[data-src] { - filter: blur(5px); - opacity: 0.7; -} - -img.lazyloaded { - filter: blur(0); - opacity: 1; -} - -/* --- 滚动进度条动效 --- */ -.reading-progress-bar { - transition: width 0.1s linear; -} - -/* --- 移动端抽屉动效 --- */ +/* 移动端 */ @media (max-width: 767px) { - .n-drawer { - transition: transform var(--dur) var(--ease-out); - } + .empty-state { + padding: 40px 16px; + } - .site-nav .n-drawer-body { - transition: transform var(--dur) var(--ease-out); - } + .empty-state__icon { + font-size: 44px; + } } -/* --- 暗色模式切换动效 --- */ -html.dark { - transition: background-color 0.3s var(--ease), - color 0.3s var(--ease); +/* source: assets/css/frontend/pagination.css */ +/* ========== 分页 — 对齐 legacy pagination ========== */ +.content-stream__footer-nav { + width: 100%; } -html.dark * { - transition: background-color 0.3s var(--ease), - border-color 0.3s var(--ease), - color 0.3s var(--ease), - box-shadow 0.3s var(--ease); +.content-stream__footer-nav.aksrt-pagination, +.content-stream__footer-nav.aksrt-pagination-ajax, +.content-stream__footer .aksrt-pagination, +.content-stream__footer .aksrt-pagination-ajax { + margin: 0; } -/* ============================================================ - * 18. 合作伙伴无缝滚动 (marquee) - * ============================================================ */ -.aksrt-marquee { - position: relative; - overflow: hidden; - width: 100%; +.pagination, +.aksrt-pagination, +.navigation.pagination .nav-links, +.pagenav.ajax-pag { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 24px 0; + flex-wrap: wrap; } -.aksrt-marquee__inner { - display: flex; - gap: 24px; - white-space: nowrap; - will-change: transform; +.pagination a, +.pagination span, +.aksrt-pagination a, +.aksrt-pagination span, +.navigation.pagination a, +.navigation.pagination span, +.pagenav.ajax-pag a, +.pagenav.ajax-pag span { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + min-height: 34px; + padding: 6px 10px; + font-size: 13px; + line-height: 1.4; + color: var(--muted-color, var(--color-ink-3)); + background: var(--surface-4, var(--main-border-color, rgba(50, 50, 50, 0.06))); + border: 1px solid transparent; + border-radius: var(--radius-xs, 3px); + text-decoration: none; + transition: all 0.2s; + user-select: none; } -.aksrt-marquee__inner > * { - flex-shrink: 0; +.pagination a:hover, +.aksrt-pagination a:hover, +.navigation.pagination a:hover, +.pagenav.ajax-pag a:hover { + opacity: 1; + color: var(--color-primary); + background: var(--color-primary-soft); } -@media (prefers-reduced-motion: reduce) { - .aksrt-marquee__inner { - animation: none !important; - transform: none !important; - } +.pagination .current, +.aksrt-pagination .current, +.navigation.pagination .current, +.pagenav.ajax-pag .current { + color: var(--color-on-primary, #fff); + background: var(--color-primary, #f04494); + border-color: var(--color-primary, #f04494); + font-weight: 500; } -/* ============================================================ - * 19. 视频剧集切换按钮 - * ============================================================ */ -.switch-video, -.switch-iframe { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 6px 14px; - border: 1px solid var(--color-line, rgba(50, 50, 50, 0.1)); - border-radius: var(--radius, 8px); - background: var(--surface-1, #fff); - color: var(--color-ink-2, #4e5358); - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - text-decoration: none; +.pagination .dots, +.aksrt-pagination .dots, +.navigation.pagination .dots, +.pagenav.ajax-pag .dots { + border: none; + background: none; + color: var(--color-ink-4, #999); + cursor: default; + min-width: auto; + padding-left: 4px; + padding-right: 4px; } -.switch-video:hover, -.switch-iframe:hover { - border-color: var(--color-primary); - color: var(--color-primary); +.pagination .prev, +.pagination .next, +.aksrt-pagination .prev, +.aksrt-pagination .next, +.navigation.pagination .prev, +.navigation.pagination .next, +.pagenav.ajax-pag .prev, +.pagenav.ajax-pag .next { + font-size: 12px; } -.switch-video.is-active, -.switch-iframe.is-active { - background: var(--color-primary); - border-color: var(--color-primary); - color: #fff; +.pagination .prev i, +.pagination .next i, +.aksrt-pagination .prev i, +.aksrt-pagination .next i, +.navigation.pagination .prev i, +.navigation.pagination .next i, +.pagenav.ajax-pag .prev i, +.pagenav.ajax-pag .next i { + font-size: 11px; } -/* ============================================================ - * 20. 一言轮播组件 - * ============================================================ */ -.aksrt-yiyan { - padding: 16px; - text-align: center; +.pagenav.ajax-pag .pag-jump, +.aksrt-pagination .pag-jump, +.navigation.pagination .pag-jump { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: 6px; } -.aksrt-yiyan__content { - min-height: 60px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; +.pagenav.ajax-pag .jump-input, +.aksrt-pagination .jump-input, +.navigation.pagination .jump-input { + width: 64px; + min-width: 64px; + padding: 6px 8px 5px; + font-size: 13px; + line-height: 1.4; + color: var(--muted-color, var(--color-ink-3)); + background: var(--main-bg-color, #fff); + border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); + border-radius: 3px; + box-shadow: none; } -.aksrt-yiyan__icon { - font-size: 20px; - color: var(--color-primary); - opacity: 0.6; +.pagenav.ajax-pag .jump-input:focus, +.aksrt-pagination .jump-input:focus, +.navigation.pagination .jump-input:focus { + outline: none; + border-color: var(--focus-color, var(--color-primary, #f04494)); } -.aksrt-yiyan__text { - font-size: 14px; - line-height: 1.8; - color: var(--color-ink-2, #4e5358); - margin: 0; - transition: opacity 0.2s ease; - font-style: italic; +.pagenav.ajax-pag .pag-jump .ajax-next, +.aksrt-pagination .pag-jump .ajax-next, +.navigation.pagination .pag-jump .ajax-next { + min-width: auto; + cursor: pointer; } -.aksrt-yiyan__from { +/* 上一页/下一页 */ +.pagination__nav { + display: flex; + gap: 8px; + justify-content: center; + margin-top: 8px; +} + +.pagination__nav a { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 14px; + font-size: 13px; + color: var(--color-ink-2, #4e5358); + background: var(--surface-1, #fff); + border: 1px solid var(--color-line, rgba(50, 50, 50, 0.06)); + border-radius: 6px; + text-decoration: none; + transition: all 0.2s; +} + +.pagination__nav a:hover { + color: var(--color-primary, #f04494); + border-color: var(--color-primary, #f04494); +} + +/* AJAX 加载更多 */ +.load-more { + text-align: center; + padding: 20px 0; +} + +.load-more__btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 10px 24px; + font-size: 13px; + color: var(--color-ink-2, #4e5358); + background: var(--surface-1, #fff); + border: 1px solid var(--color-line, rgba(50, 50, 50, 0.06)); + border-radius: 20px; + cursor: pointer; + transition: all 0.2s; +} + +.load-more__btn:hover { + color: var(--color-primary, #f04494); + border-color: var(--color-primary, #f04494); +} + +.load-more__btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.load-more__spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid var(--surface-3, #e8e8e8); + border-top-color: var(--color-primary, #f04494); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* 移动端 */ +@media (max-width: 767px) { + .pagination a, + .pagination span, + .aksrt-pagination a, + .aksrt-pagination span, + .navigation.pagination a, + .navigation.pagination span, + .pagenav.ajax-pag a, + .pagenav.ajax-pag span { + min-width: 28px; font-size: 12px; - color: var(--color-ink-4, #999); - margin: 0; -} + margin: 2px; + } -.aksrt-yiyan__dots { - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - margin-top: 12px; -} - -.aksrt-yiyan__dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--color-line, rgba(50, 50, 50, 0.1)); - transition: background 0.2s; -} - -.aksrt-yiyan__dot.is-active { - background: var(--color-primary); - width: 16px; - border-radius: 3px; -} - -/* ============================================================ - * 底部弹出 Sheet — 对齐 legacy .modal.bottom - * ============================================================ */ -.aksrt-modal--bottom .aksrt-modal-auth__card, -.modal.bottom .modal-dialog { - position: fixed; - bottom: 0; - left: 0; - right: 0; - max-width: 100%; + .pagenav.ajax-pag .pag-jump, + .aksrt-pagination .pag-jump, + .navigation.pagination .pag-jump { width: 100%; - border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0; - transform: translateY(110%); - transition: transform 0.4s; - margin-bottom: 0; - padding-bottom: env(safe-area-inset-bottom); + justify-content: center; + margin-left: 0; + margin-top: 6px; + } + + .pagenav.ajax-pag .jump-input, + .aksrt-pagination .jump-input, + .navigation.pagination .jump-input { + width: 58px; + min-width: 58px; + font-size: 12px; + } } -.aksrt-modal--bottom.is-open .aksrt-modal-auth__card, -.modal.bottom.in .modal-dialog { - transform: translateY(0); +/* source: assets/css/frontend/ajax-spinner.css */ +/* ========== AJAX 加载指示器 — 对齐 legacy loading spinner ========== */ +.aksrt-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 40px 20px; + color: var(--color-ink-4, #999); + font-size: 13px; + gap: 8px; } -.aksrt-modal--bottom .aksrt-modal-auth__card, -.modal.bottom .modal-dialog .modal-content { - box-shadow: 0 -10px 50px rgba(0, 0, 0, 0.15); +.aksrt-loading__spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 2px solid var(--surface-3, #e8e8e8); + border-top-color: var(--color-primary, #f04494); + border-radius: 50%; + animation: aksrt-spin 0.6s linear infinite; +} + +@keyframes aksrt-spin { + to { transform: rotate(360deg); } +} + +/* 全屏加载遮罩 */ +.aksrt-loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + backdrop-filter: blur(2px); +} + +.aksrt-loading-overlay__content { + text-align: center; +} + +.aksrt-loading-overlay__spinner { + width: 36px; + height: 36px; + border: 3px solid var(--surface-3, #e8e8e8); + border-top-color: var(--color-primary, #f04494); + border-radius: 50%; + animation: aksrt-spin 0.6s linear infinite; + margin: 0 auto 12px; +} + +.aksrt-loading-overlay__text { + font-size: 13px; + color: var(--color-ink-3, #777); +} + +/* 行内加载(替换内容时) */ +.aksrt-loading-inline { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + color: var(--color-ink-4, #999); + font-size: 12px; + gap: 6px; +} + +.aksrt-loading-inline__dot { + width: 6px; + height: 6px; + background: var(--color-primary, #f04494); + border-radius: 50%; + animation: aksrt-dot-pulse 1.2s ease-in-out infinite; +} + +.aksrt-loading-inline__dot:nth-child(2) { + animation-delay: 0.2s; +} + +.aksrt-loading-inline__dot:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes aksrt-dot-pulse { + 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } + 40% { opacity: 1; transform: scale(1); } +} + +/* 页面加载进度条 */ +.aksrt-progress-bar { + position: fixed; + top: 0; + left: 0; + height: 2px; + background: var(--color-primary, #f04494); + z-index: 10000; + transition: width 0.3s ease; +} + +/* 减弱动画偏好 */ +@media (prefers-reduced-motion: reduce) { + .aksrt-loading__spinner, + .aksrt-loading-overlay__spinner { + animation-duration: 1.5s; + } + + .aksrt-loading-inline__dot { + animation: none; + opacity: 0.6; + } +} + +/* source: assets/css/frontend/modal-runtime.css */ +/** + * Modal runtime + ajax list primitives (shared). + * + * @package AKSRT + */ + +/* ---- scroll lock ---- */ +html.aksrt-modal-runtime-open, +body.aksrt-modal-runtime-open { + overflow: hidden; +} + +/* ---- modal runtime shell ---- */ +.aksrt-modal-runtime { + position: fixed; + inset: 0; + z-index: 100050; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; + pointer-events: none; +} + +.aksrt-modal-runtime.is-open { + opacity: 1; + visibility: visible; + pointer-events: auto; +} + +.aksrt-modal-runtime__overlay { + position: absolute; + inset: 0; + background: rgba(15, 18, 28, 0.48); + backdrop-filter: blur(2px); +} + +.aksrt-modal-runtime__dialog { + position: relative; + z-index: 1; + width: min(440px, calc(100vw - 32px)); + max-height: min(88vh, 720px); + overflow: auto; + background: var(--surface-1, #fff); + border-radius: 16px; + box-shadow: 0 18px 48px rgba(15, 18, 28, 0.22); + transform: translateY(12px) scale(0.98); + transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1); + color: var(--color-ink, #1f2937); +} + +.aksrt-modal-runtime.is-open .aksrt-modal-runtime__dialog { + transform: translateY(0) scale(1); +} + +.aksrt-modal-runtime--bottom { + align-items: flex-end; +} + +.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog { + width: 100%; + max-width: 100%; + border-radius: 16px 16px 0 0; + max-height: 90vh; + transform: translateY(24px); +} + +.aksrt-modal-runtime--bottom.is-open .aksrt-modal-runtime__dialog { + transform: translateY(0); +} + +.aksrt-modal-runtime__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 18px 0; +} + +.aksrt-modal-runtime__title { + margin: 0; + font-size: 17px; + line-height: 1.35; +} + +.aksrt-modal-runtime__content { + padding: 18px 18px 16px; +} + +.aksrt-modal-runtime__loading, +.aksrt-modal-runtime__error-body { + padding: 28px 8px; + text-align: center; + color: var(--color-ink-2, #6b7280); + font-size: 14px; +} + +.aksrt-modal-runtime__skeleton { + display: grid; + gap: 12px; + padding: 8px 2px; +} + +.aksrt-modal-runtime__skeleton-line, +.aksrt-modal-runtime__skeleton-block { + display: block; + border-radius: 8px; + background: linear-gradient(90deg, rgba(148, 163, 184, 0.16), rgba(148, 163, 184, 0.3), rgba(148, 163, 184, 0.16)); + background-size: 200% 100%; + animation: aksrt-modal-skeleton 1.2s ease-in-out infinite; +} + +.aksrt-modal-runtime__skeleton-line { + width: 72%; + height: 12px; +} + +.aksrt-modal-runtime__skeleton-line.is-wide { + width: 92%; + height: 18px; +} + +.aksrt-modal-runtime__skeleton-line.is-short { + width: 44%; +} + +.aksrt-modal-runtime__skeleton-block { + width: 100%; + min-height: 84px; +} + +.aksrt-modal-runtime__retry { + cursor: pointer; +} + +@keyframes aksrt-modal-skeleton { + 0% { background-position: 100% 0; } + 100% { background-position: -100% 0; } +} + +.aksrt-modal-runtime .touch.border-title, +.aksrt-modal-runtime .border-title { + font-size: 16px; + font-weight: 700; + margin: 0 0 14px; + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 6px; +} + +.aksrt-modal-runtime .aksrt-modal-runtime__close { + position: absolute; + top: 10px; + right: 10px; + width: 32px; + height: 32px; + border: 0; + border-radius: 999px; + background: transparent; + color: var(--color-ink-3, #9ca3af); + cursor: pointer; + font-size: 18px; + line-height: 1; +} + +.aksrt-modal-runtime .but-average.modal-buts, +.aksrt-modal-runtime .modal-buts { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 16px; +} + +.aksrt-modal-runtime .but-average.modal-buts .but, +.aksrt-modal-runtime .modal-buts .but, +.aksrt-modal-runtime .modal-buts button { + min-height: 40px; + border-radius: 10px; + border: 1px solid var(--color-line, #e5e7eb); + background: var(--surface-4, #f3f4f6); + color: var(--color-ink, #111); + font-weight: 600; + cursor: pointer; +} + +.aksrt-modal-runtime .modal-buts .but.c-blue, +.aksrt-modal-runtime .modal-buts button.c-blue, +.aksrt-modal-runtime .modal-buts .btn-primary { + background: var(--color-primary, #f04494); + border-color: color-mix(in srgb, var(--color-primary, #f04494) 70%, #000 0%); + color: var(--color-on-primary, #fff); +} + +/* ---- ajax list ---- */ +.ajaxpager.is-ajax-loading { + position: relative; + min-height: 80px; +} + +.ajaxpager .post_ajax_loader, +.ajaxpager .post-ajax-loader { + display: none; +} + +.ajaxpager .post_ajax_loader.is-loading, +.ajaxpager .post-ajax-loader.is-loading { + display: block; +} + +.ajax-item.is-fading { + opacity: 0.35; + transition: opacity 0.15s ease; +} + +/* ---- order review modal content ---- */ +.order-comment-from .shop-score-box { + display: flex; + align-items: center; + gap: 8px; + min-height: 36px; +} + +.order-comment-from .score-title { + width: 4.5em; + flex: 0 0 auto; + font-size: 13px; + color: var(--color-ink-2, #6b7280); +} + +.order-comment-from .score-icon { + display: inline-flex; + gap: 4px; + align-items: center; + height: 30px; +} + +.order-comment-from .icon-star { + display: inline-flex; + width: 22px; + height: 22px; + color: #d1d5db; + cursor: pointer; + transition: color 0.12s ease, transform 0.12s ease; +} + +.order-comment-from .icon-star svg { + width: 22px; + height: 22px; + fill: currentColor; +} + +.order-comment-from .icon-star.active, +.order-comment-from .icon-star.hover { + color: #fc7049; +} + +.order-comment-from .icon-star.hover { + transform: scale(1.08); +} + +.order-comment-from .score-text { + min-width: 3em; + font-size: 13px; + color: var(--color-ink-2, #6b7280); +} + +.order-comment-from textarea.form-control { + width: 100%; + border: 1px solid var(--color-line, #e5e7eb); + border-radius: 10px; + padding: 10px 12px; + font-size: 14px; + resize: vertical; + min-height: 96px; + background: var(--surface-1, #fff); + color: var(--color-ink, #111); +} + +.order-comment-from .form-upload .preview { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.order-comment-from .form-upload .preview .add, +.order-comment-from .form-upload .preview .preview-item { + width: 80px; + height: 80px; + border-radius: 10px; + border: 1px dashed var(--color-line, #d1d5db); + background: var(--surface-4, #f9fafb); + position: relative; + overflow: hidden; + cursor: pointer; +} + +.order-comment-from .form-upload .preview .add::before, +.order-comment-from .form-upload .preview .add::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + background: var(--color-ink-3, #9ca3af); + transform: translate(-50%, -50%); +} + +.order-comment-from .form-upload .preview .add::before { + width: 22px; + height: 2px; +} + +.order-comment-from .form-upload .preview .add::after { + width: 2px; + height: 22px; +} + +.order-comment-from .form-upload .preview .preview-item img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.order-comment-from .form-upload .preview .preview-item .remove { + position: absolute; + top: 2px; + right: 2px; + width: 20px; + height: 20px; + border: 0; + border-radius: 999px; + background: rgba(0, 0, 0, 0.55); + color: #fff; + font-size: 12px; + line-height: 1; + cursor: pointer; +} + +.order-comment-from .form-upload .hide { + display: none !important; +} + +.order-comment-from .review-error { + color: #ef4444; + font-size: 13px; + margin-top: 8px; +} + +@media (max-width: 768px) { + .aksrt-modal-runtime:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog { + width: calc(100vw - 20px); + } +} + +/* source: assets/css/frontend/notyf.css */ +/** + * legacy-compatible frontend notifications. + * + * @package AKSRT + */ + +.notyn { + position: fixed; + bottom: 6em; + right: 0; + max-width: 320px; + z-index: 10000000; +} + +.notyf { + position: relative; + margin-bottom: 0.6em; + color: #fff; + font-size: 14px; + line-height: 1.42857143; + float: right; + min-width: 200px; + padding: 1em 3em 1em 3.4em; + box-shadow: -3px 3px 6px rgba(0, 0, 0, 0.1); + transform: translateX(1.5em); + transition: 0.3s; + -webkit-backdrop-filter: saturate(5) blur(20px); + backdrop-filter: saturate(5) blur(20px); + -webkit-animation: notyf 0.4s cubic-bezier(0.35, 0.71, 0.46, 1.08); + animation: notyf 0.4s cubic-bezier(0.35, 0.71, 0.46, 1.08); +} + +.notyn-out .notyf { + transform: translateX(110%); + opacity: 0.5; + transition: 0.5s; +} + +@-webkit-keyframes notyf { + 0% { + transform: translateX(110%); + opacity: 0; + } + + 100% { + transform: translateX(1.5em); + opacity: 1; + } +} + +@keyframes notyf { + 0% { + transform: translateX(110%); + opacity: 0; + } + + 100% { + transform: translateX(1.5em); + opacity: 1; + } +} + +.notyf::before { + font-family:"Font Awesome 5 Free";font-weight:900; + font-style: normal; + font-weight: 900; + line-height: 1; + left: 0.8em; + font-size: 1.5em; + position: absolute; + top: 0; + bottom: 0; + margin: auto; + height: 20px; +} + +.notyf.success { + background: linear-gradient(90deg, rgba(15, 147, 249, 0.7), rgba(61, 189, 249, 0.8)); +} + +.notyf.success::before { + content: '\f058'; +} + +.notyf.info { + background: linear-gradient(90deg, rgba(58, 162, 54, 0.8), rgba(89, 247, 131, 0.8)); +} + +.notyf.info::before { + content: '\f0a1'; +} + +.notyf.warning, +.notyf.load { + background: linear-gradient(90deg, rgba(253, 170, 71, 0.7), rgba(247, 154, 13, 0.8)); +} + +.notyf.warning::before { + content: '\f05a'; +} + +.notyf.danger { + background: linear-gradient(90deg, rgba(253, 69, 28, 0.7), rgba(251, 110, 75, 0.8)); +} + +.notyf.danger::before { + content: '\f057'; +} + +.notyf .fa-close { + float: right; + padding: 0.6em; + margin: -0.4em; +} + +.notyf.load::before { + content: ''; + width: 0.7em; + height: 0.7em; + display: inline-block; + border: 0.08em solid transparent; + border-width: calc(1px + 0.03em); + border-radius: 50%; + border-top-color: var(--this-color); + border-bottom-color: var(--this-color); + -webkit-animation: huan-rotate 1s cubic-bezier(0.7, 0.1, 0.31, 0.9) infinite; + animation: huan-rotate 1s cubic-bezier(0.7, 0.1, 0.31, 0.9) infinite; +} + +@-webkit-keyframes huan-rotate { + 0% { + transform: rotate(0); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes huan-rotate { + 0% { + transform: rotate(0); + } + + 100% { + transform: rotate(360deg); + } } diff --git a/assets/css/core/buttons.css b/assets/css/core/buttons.css index 579eec76..7b3bd8ea 100644 --- a/assets/css/core/buttons.css +++ b/assets/css/core/buttons.css @@ -146,7 +146,7 @@ * Additive: does NOT alter the AKSRT .btn system above. Lets reused * legacy-style markup (.but / .badg / .b-* / .jb-* / .c-* / .but-group / * .but-average / .toggle-radius) render via the --this-* context-coloring vars. - * The --this-* DEFAULTS are set on the base rule here (BEFORE the .b-*/.jb-* + * The --this-* DEFAULTS are set on the base rule here (BEFORE the .b-* and .jb-* * modifiers) so the cascade resolves colored variants correctly; scoped to * .but/.badg (never :root) so .float-btn's own var(--this-*, fallback) stays intact. * Consumes --focus-color-opacity* from compat-vars.css. diff --git a/assets/js/build/public.js b/assets/js/build/public.js new file mode 100644 index 00000000..acde68fa --- /dev/null +++ b/assets/js/build/public.js @@ -0,0 +1,5444 @@ +/* source: assets/js/lazy-image-attr.js */ +/** + * Build lazy-image attributes for dynamically rendered AKSRT markup. + * legacy owns the lazySizes runtime; this file only provides the shared bridge. + */ +(function (window) { + 'use strict'; + + function escapeAttribute(value) { + return String(value == null ? '' : value) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); + } + + window.aksrtGetLazyImageAttr = function (key, src, className, size) { + var config = window.aksrtData || {}; + var scopes = config.lazyScopes || {}; + var classes = String(className || '').trim(); + var realSrc = escapeAttribute(src); + + if (!src || scopes[key] === false) { + return ' class="' + escapeAttribute(classes) + '" src="' + realSrc + '"'; + } + + var thumbs = config.lazyThumbs || {}; + var placeholder = key === 'lazy_avatar' + ? (config.lazyAvatar || thumbs.sm || '') + : (thumbs[size || 'default'] || thumbs.default || ''); + + return ' class="lazyload ' + escapeAttribute(classes) + '" src="' + + escapeAttribute(placeholder) + '" data-src="' + realSrc + '"'; + }; +})(window); +; + +/* source: assets/js/rehydrate.js */ +/** + * AKSRT Rehydrate System + * + * Centralized component initialization and re-initialization system. + * Components register their init functions, and rehydrate() is called + * to initialize or re-initialize them on a given root element. + * + * Usage: + * AksrtFrontend.register('name', initFn); // Register a component + * AksrtFrontend.rehydrate(root, 'init'); // Initialize all components + * + * @package AKSRT + * @since 5.0.0 + */ +(function () { + 'use strict'; + + var _registry = []; + var _initialized = []; + + window.AksrtFrontend = { + + /** + * Register a component's init function. + * + * @param {string} name Unique component name + * @param {Function} initFn Function(root, reason) - root defaults to document + */ + register: function (name, initFn) { + if (typeof name !== 'string' || typeof initFn !== 'function') return; + // Prevent duplicate registrations + for (var i = 0; i < _registry.length; i++) { + if (_registry[i].name === name) { + _registry[i].initFn = initFn; + return; + } + } + _registry.push({ name: name, initFn: initFn }); + }, + + /** + * Initialize or re-initialize all registered components on a root element. + * + * @param {Element|Document} root The root DOM element to search within + * @param {string} reason Why rehydrate is called: + * 'init' = initial page load + * 'ajax' = after AJAX content injection + * 'modal' = after modal content loads + * Custom string for other triggers + */ + rehydrate: function (root, reason) { + root = root || document; + reason = reason || 'init'; + + for (var i = 0; i < _registry.length; i++) { + var entry = _registry[i]; + try { + entry.initFn(root, reason); + } catch (err) { + console.error('[AKSRT Rehydrate] Error initializing "' + entry.name + '":', err); + } + } + + // Dispatch event so external scripts can hook in + try { + root.dispatchEvent(new CustomEvent('aksrt:rehydrated', { + bubbles: true, + detail: { reason: reason } + })); + } catch (e) { /* IE fallback */ } + }, + + /** + * Get the list of registered component names (for debugging). + */ + getRegistered: function () { + return _registry.map(function (r) { return r.name; }); + } + }; + +})(); +; + +/* source: assets/js/notyf.js */ +/** + * legacy-compatible frontend notifications. + * + * @package AKSRT + */ + +(function ($, window, document) { + 'use strict'; + + function closeNotyf($element) { + $element.addClass('notyn-out'); + window.setTimeout(function () { + $element.remove(); + }, 1000); + } + + function notyf(message, type, time, id) { + var $body = $(document.body); + + if (!$('.notyn').length) { + $body.append('
'); + } + + type = type || 'success'; + if (type === 'error') { + type = 'danger'; + } + + time = time || 5000; + time = time < 100 ? time * 1000 : time; + + var idAttribute = id ? ' id="' + id + '"' : ''; + var $notification = $('
' + message + '
'); + var shouldClose = !id; + + if (id && $('#' + id).length) { + $('#' + id) + .find('.notyf') + .removeClass() + .addClass('notyf ' + type) + .html(message); + $notification = $('#' + id); + shouldClose = true; + } else { + $('.notyn').append($notification); + } + + if (shouldClose) { + window.setTimeout(function () { + closeNotyf($notification); + }, time); + } + } + + $(document.body).on('click', '.noty1', function () { + closeNotyf($(this)); + }); + + window.notyf = notyf; +})(jQuery, window, document); +; + +/* source: assets/js/aksrt-runtime.js */ +/** + * AKSRT Runtime Infrastructure + * + * Unified core primitives for the entire frontend: + * aksrt.tbquire(deps, cb) — lazy script/CSS loader + * aksrt.confirm(msg, opts) — i18n confirm dialog + * aksrt.scrollTopTo(target, off) — smart scroll with header offset + * aksrt.loading(el, show) — element-level loading overlay + * aksrt.route(url, title) — push/replace URL + title + * + * Declarative interactions (event delegation on body): + * [data-close] — close target element + * [data-onclick] — trigger click on another element + * [data-confirm] — confirm before action + * [data-action] — universal AJAX action button (like/follow/checkin) + * [ajax-action] — general AJAX button with modal response + * + * @package AKSRT + * @since 5.0.0 + */ +(function () { + 'use strict'; + + var aksrt = window.aksrt || {}; + window.aksrt = aksrt; + + // ══════════════════════════════════════════════════════════════ + // §10.11 — tbquire: lazy script/CSS loader + // ══════════════════════════════════════════════════════════════ + + var _loadedScripts = {}; + var _loadingPromises = {}; + + function loadSingleScript(src) { + if (_loadedScripts[src]) return Promise.resolve(); + if (_loadingPromises[src]) return _loadingPromises[src]; + + _loadingPromises[src] = new Promise(function (resolve, reject) { + var isCSS = /\.css(\?|$)/i.test(src); + var el; + + if (isCSS) { + el = document.createElement('link'); + el.rel = 'stylesheet'; + el.href = src; + } else { + el = document.createElement('script'); + el.src = src; + el.async = true; + } + + el.onload = function () { + _loadedScripts[src] = true; + delete _loadingPromises[src]; + resolve(); + }; + el.onerror = function () { + delete _loadingPromises[src]; + reject(new Error('Failed to load: ' + src)); + }; + + (document.head || document.documentElement).appendChild(el); + }); + + return _loadingPromises[src]; + } + + aksrt.tbquire = function (deps, callback) { + if (typeof deps === 'string') deps = [deps]; + if (!Array.isArray(deps) || !deps.length) { + if (typeof callback === 'function') callback(); + return Promise.resolve(); + } + + var resolvedDeps = deps.map(function (dep) { + if (/^https?:\/\/|^\//.test(dep)) return dep; + var map = (window.aksrtData && aksrtData.tbquireMap) || {}; + return map[dep] || dep; + }); + + var p = Promise.all(resolvedDeps.map(loadSingleScript)); + if (typeof callback === 'function') { + p.then(callback).catch(function (err) { + console.error('[AKSRT tbquire]', err); + }); + } + return p; + }; + + // ══════════════════════════════════════════════════════════════ + // §10.10 — confirm: i18n confirm dialog + [data-confirm] + // ══════════════════════════════════════════════════════════════ + + var _i18n = (window.aksrtData && aksrtData.i18n) || {}; + + function __(key, fallback) { + return _i18n[key] || fallback || key; + } + + aksrt.__ = __; + + aksrt.confirm = function (msg, opts) { + opts = opts || {}; + return new Promise(function (resolve) { + var overlay = document.createElement('div'); + overlay.className = 'aksrt-confirm-overlay'; + + var dialog = document.createElement('div'); + dialog.className = 'aksrt-confirm-dialog'; + dialog.setAttribute('role', 'alertdialog'); + dialog.setAttribute('aria-modal', 'true'); + + dialog.innerHTML = + '
' + escapeHtml(msg) + '
' + + '
' + + '' + + '' + + '
'; + + overlay.appendChild(dialog); + document.body.appendChild(overlay); + + requestAnimationFrame(function () { overlay.classList.add('is-open'); }); + + function close(result) { + overlay.classList.remove('is-open'); + setTimeout(function () { overlay.remove(); }, 200); + resolve(result); + } + + dialog.querySelector('.aksrt-confirm__cancel').addEventListener('click', function () { close(false); }); + dialog.querySelector('.aksrt-confirm__ok').addEventListener('click', function () { close(true); }); + overlay.addEventListener('click', function (e) { if (e.target === overlay) close(false); }); + + dialog.querySelector('.aksrt-confirm__ok').focus(); + }); + }; + + // ══════════════════════════════════════════════════════════════ + // §10.10 — scrollTopTo: smart scroll with header offset + // ══════════════════════════════════════════════════════════════ + + aksrt.scrollTopTo = function (target, extraOffset) { + var top = 0; + if (typeof target === 'number') { + top = target; + } else if (target && target.getBoundingClientRect) { + top = target.getBoundingClientRect().top + window.pageYOffset; + } + + var navFixed = document.querySelector('body.nav-fixed .header, .navbar.navbar-top'); + var headerOffset = navFixed ? (navFixed.offsetHeight + 20) : 0; + top = Math.max(0, top - headerOffset - (extraOffset || 0)); + + var current = window.pageYOffset; + if (Math.abs(top - current) < 80) return; + + window.scrollTo({ top: top, behavior: 'smooth' }); + }; + + // ══════════════════════════════════════════════════════════════ + // §10.10 — loading: element-level loading overlay + // ══════════════════════════════════════════════════════════════ + + aksrt.loading = function (el, show) { + if (!el) return; + if (show === false) { + var mask = el.querySelector('.aksrt-loading-mask'); + if (mask) { + mask.classList.add('aksrt-loading-mask--out'); + setTimeout(function () { mask.remove(); }, 300); + } + return; + } + if (el.querySelector('.aksrt-loading-mask')) return; + var pos = getComputedStyle(el).position; + if (pos === 'static') el.style.position = 'relative'; + + var mask = document.createElement('div'); + mask.className = 'aksrt-loading-mask'; + mask.innerHTML = '' + + '' + + '' + + ''; + el.appendChild(mask); + }; + + // ══════════════════════════════════════════════════════════════ + // §10.11 — route: tab route + popstate + // ══════════════════════════════════════════════════════════════ + + aksrt.route = function (url, title) { + if (!window.history || !history.pushState) return; + history.pushState({ aksrt: true }, title || '', url); + if (title) document.title = title; + }; + + aksrt.routeReplace = function (url, title) { + if (!window.history || !history.replaceState) return; + history.replaceState({ aksrt: true }, title || '', url); + if (title) document.title = title; + }; + + // Tab route: elements with [data-route] push URL on activation + window.addEventListener('popstate', function (e) { + var routeTabs = document.querySelectorAll('[data-route]'); + if (!routeTabs.length) return; + + var path = window.location.pathname + window.location.search; + routeTabs.forEach(function (tab) { + var tabRoute = tab.getAttribute('data-route'); + var isMatch = path === tabRoute || path.indexOf(tabRoute) === 0; + tab.classList.toggle('is-active', isMatch); + var target = tab.getAttribute('data-route-target'); + if (target) { + var panel = document.querySelector(target); + if (panel) panel.classList.toggle('is-active', isMatch); + } + }); + }); + + // ══════════════════════════════════════════════════════════════ + // §10.11 — Declarative interactions (event delegation) + // ══════════════════════════════════════════════════════════════ + + document.addEventListener('click', function (e) { + var target = e.target.closest('[data-close]'); + if (target) { + e.preventDefault(); + var sel2 = target.getAttribute('data-close'); + var el2 = sel2 ? document.querySelector(sel2) : target.parentElement; + if (el2) { + el2.classList.remove('is-open', 'is-active', 'is-visible'); + el2.hidden = true; + } + return; + } + + target = e.target.closest('[data-onclick]'); + if (target) { + e.preventDefault(); + var sel3 = target.getAttribute('data-onclick'); + var el3 = document.querySelector(sel3); + if (el3) el3.click(); + return; + } + }); + + // [data-confirm] — intercept submission + document.addEventListener('click', function (e) { + var target = e.target.closest('[data-confirm]'); + if (!target) return; + + var msg = target.getAttribute('data-confirm'); + if (!msg) return; + + e.preventDefault(); + e.stopPropagation(); + + aksrt.confirm(msg).then(function (ok) { + if (!ok) return; + target.removeAttribute('data-confirm'); + target.click(); + target.setAttribute('data-confirm', msg); + }); + }, true); + + // ══════════════════════════════════════════════════════════════ + // §10.10a — 拦截 vendor 评论脚本 & 解绑 vendor ajax 委托 + // ══════════════════════════════════════════════════════════════ + // vendor 链路:loader.js(tbquire,改造版 RequireJS)→ main.min.js 检测到 + // #commentform/#postcomments 即 tbquire(["comment"]) 懒加载 comment.min.js。 + // AKSRT 自有评论脚本(comments/comment-tools/comment-fixed-input/ + // comment-move-form/comment-ajax)已承担全部行为,vendor comment.min.js 的 + // 7 条 body 委托会双路触发(提交 legacy action=submit_comment 无处理器, + // 即控制台「ajax请求错误」400 "0" 的来源;回复/排序/分页同理)。 + // + // 双层防御: + // 1) tbfine 预注册空 'comment' 模块 → tbquire 不再下载 comment.min.js; + // 2) 若模块抢在注册前加载,退化为对 7 条委托做幂等 .off。 + function stubVendorCommentModule() { + if (!(window.tbfine && window.tbquire && window.tbquire.defined)) return false; + try { + if (window.tbquire.defined('comment')) return true; + window.tbfine('comment', [], function () { return {}; }); + return true; + } catch (e) { + return false; + } + } + + // main.min.js 的 body 级 legacy 委托同理摘除(vendor 只保留 UI 行为)。 + function unbindVendorAjaxDelegates() { + stubVendorCommentModule(); + if (!window.jQuery) return; + try { + var $body = jQuery('body'); + $body.off('click', '[data-action]'); + $body.off('click', '[ajax-action]'); + $body.off('click', '.wp-ajax-submit,[zibajax="submit"]'); + $body.off('click', '.initiate-checkin'); + $body.off('click', '.oauth-untying'); + // comment.min.js 的 7 条委托(legacy/js/comment.js 原始选择器) + $body.off('click', '[fixed-input]'); + $body.off('click', '.commentlist .pagenav a,.comment-orderby'); + $body.off('click', '.comment-edit-link,.comment-reply-link'); + $body.off('keydown', '#comment'); + $body.off('click', '.comment-trash-link'); + $body.off('click', '.comment-approve-link'); + $body.off('click', '#commentform #submit'); + } catch (e) { /* vendor 运行时未就绪时忽略 */ } + } + var unbindVendorAjaxLoop = function () { + unbindVendorAjaxDelegates(); + if (++unbindVendorAjaxLoop.tries >= 50) return; // ~10s @200ms 覆盖异步加载窗口 + setTimeout(unbindVendorAjaxLoop, 200); + }; + unbindVendorAjaxLoop.tries = 0; + unbindVendorAjaxLoop(); + + // ══════════════════════════════════════════════════════════════ + // §10.11 — [data-action] / [ajax-action] universal buttons + // ══════════════════════════════════════════════════════════════ + + // Only these data-action values are network AJAX. UI-only actions + // (toggle-theme, editor toolbar, download, etc.) must not hit admin-ajax. + var DATA_ACTION_AJAX = { + like: true, + favorite: true, + follow: true, + checkin: true + }; + + document.addEventListener('click', function (e) { + // [data-action] — like/follow/checkin with localStorage de-dup + var btn = e.target.closest('[data-action]'); + if (btn) { + var dataAction = btn.getAttribute('data-action') || ''; + if (!DATA_ACTION_AJAX[dataAction]) { + // Leave non-AJAX data-action handlers (theme toggle, toolbar, download) alone. + return; + } + e.preventDefault(); + handleDataAction(btn); + return; + } + + // [ajax-action] — general AJAX button + btn = e.target.closest('[ajax-action]'); + if (btn) { + e.preventDefault(); + handleAjaxAction(btn); + return; + } + }); + + function handleDataAction(btn) { + if (btn.classList.contains('is-loading')) return; + + var action = btn.getAttribute('data-action'); + if (!action || !DATA_ACTION_AJAX[action]) return; + + var id = btn.getAttribute('data-id') || ''; + var storageKey = 'aksrt_action_' + action + '_' + id; + + if (action === 'like' || action === 'favorite') { + try { + if (localStorage.getItem(storageKey) && !btn.classList.contains('is-active')) { + notyf(__('already_done', '已操作'), 'warning'); + return; + } + } catch (ex) { /* storage unavailable */ } + } + + btn.classList.add('is-loading'); + + var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php'; + if (!(window.aksrtData && aksrtData.nonce)) { + btn.classList.remove('is-loading'); + notyf(__('action_failed', '操作失败'), 'danger'); + return; + } + var params = new URLSearchParams(); + params.set('action', 'aksrt_' + action); + params.set('nonce', aksrtData.nonce); + params.set('id', id); + + var extraData = btn.getAttribute('data-action-data'); + if (extraData) { + try { + var ed = JSON.parse(extraData); + Object.keys(ed).forEach(function (k) { params.set(k, ed[k]); }); + } catch (ex) { /* ignore */ } + } + + fetch(ajaxUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, + credentials: 'same-origin', + body: params.toString() + }) + .then(function (r) { return r.json(); }) + .then(function (json) { + btn.classList.remove('is-loading'); + if (json && json.success) { + var isActive = !!json.data && !!json.data.active; + btn.classList.toggle('is-active', isActive); + + var countEl = btn.querySelector('[data-count]') || btn.querySelector('.action-count'); + if (countEl && json.data && json.data.count !== undefined) { + countEl.textContent = String(json.data.count); + } + + try { + if (isActive) localStorage.setItem(storageKey, '1'); + else localStorage.removeItem(storageKey); + } catch (ex) { /* */ } + + if (json.data && json.data.message) { + notyf(json.data.message, 'success'); + } + + if (json.data && json.data.goto) { + window.location.href = json.data.goto; + } + } else { + notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger'); + } + }) + .catch(function (err) { + btn.classList.remove('is-loading'); + notyf(err.message || __('network_error', '网络错误'), 'danger'); + }); + } + + function handleAjaxAction(btn) { + if (btn.classList.contains('is-loading')) return; + btn.classList.add('is-loading'); + + var action = btn.getAttribute('ajax-action'); + var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php'; + var params = new URLSearchParams(); + params.set('action', action); + if (window.aksrtData && aksrtData.nonce) params.set('nonce', aksrtData.nonce); + + var form = btn.closest('form'); + if (form) { + new FormData(form).forEach(function (v, k) { params.set(k, v); }); + } + + fetch(ajaxUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, + credentials: 'same-origin', + body: params.toString() + }) + .then(function (r) { return r.json(); }) + .then(function (json) { + btn.classList.remove('is-loading'); + if (json && json.success) { + if (json.data && json.data.show_modal) { + window.AksrtModalRuntime.open({ + html: json.data.show_modal, + bottom: true, + mobileBottom: true, + kind: 'generic', + }); + } else if (json.data && json.data.message) { + notyf(json.data.message, 'success'); + } + if (json.data && json.data.goto) { + window.location.href = json.data.goto; + } + if (json.data && json.data.reload) { + window.location.reload(); + } + } else { + notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger'); + } + }) + .catch(function (err) { + btn.classList.remove('is-loading'); + notyf(err.message || __('network_error', '网络错误'), 'danger'); + }); + } + + // ══════════════════════════════════════════════════════════════ + // §10.11 — Ripple (broad coverage) + // ══════════════════════════════════════════════════════════════ + + var RIPPLE_SELECTORS = '.toggle-radius,.but-ripple,.but,.btn,.btn-primary,.btn-ghost,.btn-outline,.item-thumbnail,.menu-item>a,.aksrt-ripple,.yiyan-box,.aksrt-card'; + + document.addEventListener('click', function (e) { + var target = e.target.closest(RIPPLE_SELECTORS); + if (!target) return; + // Markup opt-out (header publish uses class="nowave") + nav CTA chips + if (target.classList.contains('nowave') || target.closest('.nowave')) return; + if (target.classList.contains('navbar-cta') || target.closest('.navbar-cta')) return; + if (target.closest('.navbar-top .navbar-but, .navbar-top .navbar-vip, .navbar-but-cluster')) return; + if (target.querySelector('.aksrt-wave')) return; + + var rect = target.getBoundingClientRect(); + var size = Math.max(rect.width, rect.height) * 2; + var x = e.clientX - rect.left - size / 2; + var y = e.clientY - rect.top - size / 2; + + var wave = document.createElement('span'); + wave.className = 'aksrt-wave'; + // Must be absolute — without it the huge span expands the button box ("变长"). + wave.style.cssText = 'position:absolute;pointer-events:none;border-radius:50%;background:rgba(255,255,255,0.35);width:' + + size + 'px;height:' + size + 'px;left:' + x + 'px;top:' + y + 'px;transform:scale(0);animation:aksrtWaveRipple .6s ease-out forwards;'; + + var cs = getComputedStyle(target); + if (cs.position === 'static') target.style.position = 'relative'; + if (cs.overflow === 'visible') target.style.overflow = 'hidden'; + + target.appendChild(wave); + setTimeout(function () { + if (wave.parentNode) wave.parentNode.removeChild(wave); + }, 600); + }); + + // ══════════════════════════════════════════════════════════════ + // §10.11 — auto_fun: extended rehydration coverage + // ══════════════════════════════════════════════════════════════ + + aksrt.autoFun = function (root) { + root = root || document; + if (window.AksrtFrontend) { + AksrtFrontend.rehydrate(root, 'ajax'); + } + + // Tooltips + root.querySelectorAll('[data-tooltip]').forEach(function (el) { + if (el.dataset.tooltipBound) return; + el.dataset.tooltipBound = '1'; + el.addEventListener('mouseenter', function () { + var tip = document.createElement('div'); + tip.className = 'aksrt-tooltip'; + tip.textContent = el.getAttribute('data-tooltip'); + document.body.appendChild(tip); + var rect = el.getBoundingClientRect(); + tip.style.left = (rect.left + rect.width / 2 - tip.offsetWidth / 2) + 'px'; + tip.style.top = (rect.top - tip.offsetHeight - 8 + window.pageYOffset) + 'px'; + el._aksrtTooltip = tip; + }); + el.addEventListener('mouseleave', function () { + if (el._aksrtTooltip) { el._aksrtTooltip.remove(); el._aksrtTooltip = null; } + }); + }); + + // Countdowns + root.querySelectorAll('[data-countdown]').forEach(function (el) { + if (el.dataset.countdownBound) return; + el.dataset.countdownBound = '1'; + var end = parseInt(el.getAttribute('data-countdown'), 10); + if (!end) return; + var iv = setInterval(function () { + var left = Math.max(0, end - Math.floor(Date.now() / 1000)); + if (left <= 0) { clearInterval(iv); el.textContent = '00:00'; return; } + var m = Math.floor(left / 60); + var s = left % 60; + el.textContent = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; + }, 1000); + }); + }; + + // ══════════════════════════════════════════════════════════════ + // Helpers + // ══════════════════════════════════════════════════════════════ + + function escapeHtml(str) { + if (!str) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + // ── DOMContentLoaded ── + + function initRuntime() { + aksrt.autoFun(document); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initRuntime); + } else { + initRuntime(); + } + +})(); +; + +/* source: assets/js/grade.min.js */ +(function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Grade=e()}})(function(){return function e(t,n,r){function i(o,s){if(!n[o]){if(!t[o]){var u="function"==typeof require&&require;if(!s&&u)return u(o,!0);if(a)return a(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var g=n[o]={exports:{}};t[o][0].call(g.exports,function(e){var n=t[o][1][e];return i(n||e)},g,g.exports,e,t,n,r)}return n[o].exports}for(var a="function"==typeof require&&require,o=0;o20});return(!n||n.length<2)&&(n=t.filter(function(e){return e.slice(0,2).every(function(e){return e<245})&&e.slice(0,2).every(function(e){return e>10})})),n}},{key:"getRGBAGradientValues",value:function(e){return e.map(function(e,t){return"rgb("+e.rgba.slice(0,3).join(",")+") "+(0==t?"0%":"75%")}).join(",")}},{key:"getCSSGradientProperty",value:function(e){var t=this.getRGBAGradientValues(e);return a.map(function(e){return"background-image: -"+e+"-linear-gradient(\n 135deg,\n "+t+"\n )"}).concat(["background-image: linear-gradient(\n 135deg,\n "+t+"\n )"]).join(";")}},{key:"getMiddleRGB",value:function(e,t){var n=0,r=(n+1)/2,i=1-r,a=[parseInt(e[0]*r+t[0]*i),parseInt(e[1]*r+t[1]*i),parseInt(e[2]*r+t[2]*i)];return a}},{key:"getSortedValues",value:function(e){var t=Object.keys(e).map(function(t){var n=t,r=t.split("|"),i=(299*r[0]+587*r[1]+114*r[2])/1e3;return{rgba:n.split("|"),occurs:e[t],brightness:i}}).sort(function(e,t){return e.occurs-t.occurs}).reverse().slice(0,10);return t.sort(function(e,t){return e.brightness-t.brightness}).reverse()}},{key:"getTextProperty",value:function(e){var t=this.getMiddleRGB(e[0].rgba.slice(0,3),e[1].rgba.slice(0,3)),n=Math.round((299*parseInt(t[0])+587*parseInt(t[1])+114*parseInt(t[2]))/1e3);return n>125?"color: #000":"color: #fff"}},{key:"getTopValues",value:function(e){var t=this.getSortedValues(e);return[t[0],t[t.length-1]]}},{key:"getUniqValues",value:function(e){return e.reduce(function(e,t){var n=t.join("|");return e[n]?(e[n]=++e[n],e):(e[n]=1,e)},{})}},{key:"renderGradient",value:function(){var e=window.localStorage,t="grade-"+this.image.getAttribute("src"),n=null;if(e&&e.getItem(t)&&(n=JSON.parse(e.getItem(t))),!n||!n[0]){var r=this.getChunkedImageData();n=this.getTopValues(this.getUniqValues(r)),e&&e.setItem(t,JSON.stringify(n))}if(this.callback)return this.gradientData=n,void this.callback([{element:this.container,gradientData:n}]);var i=this.getCSSGradientProperty(n),a=this.getTextProperty(n),o=(this.container.getAttribute("style")||"")+"; "+i+"; "+a;this.container.setAttribute("style",o)}},{key:"render",value:function(){this.canvas.width=this.imageDimensions.width,this.canvas.height=this.imageDimensions.height,this.ctx.drawImage(this.image,0,0,this.imageDimensions.width,this.imageDimensions.height),this.getImageData(),this.renderGradient()}}]),e}();t.exports=function(e,t,n){var r=function(e,t,n){var r=new o(e,t,n),i=r.gradientData;return i.length?{element:e,gradientData:i}:null},i=(NodeList.prototype.isPrototypeOf(e)?Array.from(e).map(function(e){return r(e,t,n)}):[r(e,t,n)]).filter(Boolean);if(i.length)return n(i)}},{}]},{},[1])(1)}); +; + +/* source: assets/js/core/modal-telemetry.js */ +/** + * Frontend modal performance marks. + * + * Records only bounded domain/event names and timing data. Never include + * payment credentials, order contents, identity evidence, or user payloads. + * + * @package AKSRT + */ +(function (window) { + 'use strict'; + + var allowedKinds = { + identity: true, + badges: true, + orders: true, + vip: true, + }; + var allowedEvents = { + click: true, + shell: true, + 'first-content': true, + ready: true, + 'action-start': true, + 'action-end': true, + unauthorized: true, + error: true, + }; + var sessions = Object.create(null); + var sequence = 0; + var maxSessions = 100; + + function normalize(value, fallback) { + var normalized = String(value || fallback || '').toLowerCase().replace(/[^a-z0-9-]/g, ''); + return normalized || fallback; + } + + function now() { + return window.performance && typeof window.performance.now === 'function' + ? window.performance.now() + : 0; + } + + function markName(session, event) { + return 'aksrt-modal-' + session.kind + '-' + session.id + '-' + event; + } + + function emit(session, event) { + if (!window.performance || typeof window.performance.mark !== 'function') return; + + var name = markName(session, event); + window.performance.mark(name); + session.marks[event] = name; + + if (typeof window.performance.measure === 'function' && session.marks.click && event !== 'click') { + try { + window.performance.measure( + 'aksrt-modal-' + session.kind + '-' + session.id + '-click-to-' + event, + session.marks.click, + name + ); + } catch (error) { + // A missing mark must never affect the modal interaction. + } + } + } + + function start(kind) { + var safeKind = normalize(kind, 'unknown'); + if (!allowedKinds[safeKind]) safeKind = 'unknown'; + + sequence += 1; + var id = String(sequence); + var session = { + id: id, + kind: safeKind, + startedAt: now(), + marks: Object.create(null), + }; + + sessions[id] = session; + emit(session, 'click'); + + var ids = Object.keys(sessions); + while (ids.length > maxSessions) { + delete sessions[ids.shift()]; + } + + return id; + } + + function mark(id, event) { + var session = sessions[String(id || '')]; + var safeEvent = normalize(event, 'error'); + if (!session || !allowedEvents[safeEvent]) return; + emit(session, safeEvent); + } + + function finish(id) { + mark(id, 'ready'); + } + + function fail(id) { + mark(id, 'error'); + } + + window.AksrtModalTelemetry = { + start: start, + mark: mark, + finish: finish, + error: fail, + }; +})(window); +; + +/* source: assets/js/core/request.js */ +/** + * AKSRT frontend request adapter. + * + * Owns the injected REST root, nonce headers, response envelopes, and + * abort-aware error normalization used by modal loaders. + * + * @package AKSRT + */ +(function (window) { + "use strict"; + + function data() { + return window.aksrtData || window.aksrt || {}; + } + + function createIdempotencyKey() { + if (window.crypto && typeof window.crypto.randomUUID === "function") { + return window.crypto.randomUUID(); + } + if (!window.crypto || typeof window.crypto.getRandomValues !== "function") { + throw new Error("当前浏览器无法生成安全的操作标识"); + } + var bytes = new Uint8Array(16); + window.crypto.getRandomValues(bytes); + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + var hex = Array.prototype.map.call(bytes, function (value) { + return value.toString(16).padStart(2, "0"); + }).join(""); + return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20); + } + + function restBase() { + var config = data(); + var raw = config.restUrl || config.apiUrl || config.rest_url || ""; + var siteUrl = config.siteUrl || config.homeUrl || (window.location.origin + "/"); + + if (window.location.protocol === "https:" && siteUrl.substring(0, 5) === "http:") { + siteUrl = "https:" + siteUrl.substring(5); + } + + if (raw) return String(raw).replace(/\/$/, ""); + return String(siteUrl).replace(/\/$/, "") + "/index.php?rest_route=/aksrt/v1"; + } + + function appendQuery(url, query) { + if (!query) return url; + var parts = []; + + if (typeof query === "string") { + parts = query.replace(/^\?/, "").split("&").filter(Boolean); + } else if (typeof query === "object") { + Object.keys(query).forEach(function (key) { + if (query[key] === undefined || query[key] === null) return; + parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(query[key]))); + }); + } + + if (!parts.length) return url; + return url + (url.indexOf("?") >= 0 ? "&" : "?") + parts.join("&"); + } + + function url(path, query) { + path = String(path || ""); + var inlineQuery = ""; + var queryIndex = path.indexOf("?"); + + if (queryIndex >= 0) { + inlineQuery = path.slice(queryIndex + 1); + path = path.slice(0, queryIndex); + } + + if (/^https?:\/\//i.test(path)) { + return appendQuery(path, query || inlineQuery); + } + + if (path.charAt(0) !== "/") path = "/" + path; + var base = restBase(); + var result = base.indexOf("rest_route=") >= 0 ? base + path : base + path; + return appendQuery(result, query || inlineQuery); + } + + function headers(options) { + var config = data(); + var source = (options && options.headers) || {}; + var result = {}; + var nonce = config.restNonce || config.nonce || ""; + + Object.keys(source).forEach(function (key) { + result[key] = source[key]; + }); + + if (!result.Accept) result.Accept = "application/json, text/html;q=0.9, */*;q=0.8"; + if (nonce && !result["X-WP-Nonce"]) result["X-WP-Nonce"] = nonce; + + if (options && options.body !== undefined && options.body !== null && + !(window.FormData && options.body instanceof window.FormData) && + !result["Content-Type"]) { + result["Content-Type"] = "application/json"; + } + + return result; + } + + function responseError(result) { + var json = result.json; + var message = json && (json.message || (json.data && json.data.message)); + var error = new Error(message || (result.status ? "请求失败(HTTP " + result.status + ")" : "请求失败")); + error.status = result.status; + error.code = result.status === 401 ? "unauthorized" : "http_error"; + error.response = result; + error.json = json; + return error; + } + + function parseResponse(response) { + return response.text().then(function (text) { + var type = (response.headers.get("content-type") || "").toLowerCase(); + var json = null; + var trimmed = String(text || "").trim(); + + if (type.indexOf("application/json") >= 0 || trimmed.charAt(0) === "{" || trimmed.charAt(0) === "[") { + try { + json = trimmed ? JSON.parse(trimmed) : {}; + } catch (error) { + json = null; + } + } + + return { + ok: response.ok, + status: response.status, + headers: response.headers, + text: text, + json: json, + data: json && json.data !== undefined ? json.data : json, + }; + }); + } + + function request(path, options) { + options = Object.assign({}, options || {}); + var requestUrl = url(path, options.query); + delete options.query; + + var fetchOptions = Object.assign({}, options, { + credentials: options.credentials || "same-origin", + headers: headers(options), + }); + + return window.fetch(requestUrl, fetchOptions).then(function (response) { + return parseResponse(response); + }).then(function (result) { + if (!result.ok || (result.json && result.json.success === false)) { + throw responseError(result); + } + return result; + }); + } + + window.AksrtRequest = { + url: url, + request: request, + createIdempotencyKey: createIdempotencyKey, + get: function (path, options) { + return request(path, Object.assign({}, options || {}, { method: "GET" })); + }, + post: function (path, body, options) { + return request(path, Object.assign({}, options || {}, { + method: "POST", + body: body, + })); + }, + }; +})(window); +; + +/* source: assets/js/core/modal-cache.js */ +/** + * User-scoped in-memory modal cache with short TTLs and request deduplication. + * Private modal payloads are never persisted to browser storage. + * + * @package AKSRT + */ +(function (window, document) { + "use strict"; + + var DEFAULT_TTL = 15000; + var entries = Object.create(null); + var flights = Object.create(null); + var lastScope = ""; + + function scope() { + var config = window.aksrtData || window.aksrt || {}; + var userId = parseInt(config.userId || 0, 10) || 0; + var current = (config.isLoggedIn && userId > 0 ? "user:" + userId : "guest") + ":v1"; + if (lastScope && lastScope !== current) clear(); + lastScope = current; + return current; + } + + function token(key) { + return scope() + "::" + String(key || ""); + } + + function read(key) { + var id = token(key); + var entry = entries[id]; + if (!entry) return { hit: false, value: undefined }; + if (entry.expiresAt <= Date.now()) { + delete entries[id]; + return { hit: false, value: undefined }; + } + return { hit: true, value: entry.value }; + } + + function tagsOf(value) { + return Array.isArray(value) ? value.slice() : (value ? [value] : []); + } + + function set(key, value, options) { + options = options || {}; + var ttl = Number(options.ttl || DEFAULT_TTL); + entries[token(key)] = { + value: value, + expiresAt: Date.now() + Math.max(0, ttl), + tags: tagsOf(options.tags), + }; + return value; + } + + function matchesTags(entry, wanted) { + if (!wanted.length) return true; + return wanted.some(function (tag) { return entry.tags.indexOf(tag) >= 0; }); + } + + function invalidate(tags) { + var wanted = Array.isArray(tags) ? tags : (tags ? [tags] : []); + var removed = 0; + Object.keys(entries).forEach(function (id) { + if (!wanted.length || matchesTags(entries[id], wanted)) { + delete entries[id]; + removed += 1; + } + }); + Object.keys(flights).forEach(function (id) { + var flight = flights[id]; + if (!wanted.length || matchesTags(flight, wanted)) { + flight.cancelled = true; + delete flights[id]; + if (typeof flight.cancel === "function") flight.cancel(); + } + }); + return removed; + } + + function clear() { + entries = Object.create(null); + Object.keys(flights).forEach(function (id) { + var flight = flights[id]; + flight.cancelled = true; + if (typeof flight.cancel === "function") flight.cancel(); + }); + flights = Object.create(null); + } + + function acquire(key, loader, options) { + options = options || {}; + var id = token(key); + var cached = options.skipCache ? { hit: false } : read(key); + + if (cached.hit) { + return { + cached: true, + promise: Promise.resolve(cached.value), + release: function () {}, + }; + } + + var flight = flights[id]; + if (!flight) { + flight = { + refs: 0, + settled: false, + cancelled: false, + tags: tagsOf(options.tags), + cancel: options.cancel, + }; + flight.promise = Promise.resolve().then(loader).then(function (value) { + flight.settled = true; + if (!flight.cancelled && options.store !== false) set(key, value, options); + if (flights[id] === flight) delete flights[id]; + return value; + }, function (error) { + flight.settled = true; + if (flights[id] === flight) delete flights[id]; + throw error; + }); + flights[id] = flight; + } + + flight.refs += 1; + var released = false; + return { + cached: false, + promise: flight.promise, + release: function () { + if (released) return; + released = true; + flight.refs = Math.max(0, flight.refs - 1); + if (flight.refs === 0 && !flight.settled) { + flight.cancelled = true; + if (flights[id] === flight) delete flights[id]; + if (typeof flight.cancel === "function") flight.cancel(); + } + }, + }; + } + + ["aksrt:auth:logout", "aksrt:account-switch", "aksrt:logout", "aksrt:user:logout", "aksrt:auth:changed", "wp_logout"].forEach(function (eventName) { + document.addEventListener(eventName, clear); + }); + + window.AksrtModalCache = { + get: function (key) { return read(key); }, + set: set, + acquire: acquire, + invalidate: invalidate, + clear: clear, + scope: scope, + }; +})(window, document); +; + +/* source: assets/js/core/modal-state.js */ +/** + * Shared modal state vocabulary. + * + * @package AKSRT + */ +(function (window) { + "use strict"; + + var statuses = { + idle: "idle", + loading: "loading", + ready: "ready", + empty: "empty", + submitting: "submitting", + success: "success", + error: "error", + unauthorized: "unauthorized", + stale: "stale", + }; + + function create(kind) { + return { + kind: String(kind || "generic"), + status: statuses.idle, + updatedAt: 0, + error: null, + }; + } + + window.AksrtModalState = { + statuses: statuses, + create: create, + }; +})(window); +; + +/* source: assets/js/core/modal-runtime.js */ +/** + * Canonical frontend modal runtime. + * + * Owns the modal shell while adding cancellable loaders, + * short in-memory caching, request deduplication, retryable errors, focus + * restoration, and modal-scoped rehydration. + * + * @package AKSRT + */ +(function (window, document) { + "use strict"; + + var ROOT_ID = "aksrt-modal-runtime-root"; + var active = null; + var sequence = 0; + var loaders = Object.create(null); + + function isMobile() { + return window.matchMedia && window.matchMedia("(max-width: 768px)").matches; + } + + function ensureRoot() { + var root = document.getElementById(ROOT_ID); + if (root) return root; + root = document.createElement("div"); + root.id = ROOT_ID; + root.className = "aksrt-modal-runtime-host"; + document.body.appendChild(root); + return root; + } + + function escapeHtml(value) { + return String(value == null ? "" : value) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\"/g, """) + .replace(/'/g, "'"); + } + + function lockScroll(lock) { + document.documentElement.classList.toggle("aksrt-modal-runtime-open", !!lock); + document.body.classList.toggle("aksrt-modal-runtime-open", !!lock); + } + + function skeleton(options) { + return (options && options.skeleton) || + '
' + + '' + + '' + + '' + + '' + + '
'; + } + + function telemetryStart(kind) { + if (kind === false) return ""; + return window.AksrtModalTelemetry.start(kind || "generic"); + } + + function telemetryMark(session, event) { + if (!session || !session.telemetryId) return; + window.AksrtModalTelemetry.mark(session.telemetryId, event); + } + + function setState(session, status, error) { + session.state.status = status; + session.state.error = error || null; + session.state.updatedAt = Date.now(); + if (session.shell) session.shell.setAttribute("data-aksrt-modal-state", status); + if (typeof session.options.onState === "function") { + session.options.onState(status, session); + } + } + + function focusable(shell) { + return shell.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])"); + } + + function closeSelector() { + return "[data-aksrt-modal-close], .aksrt-modal-runtime__close"; + } + + function bindChrome(session) { + var shell = session.shell; + shell.addEventListener("click", function (event) { + var closeButton = event.target.closest(closeSelector()); + if (closeButton) { + event.preventDefault(); + close("control"); + return; + } + if (event.target === shell || event.target.classList.contains("aksrt-modal-runtime__overlay")) { + close("overlay"); + return; + } + var retry = event.target.closest("[data-aksrt-modal-retry]"); + if (retry) { + event.preventDefault(); + refresh(); + } + }); + + shell.addEventListener("keydown", function (event) { + if (event.key === "Escape") { + event.preventDefault(); + close("escape"); + return; + } + if (event.key !== "Tab") return; + var nodes = focusable(shell); + if (!nodes.length) { + event.preventDefault(); + shell.focus(); + return; + } + var first = nodes[0]; + var last = nodes[nodes.length - 1]; + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + }); + } + + function createSession(options) { + options = options || {}; + var root = ensureRoot(); + var shell = document.createElement("div"); + var bottom = options.bottom === true || (options.mobileBottom !== false && isMobile()); + var heightStyle = options.height ? "min-height:" + parseInt(options.height, 10) + "px;" : ""; + var initialHtml = options.html !== undefined ? options.html : skeleton(options); + var titleId = "aksrt-modal-title-" + String(sequence + 1); + var titleHtml = options.title + ? '

' + escapeHtml(options.title) + '

' + : ""; + + shell.className = + "aksrt-modal-runtime" + + (bottom ? " aksrt-modal-runtime--bottom" : "") + + (options.className ? " " + options.className : ""); + shell.setAttribute("role", "dialog"); + shell.setAttribute("aria-modal", "true"); + if (options.title) shell.setAttribute("aria-labelledby", titleId); + shell.setAttribute("tabindex", "-1"); + shell.innerHTML = + '
' + + '
' + + titleHtml + + '
' + initialHtml + "
"; + + var session = { + id: String(++sequence), + shell: shell, + content: shell.querySelector(".aksrt-modal-runtime__content"), + options: options, + state: window.AksrtModalState.create(options.kind || "generic"), + focusReturn: document.activeElement, + controller: null, + flight: null, + closed: false, + telemetryId: telemetryStart(options.telemetry === false ? false : options.kind), + }; + + root.appendChild(shell); + bindChrome(session); + active = session; + lockScroll(true); + void shell.offsetWidth; + shell.classList.add("is-open"); + setState(session, options.html !== undefined ? "ready" : "loading"); + telemetryMark(session, "shell"); + + var nodes = focusable(shell); + try { + (nodes.length ? nodes[0] : shell).focus(); + } catch (error) {} + + document.dispatchEvent(new CustomEvent("aksrt:modal:open", { + detail: { shell: shell, options: options, session: session }, + })); + + if (options.html !== undefined) { + rehydrate(session); + telemetryMark(session, "first-content"); + telemetryMark(session, "ready"); + if (typeof options.onContent === "function") options.onContent(options.html, session); + } + + return session; + } + + function rehydrate(session) { + if (!session || !session.content) return; + window.AksrtFrontend.rehydrate(session.content, "modal"); + } + + function abortSessionRequest(session) { + if (!session) return; + if (session.flight && typeof session.flight.release === "function") session.flight.release(); + session.flight = null; + if (session.controller) { + try { session.controller.abort(); } catch (error) {} + } + session.controller = null; + } + + function close(reason, preserveRequest) { + if (!active) return; + var session = active; + active = null; + session.closed = true; + if (!preserveRequest) abortSessionRequest(session); + session.shell.classList.remove("is-open"); + if (!preserveRequest) lockScroll(false); + document.dispatchEvent(new CustomEvent("aksrt:modal:close", { + detail: { shell: session.shell, reason: reason || "close", session: session }, + })); + + var node = session.shell; + window.setTimeout(function () { + if (node && node.parentNode) node.parentNode.removeChild(node); + var target = session.focusReturn; + if (!preserveRequest && !active && target && target !== node && document.contains(target) && typeof target.focus === "function") { + try { target.focus(); } catch (error) {} + } + }, 220); + } + + function responseData(result) { + if (!result) return null; + if (result.data !== undefined && result.data !== null) return result.data; + if (result.json && result.json.data !== undefined) return result.json.data; + return result.json; + } + + function responseHtml(result) { + var data = responseData(result); + if (data && typeof data.html === "string") return data.html; + if (result && result.json && typeof result.json.html === "string") return result.json.html; + return result && typeof result.text === "string" ? result.text : ""; + } + + function errorMessage(error) { + if (error && error.code === "unauthorized") return "请先登录"; + return error && error.message ? String(error.message) : "加载失败,请重试"; + } + + function renderError(session, error) { + if (!session || session.closed || active !== session) return; + var unauthorized = error && error.code === "unauthorized"; + setState(session, unauthorized ? "unauthorized" : "error", error); + telemetryMark(session, unauthorized ? "unauthorized" : "error"); + session.content.innerHTML = + '
提示
' + + '
' + escapeHtml(errorMessage(error)) + + '
'; + rehydrate(session); + if (typeof session.options.onError === "function") session.options.onError(error, session); + } + + function renderResult(session, result) { + if (!session || session.closed || active !== session) return; + + if (session.options.responseType === "json" || typeof session.options.renderData === "function") { + var data = responseData(result); + if (data === null || data === undefined) { + setState(session, "empty"); + telemetryMark(session, "first-content"); + telemetryMark(session, "ready"); + session.content.innerHTML = '
暂无内容
'; + return; + } + if (typeof session.options.renderData === "function") { + session.options.renderData(data, session, result); + } + setState(session, "ready"); + telemetryMark(session, "first-content"); + rehydrate(session); + telemetryMark(session, "ready"); + if (typeof session.options.onData === "function") session.options.onData(data, session, result); + return; + } + + var html = responseHtml(result); + if (!html) { + setState(session, "empty"); + telemetryMark(session, "first-content"); + telemetryMark(session, "ready"); + session.content.innerHTML = '
暂无内容
'; + return; + } + session.content.innerHTML = html; + setState(session, "ready"); + telemetryMark(session, "first-content"); + rehydrate(session); + telemetryMark(session, "ready"); + if (typeof session.options.onContent === "function") session.options.onContent(html, session); + } + + function load(session, loadOptions) { + loadOptions = loadOptions || {}; + if (!session || session.closed || !session.options.url) return Promise.resolve(session && session.shell); + abortSessionRequest(session); + setState(session, "loading"); + session.content.innerHTML = skeleton(session.options); + session.controller = window.AbortController ? new window.AbortController() : null; + + var requestOptions = { + method: session.options.method || "GET", + headers: session.options.headers || {}, + body: session.options.body, + query: session.options.query, + signal: session.controller ? session.controller.signal : undefined, + }; + if (requestOptions.body === undefined) delete requestOptions.body; + if (!requestOptions.signal) delete requestOptions.signal; + + var request = function () { + return window.AksrtRequest.request(session.options.url, requestOptions); + }; + + var method = String(session.options.method || "GET").toUpperCase(); + var cacheable = method === "GET" && session.options.cache !== false; + var cacheKey = session.options.cacheKey || + method + ":" + window.AksrtRequest.url(session.options.url, session.options.query); + var cache = window.AksrtModalCache; + var acquireOptions = { + ttl: Number(session.options.cacheTtl || 15000), + tags: session.options.cacheTags || session.options.tags || [], + skipCache: !!loadOptions.force || !cacheable, + store: cacheable, + cancel: function () { + if (session.controller) { + try { session.controller.abort(); } catch (error) {} + } + }, + }; + + var flight = cacheable + ? cache.acquire(cacheKey, request, acquireOptions) + : { promise: request(), release: function () {} }; + session.flight = flight; + + return flight.promise.then(function (result) { + if (!session.closed && active === session) renderResult(session, result); + return session.shell; + }).catch(function (error) { + var aborted = error && (error.name === "AbortError" || error.code === "ABORT_ERR"); + if (!aborted && !session.closed && active === session) renderError(session, error); + return session.shell; + }).then(function (shell) { + // A refresh/replacement may have installed a newer flight on this session. + // Only the owner of this promise may release and clear the slot. + if (session.flight === flight) { + if (flight && typeof flight.release === "function") flight.release(); + session.flight = null; + } + return shell; + }); + } + + function open(options) { + options = options || {}; + close("replace"); + var session = createSession(options); + return session.shell; + } + + function sessionKey(options) { + if (!options || !options.url) return ""; + var method = String(options.method || "GET").toUpperCase(); + var requestUrl = window.AksrtRequest.url(options.url, options.query); + return method + ":" + requestUrl; + } + + function openRemote(options) { + options = Object.assign({}, options || {}); + if (!options.url) return Promise.reject(new Error("missing url")); + var sameRequest = active && active.flight && sessionKey(active.options) === sessionKey(options); + close("replace", !!sameRequest); + var session = createSession(options); + return load(session).then(function () { return session.shell; }); + } + + function refresh(options) { + if (!active || !active.options.url) return Promise.resolve(null); + if (options && typeof options === "object") active.options = Object.assign({}, active.options, options); + return load(active, { force: true }); + } + + function registerLoader(kind, loader) { + if (!kind || typeof loader !== "function") return; + loaders[String(kind)] = loader; + } + + function openLoader(kind, context) { + var loader = loaders[String(kind)]; + if (!loader) return Promise.reject(new Error("missing modal loader: " + kind)); + return openRemote(loader(context || {})); + } + + function invalidate(tags) { + return window.AksrtModalCache.invalidate(tags); + } + + window.AksrtModalRuntime = { + open: open, + openRemote: openRemote, + close: close, + refresh: refresh, + registerLoader: registerLoader, + openLoader: openLoader, + invalidate: invalidate, + getActive: function () { return active ? active.shell : null; }, + getState: function () { return active ? active.state : null; }, + }; + + document.dispatchEvent(new CustomEvent("aksrt:modal-runtime:ready")); +})(window, document); +; + +/* source: assets/js/core/modal-triggers.js */ +/** + * Declarative modal triggers. + * + * The only modal entry point is AksrtModalRuntime. Triggers carry an + * explicit URL and never create a second modal implementation. + * + * @package AKSRT + */ +(function (window, document) { + "use strict"; + + function openFromTrigger(element) { + var runtime = window.AksrtModalRuntime; + var url = element.getAttribute("data-aksrt-modal-url") || ""; + if (!url || url === "#") return; + + var ttl = parseInt(element.getAttribute("data-aksrt-modal-cache-ttl") || "15000", 10); + var cache = element.getAttribute("data-aksrt-modal-cache") !== "false"; + var tags = (element.getAttribute("data-aksrt-modal-cache-tags") || "").split(",").map(function (tag) { + return tag.trim(); + }).filter(Boolean); + + runtime.openRemote({ + url: url, + method: element.getAttribute("data-aksrt-modal-method") || "GET", + className: element.getAttribute("data-aksrt-modal-class") || "modal-mini full-sm", + height: parseInt(element.getAttribute("data-aksrt-modal-height") || "0", 10) || 0, + mobileBottom: element.getAttribute("data-aksrt-modal-mobile-bottom") === "true", + bottom: element.getAttribute("data-aksrt-modal-bottom") === "true", + kind: element.getAttribute("data-aksrt-modal-kind") || "generic", + cacheKey: element.getAttribute("data-aksrt-modal-cache-key") || "", + cache: cache, + cacheTtl: ttl > 0 ? ttl : 15000, + cacheTags: tags, + }); + } + + document.addEventListener("click", function (event) { + var trigger = event.target.closest('[data-aksrt-modal="remote"]'); + if (!trigger) return; + event.preventDefault(); + openFromTrigger(trigger); + }); +})(window, document); +; + +/* source: assets/js/core/article.js */ +/** + * AKSRT Article Tools — password toggles, search clear, article actions, interaction bar + */ +(function () { + "use strict"; + + function initPasswordToggles(root) { + (root || document).querySelectorAll(".toggle-pwd").forEach(function (btn) { + if (btn.dataset.pwdToggleBound) return; + btn.dataset.pwdToggleBound = "1"; + btn.addEventListener("click", function () { + var id = btn.getAttribute("data-target"); + var input = id ? document.getElementById(id) : null; + if (!input) return; + var show = input.getAttribute("type") === "password"; + input.setAttribute("type", show ? "text" : "password"); + btn.setAttribute("aria-label", show ? "隐藏密码" : "显示密码"); + }); + }); + } + + function initSearchClear() { + var btn = document.getElementById("search-clear-btn"); + var input = document.getElementById("search-page-input"); + + if (btn && input) { + var syncClearButton = function () { + var hasValue = input.value.trim().length > 0; + btn.classList.toggle("is-visible", hasValue); + btn.hidden = !hasValue; + }; + + btn.addEventListener("click", function () { + input.value = ""; + input.focus(); + syncClearButton(); + }); + + input.addEventListener("input", syncClearButton); + syncClearButton(); + } + + var categorySelect = document.getElementById("search-category-filter"); + if (categorySelect) { + categorySelect.addEventListener("change", function () { + var url = new URL(window.location.href); + if (this.value) { + url.searchParams.set("aksrt_category", this.value); + } else { + url.searchParams.delete("aksrt_category"); + } + if (url.searchParams.get("aksrt_search_type") && url.searchParams.get("aksrt_search_type") !== "post") { + url.searchParams.delete("aksrt_category"); + } + url.searchParams.delete("paged"); + window.location.href = url.toString(); + }); + } + } + + function initArticleTools() { + var buttons = document.querySelectorAll("[data-article-tool]"); + if (!buttons.length) return; + + buttons.forEach(function (button) { + button.addEventListener("click", function () { + var tool = button.getAttribute("data-article-tool"); + if (tool === "print") { + window.print(); + return; + } + + if (tool === "pdf") { + window.print(); + notyf("请在打印对话框中选择“另存为 PDF”", "info"); + } + }); + }); + } + + function getBaseStatCount(statName) { + var statEl = document.querySelector('[data-stat-count="' + statName + '"][data-base-count]'); + return statEl ? (parseInt(statEl.dataset.baseCount, 10) || 0) : 0; + } + + function updateStatCounts(statName, count) { + var safeCount = Math.max(0, count); + document.querySelectorAll('[data-stat-count="' + statName + '"]').forEach(function (statEl) { + statEl.textContent = String(safeCount); + }); + } + + function setBaseStatCount(statName, count) { + var safeCount = Math.max(0, count); + document.querySelectorAll('[data-stat-count="' + statName + '"][data-base-count]').forEach(function (statEl) { + statEl.dataset.baseCount = String(safeCount); + }); + updateStatCounts(statName, safeCount); + } + + function requestInteraction(actionName, postId, active) { + if (!window.aksrtData || !aksrtData.ajaxUrl || !aksrtData.nonce) { + return Promise.reject(new Error("互动服务未配置")); + } + + var body = new URLSearchParams(); + body.set("action", actionName); + body.set("nonce", aksrtData.nonce); + body.set("postId", String(postId)); + body.set("active", active ? "1" : "0"); + + return fetch(aksrtData.ajaxUrl, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, + credentials: "same-origin", + body: body.toString() + }).then(function (response) { + return response.json().catch(function () { + throw new Error("服务器返回无效响应"); + }); + }).then(function (payload) { + if (!payload || !payload.success) { + var message = payload && payload.data && payload.data.message ? payload.data.message : "请求失败"; + throw new Error(message); + } + return payload.data || {}; + }); + } + + function setButtonBusy(button, busy) { + button.disabled = !!busy; + button.classList.toggle("is-loading", !!busy); + } + + function findInScope(scope, id) { + scope = scope || document; + if (!scope) return null; + if (scope.nodeType === 9) { + return scope.getElementById(id); + } + if (scope.nodeType === 1) { + if (scope.id === id) return scope; + return typeof scope.querySelector === "function" + ? scope.querySelector("#" + id) + : null; + } + return null; + } + + function collectActionButtons(scope, selector) { + scope = scope || document; + var list = []; + if (!scope) return list; + if (scope.nodeType === 1 && scope.matches && scope.matches(selector)) { + list.push(scope); + } + if (typeof scope.querySelectorAll === "function") { + var found = scope.querySelectorAll(selector); + for (var i = 0; i < found.length; i++) list.push(found[i]); + } else if (scope.nodeType === 9) { + var all = document.querySelectorAll(selector); + for (var j = 0; j < all.length; j++) list.push(all[j]); + } + // also keep legacy id hooks + var legacyId = selector.indexOf("action-like") >= 0 ? "action-like" : (selector.indexOf("action-favorite") >= 0 ? "action-favorite" : ""); + if (legacyId) { + var legacy = findInScope(scope, legacyId); + if (legacy && list.indexOf(legacy) === -1) list.push(legacy); + } + return list; + } + + function initInteractionBar(root) { + var scope = root || document; + // Bind all like/favorite controls (in-content + footer tabbar extend). + var likeBtns = collectActionButtons(scope, ".action-like[data-post-id]"); + var favBtns = collectActionButtons(scope, ".action-favorite[data-post-id]"); + + function paintLike(btn, isActive) { + btn.dataset.initialActive = isActive ? "1" : "0"; + btn.classList.toggle("is-active", isActive); + var icon = btn.querySelector("i"); + if (icon) icon.className = isActive ? "fa fas fa-heart" : "fa fas fa-heart"; + } + + function paintFavorite(btn, isActive) { + btn.dataset.initialActive = isActive ? "1" : "0"; + btn.classList.toggle("is-active", isActive); + var icon = btn.querySelector("i"); + if (icon) icon.className = isActive ? "fa fas fa-star" : "fa fas fa-star"; + } + + function applyLikeState(isActive, count) { + for (var i = 0; i < likeBtns.length; i++) paintLike(likeBtns[i], isActive); + if ("number" === typeof count && !isNaN(count)) { + setBaseStatCount("likes", count); + } else { + updateStatCounts("likes", getBaseStatCount("likes")); + } + } + + function applyFavoriteState(isActive, count) { + for (var i = 0; i < favBtns.length; i++) paintFavorite(favBtns[i], isActive); + if ("number" === typeof count && !isNaN(count)) { + setBaseStatCount("favorites", count); + } else { + updateStatCounts("favorites", getBaseStatCount("favorites")); + } + } + + if (likeBtns.length) { + var seedLike = likeBtns[0]; + if (!seedLike.dataset.aksrtInteractionBound) { + applyLikeState(seedLike.dataset.initialActive === "1", getBaseStatCount("likes")); + likeBtns.forEach(function (likeBtn) { + if (likeBtn.dataset.aksrtInteractionBound) return; + likeBtn.dataset.aksrtInteractionBound = "1"; + likeBtn.addEventListener("click", function () { + var postId = likeBtn.dataset.postId ? String(likeBtn.dataset.postId) : ""; + if (!postId || likeBtn.disabled) return; + var nextState = likeBtn.dataset.initialActive !== "1"; + setButtonBusy(likeBtn, true); + requestInteraction("aksrt_toggle_like", postId, nextState).then(function (data) { + applyLikeState(!!data.active, parseInt(data.count, 10) || 0); + notyf(data.active ? "感谢你的点赞" : "已取消点赞", "success"); + }).catch(function (error) { + notyf(error && error.message ? error.message : "点赞失败", "danger"); + }).finally(function () { + setButtonBusy(likeBtn, false); + }); + }); + }); + } + } + + if (favBtns.length) { + var seedFav = favBtns[0]; + if (!seedFav.dataset.aksrtInteractionBound) { + applyFavoriteState(seedFav.dataset.initialActive === "1", getBaseStatCount("favorites")); + favBtns.forEach(function (favBtn) { + if (favBtn.dataset.aksrtInteractionBound) return; + favBtn.dataset.aksrtInteractionBound = "1"; + favBtn.addEventListener("click", function () { + var postId = favBtn.dataset.postId ? String(favBtn.dataset.postId) : ""; + if (!postId || favBtn.disabled) return; + var nextState = favBtn.dataset.initialActive !== "1"; + setButtonBusy(favBtn, true); + requestInteraction("aksrt_toggle_favorite", postId, nextState).then(function (data) { + applyFavoriteState(!!data.active, parseInt(data.count, 10) || 0); + notyf(data.active ? "收藏成功" : "已取消收藏", "success"); + }).catch(function (error) { + notyf(error && error.message ? error.message : "收藏失败", "danger"); + }).finally(function () { + setButtonBusy(favBtn, false); + }); + }); + }); + } + } + } + + window.AksrtFrontend.register("aksrt-search-clear", function () { initSearchClear(); }); + window.AksrtFrontend.register("aksrt-article-tools", function () { initArticleTools(); }); + window.AksrtFrontend.register("aksrt-password-toggles", function (root) { initPasswordToggles(root); }); + window.AksrtFrontend.register("aksrt-interaction-bar", function (root) { initInteractionBar(root); }); +})(); +; + +/* source: assets/js/core/comments.js */ +/** + * AKSRT Comment System — AJAX submit, captcha, ordering, author filter + */ +(function () { + "use strict"; + + function initCommentSystem() { + var form = document.querySelector("#commentform"); + if (!form) return; + + // 修复:同时匹配 button 和 input 元素 + var submitBtn = form.querySelector('[type="submit"]'); + if (!submitBtn) return; + var isInputSubmit = submitBtn.tagName === 'INPUT'; + + // 保存原始按钮内容(用于恢复) + var originalBtnHTML = isInputSubmit ? submitBtn.value : submitBtn.innerHTML; + + function setSubmitBtnContent(html, textFallback) { + if (isInputSubmit) { + submitBtn.value = textFallback; + } else { + submitBtn.innerHTML = html; + } + } + + // 状态管理 + var isSubmitting = false; + var countdownTimer = null; + + // 显示/隐藏加载状态 + function setLoading(loading) { + if (loading) { + isSubmitting = true; + submitBtn.classList.add("is-loading"); + submitBtn.disabled = true; + var loadingText = (window.aksrtData && aksrtData.commentSubmittingText) || '发送中...'; + setSubmitBtnContent(' ' + loadingText, loadingText); + } else { + isSubmitting = false; + submitBtn.classList.remove("is-loading"); + submitBtn.classList.remove('countdown-active'); + submitBtn.disabled = false; + setSubmitBtnContent(originalBtnHTML, originalBtnHTML); + } + } + + // 收集表单数据(包含验证码字段) + function collectFormData() { + var formData = new FormData(form); + + // 确保包含必要的隐藏字段 + var postId = document.querySelector('input[name="comment_post_ID"]'); + if (postId && !formData.get('comment_post_ID')) { + formData.append('comment_post_ID', postId.value); + } + + return formData; + } + + // 从 wp_die() HTML 中提取错误信息 + function extractErrorMessage(html) { + // WordPress wp_die() 输出格式:

错误信息

+ var match = html.match(/]*>([^<]+)<\/p>/i); + return match ? match[1].trim() : '未知错误'; + } + + // 从错误消息中提取等待时间(秒) + function extractWaitTime(msg) { + var match = msg.match(/(\d+)\s*秒/); + return match ? parseInt(match[1]) : 0; + } + + // 启动倒计时(用于频率限制) + function startCountdown(seconds) { + if (countdownTimer) clearInterval(countdownTimer); + + submitBtn.classList.add('countdown-active'); + submitBtn.disabled = true; + var remaining = seconds; + + countdownTimer = setInterval(function () { + setSubmitBtnContent( + ' 请等待 ' + remaining + 's', + '请等待 ' + remaining + 's' + ); + + if (--remaining <= 0) { + clearInterval(countdownTimer); + countdownTimer = null; + submitBtn.classList.remove('countdown-active', 'is-loading'); + submitBtn.disabled = false; + setSubmitBtnContent(originalBtnHTML, originalBtnHTML); + isSubmitting = false; + } + }, 1000); + } + + function updateCommentCount(delta) { + var countEl = document.querySelector('#comments .title-theme small'); + if (!countEl) return; + var text = countEl.textContent || ''; + var match = text.match(/(\d+)/); + if (!match) return; + var nextCount = Math.max(0, parseInt(match[1], 10) + delta); + countEl.textContent = text.replace(match[1], String(nextCount)); + } + function handleResponse(responseText, statusCode) { + try { + // 情况1: 成功提交 - 包含评论ID或成功标记 + if (statusCode === 200 || statusCode === 201) { + if (responseText.includes('comment-submitted') || + responseText.indexOf('id="comment-') !== -1 || + responseText.indexOf('awaiting-moderation') !== -1) { + + // 区分是否需要审核 + if (responseText.indexOf('审核') !== -1 || + responseText.indexOf('moderation') !== -1 || + responseText.includes('awaiting-moderation')) { + notyf('评论已提交,等待管理员审核', 'info'); + } else { + notyf('评论发布成功', 'success'); + } + + // 清空文本框 + var textarea = form.querySelector('#comment'); + if (textarea) textarea.value = ''; + + // 重置回复状态 + var replyInfo = document.getElementById('comment-reply-info'); + if (replyInfo) replyInfo.style.display = 'none'; + + // 刷新评论列表(延迟执行,确保后端处理完成) + setTimeout(function () { + reloadCommentList(); + }, 500); + setLoading(false); + + return true; + } + } + + // 情况2: HTTP 429 频率限制 + if (statusCode === 429) { + var errorMsg = extractErrorMessage(responseText); + var waitSeconds = extractWaitTime(errorMsg); + + notyf(errorMsg, 'warning'); + + if (waitSeconds > 0) { + startCountdown(waitSeconds); + } else { + setLoading(false); // 无具体等待时间时恢复按钮 + } + + return false; + } + + // 情况3: HTTP 403 权限不足(地区/IP/关键词等) + if (statusCode === 403) { + var forbiddenMsg = extractErrorMessage(responseText); + notyf(forbiddenMsg, 'danger'); + setLoading(false); + return false; + } + + // 情况4: 包含WordPress错误信息 + var errorMatch = responseText.match(/]*>([^<]*(?:错误|失败|error|Error)[^<]*)<\/p>/i); + if (errorMatch) { + notyf(errorMatch[1].trim(), 'danger'); + setLoading(false); + return false; + } + // 默认情况:可能是成功或其他 + notyf('评论已提交', 'success'); + var textarea2 = form.querySelector('#comment'); + if (textarea2) textarea2.value = ''; + + // 局部刷新评论列表(不再整页 reload) + setTimeout(function () { + reloadCommentList(); + }, 500); + setLoading(false); + + return true; + + } catch (e) { + console.error('[AKSRT Comment] 解析响应失败:', e); + notyf('响应解析异常,请重试', 'danger'); + setLoading(false); + return false; + } + } + + // 执行实际的 AJAX 请求 — local insert instead of full-page re-fetch + function performSubmit() { + setLoading(true); + + var formData = collectFormData(); + var restRoot = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, '') : ''; + var editId = form.querySelector('[name="edit_comment_ID"]'); + var isEdit = editId && parseInt(editId.value, 10) > 0; + var restBase = (restRoot || '') + '/wp-json/aksrt/v1/comment/' + (isEdit ? 'edit' : 'submit'); + + // Try REST endpoint for local-insert HTML response + var restBody = {}; + formData.forEach(function (v, k) { restBody[k] = v; }); + if (isEdit) { + restBody.comment_id = parseInt(editId.value, 10); + restBody.content = restBody.comment || restBody.content || ''; + } + + fetch(restBase, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': (window.aksrtData && aksrtData.restNonce) || '' + }, + credentials: 'same-origin', + body: JSON.stringify(restBody) + }) + .then(function (r) { return r.json(); }) + .then(function (json) { + if (isEdit && json && json.success) { + var cid = restBody.comment_id; + var contentEl = document.querySelector('#comment-content-' + cid + ', [data-comment-id="' + cid + '"] .comment-content'); + var rendered = (json.data && json.data.content) || json.content || ''; + if (contentEl && rendered) contentEl.innerHTML = rendered; + var textarea = form.querySelector('#comment'); + if (textarea) textarea.value = ''; + if (editId) editId.remove(); + restoreRespondPosition(); + setLoading(false); + notyf(json.message || '评论已更新', 'success'); + var respondEl = document.getElementById('respond'); + if (respondEl && window.AksrtFixedInput) window.AksrtFixedInput(respondEl, 'hide'); + return; + } + if (json && json.success && json.html) { + // Local insert: prepend new comment HTML + localInsertComment(json.html, json.is_reply, json.parent_id, json.needs_moderation); + + var textarea = form.querySelector('#comment'); + if (textarea) textarea.value = ''; + + var replyInfo = document.getElementById('comment-reply-info'); + if (replyInfo) replyInfo.style.display = 'none'; + // Reset captcha after successful submit + try { + var captchaScene = window.AKSRTCaptchaState && + window.AKSRTCaptchaState.scenes && + window.AKSRTCaptchaState.scenes.comment; + if (captchaScene && typeof captchaScene.reset === 'function') { + captchaScene.reset(); + } + } catch (e) { /* ignore */ } + + // Wait time countdown + if (json.wait_time && json.wait_time > 0) { + startCountdown(json.wait_time); + } else { + setLoading(false); + } + + // Move respond back to original position + restoreRespondPosition(); + + // Hide mobile FixedInput after successful submit. + var respondEl = document.getElementById('respond'); + if (respondEl && window.AksrtFixedInput) { + window.AksrtFixedInput(respondEl, 'hide'); + } else if (respondEl) { + respondEl.classList.remove('show'); + } + + // If editing via moveForm, clear edit field. + var editField = form.querySelector('[name="edit_comment_ID"]'); + if (editField) editField.remove(); + + return; + } + + if (json && json.code) { + notyf(json.message || '评论提交失败', 'danger'); + try { + var failScene = window.AKSRTCaptchaState && + window.AKSRTCaptchaState.scenes && + window.AKSRTCaptchaState.scenes.comment; + if (failScene && typeof failScene.reset === 'function') { + failScene.reset(); + } + } catch (e) { /* ignore */ } + if (json.wait_time) startCountdown(json.wait_time); + else setLoading(false); + return; + } + + notyf('评论已提交', 'success'); + setLoading(false); + setTimeout(function () { reloadCommentList(); }, 500); + }) + .catch(function () { + // Fallback: submit to wp-comments-post.php directly + performLegacySubmit(); + }); + } + + function localInsertComment(html, isReply, parentId, needsModeration) { + var temp = document.createElement('div'); + temp.innerHTML = html; + var newComment = temp.firstElementChild; + if (!newComment) { reloadCommentList(); return; } + + newComment.style.overflow = 'hidden'; + newComment.style.maxHeight = '0'; + newComment.style.opacity = '0'; + newComment.style.transition = 'max-height 0.4s ease, opacity 0.3s ease'; + if (isReply && parentId) { + var parentComment = document.querySelector('#comment-' + parentId); + if (parentComment) { + var children = parentComment.querySelector(':scope > .children'); + if (!children) { + children = document.createElement('ol'); + children.className = 'children'; + parentComment.appendChild(children); + } + children.insertBefore(newComment, children.firstChild); + } else { + prependToCommentList(newComment); + } + } else { + prependToCommentList(newComment); + } + + // slideDown animation + requestAnimationFrame(function () { + newComment.style.maxHeight = newComment.scrollHeight + 100 + 'px'; + newComment.style.opacity = '1'; + setTimeout(function () { + newComment.style.maxHeight = ''; + newComment.style.overflow = ''; + newComment.style.transition = ''; + }, 500); + }); + + if (needsModeration) { + notyf('评论已提交,等待管理员审核', 'info'); + } else { + notyf('评论发布成功', 'success'); + } + + if (window.AksrtFrontend) AksrtFrontend.rehydrate(newComment, 'ajax'); + updateCommentCount(1); + } + + function prependToCommentList(el) { + var list = document.querySelector('#comments .commentlist'); + if (!list) return; + // 插到筛选栏之后、最新评论之前 + var filter = list.querySelector('.comment-filter'); + if (filter && filter.nextSibling) { + list.insertBefore(el, filter.nextSibling); + } else { + list.insertBefore(el, list.firstChild); + } + } + + function restoreRespondPosition() { + var respond = document.getElementById('respond'); + var placeholder = document.getElementById('wp-temp-form-div'); + if (respond && placeholder && placeholder.parentNode) { + placeholder.parentNode.insertBefore(respond, placeholder); + placeholder.remove(); + } + } + // Legacy fallback: POST to wp-comments-post.php + function performLegacySubmit() { + var formData = collectFormData(); + var actionUrl = form.getAttribute('action') || '/wp-comments-post.php'; + + fetch(actionUrl, { + method: 'POST', + body: formData, + credentials: 'same-origin', + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }) + .then(function (response) { + return response.text().then(function (text) { + return { status: response.status, text: text, ok: response.ok }; + }); + }) + .then(function (result) { + handleResponse(result.text, result.status); + }) + .catch(function (error) { + console.error('[AKSRT Comment] 提交失败:', error); + + if (error.message === 'Failed to fetch' || error.name === 'TypeError') { + notyf('网络连接失败,请检查网络后重试', 'danger'); + } else { + notyf(error.message || '评论提交失败,请重试', 'danger'); + } + + setLoading(false); + }) + .finally(function () { + // 兜底:避免异常分支漏掉状态恢复导致按钮卡在"发送中" + if (!countdownTimer && isSubmitting) { + setLoading(false); + } + }); + } + + // 核心表单提交事件处理器 + function handleSubmit(e) { + // 如果已经在提交中或倒计时中,防止重复提交 + if (isSubmitting || countdownTimer) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } + + // 阻止默认表单提交 + e.preventDefault(); + e.stopImmediatePropagation(); + // 检查验证码状态(如果有验证码组件) + var captchaScene = window.AKSRTCaptchaState && + window.AKSRTCaptchaState.scenes && + window.AKSRTCaptchaState.scenes.comment; + + if (captchaScene && typeof captchaScene.verify === 'function') { + // 有验证码:先触发验证流程 + setLoading(true); + + captchaScene.verify(function (verified) { + if (verified !== false) { + // 验证通过,执行 AJAX 提交 + performSubmit(); + } else { + // 验证失败 + notyf('人机验证未通过,请重试', 'warning'); + setLoading(false); + } + }); + } else { + // 无验证码或已经验证过:直接提交 + performSubmit(); + } + + return false; + } + + // 绑定表单提交事件(使用捕获阶段,高优先级) + form.addEventListener('submit', handleSubmit, true); + } + + + function getCommentsRestBase() { + var root = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : ""; + return root + "/wp-json/aksrt/v1"; + } + + function getCurrentCommentPostId() { + var input = document.querySelector('input[name="comment_post_ID"]'); + if (input && input.value) return parseInt(input.value, 10) || 0; + var comments = document.getElementById("comments"); + if (comments && comments.dataset.postId) return parseInt(comments.dataset.postId, 10) || 0; + return 0; + } + + function getCurrentCommentOrder() { + var active = document.querySelector("#comments .comment-order-box li.active .comment-orderby"); + if (active && active.dataset && active.dataset.order) return active.dataset.order; + var url = new URL(window.location.href); + return url.searchParams.get("aksrt_comment_order") || "newest"; + } + + function getCurrentAuthorOnly() { + var url = new URL(window.location.href); + if (url.searchParams.get("aksrt_author_only") === "1") return true; + var btn = document.querySelector("#comments .btn-only-author"); + return !!(btn && btn.classList.contains("active") && btn.dataset.authorOnly === "0"); + } + + function applyFragmentPayload(commentsArea, data, urlState) { + if (!commentsArea || !data || !data.html) return; + + // data.html 是完整的
(含筛选、列表、分页) + var tempList = document.createElement("div"); + tempList.innerHTML = data.html; + var newListWrap = tempList.querySelector("#postcomments"); + if (!newListWrap) return; + + var currentListWrap = commentsArea.querySelector("#postcomments"); + if (currentListWrap) { + currentListWrap.replaceWith(newListWrap); + } else { + commentsArea.appendChild(newListWrap); + } + + var currentCount = commentsArea.querySelector(".title-theme small"); + if (currentCount && data.count_text) currentCount.textContent = data.count_text; + + if (urlState && window.history && typeof window.history.replaceState === "function") { + window.history.replaceState(null, "", urlState); + } + if (window.AksrtFrontend) AksrtFrontend.rehydrate(commentsArea, "ajax"); + } + + function fetchCommentFragment(opts) { + opts = opts || {}; + var postId = opts.postId || getCurrentCommentPostId(); + if (!postId) return Promise.reject(new Error("missing post id")); + var order = opts.order || getCurrentCommentOrder() || "newest"; + var authorOnly = !!opts.authorOnly; + var page = opts.page || 1; + var qs = new URLSearchParams(); + qs.set("post_id", String(postId)); + qs.set("order", order); + qs.set("page", String(page)); + if (authorOnly) qs.set("author_only", "1"); + var url = getCommentsRestBase() + "/comment/list?" + qs.toString(); + return fetch(url, { + method: "GET", + credentials: "same-origin", + headers: { + "X-WP-Nonce": (window.aksrtData && aksrtData.restNonce) || "" + } + }).then(function (r) { return r.json(); }); + } + + /* ====== 评论列表动态刷新 ====== */ + function showListLoading(commentsArea) { + var list = commentsArea.querySelector(".commentlist"); + if (!list) return; + // 保留筛选栏,清空条目并显示 legacy 骨架 + var filter = list.querySelector(".comment-filter"); + var loader = list.querySelector(".post_ajax_loader"); + var skeleton = loader ? loader.cloneNode(true) : null; + Array.prototype.slice.call(list.children).forEach(function (child) { + if (child !== filter && child !== loader) child.remove(); + }); + if (skeleton) { + skeleton.style.display = "block"; + skeleton.removeAttribute("hidden"); + list.appendChild(skeleton); + } + } + + function reloadCommentList() { + var commentsArea = document.getElementById('comments'); + if (!commentsArea) { + window.location.reload(); + return; + } + showListLoading(commentsArea); + + fetchCommentFragment({ + order: getCurrentCommentOrder(), + authorOnly: getCurrentAuthorOnly(), + page: 1 + }).then(function (json) { + if (json && json.success && json.data) { + applyFragmentPayload(commentsArea, json.data, null); + return; + } + throw new Error('fragment failed'); + }).catch(function (error) { + console.error('[AKSRT Comment] 刷新评论列表失败:', error); + window.location.reload(); + }); + } + + /* ====== 评论排序(最新 / 最早) ====== */ + /** + * Locate #id within a rehydrate root. + * Document has getElementById; Element roots (AJAX panels) only have querySelector. + */ + function findInScope(scope, id) { + scope = scope || document; + if (!scope) return null; + if (scope.nodeType === 9) { + return scope.getElementById(id); + } + if (scope.nodeType === 1) { + if (scope.id === id) return scope; + return typeof scope.querySelector === "function" + ? scope.querySelector("#" + id) + : null; + } + return null; + } + + function initCommentOrder(root) { + var scope = root || document; + var commentsArea = findInScope(scope, "comments"); + if (!commentsArea) return; + // Event delegation survives filter HTML replacement after AJAX rehydrate. + if (commentsArea.dataset.orderInit === "1") return; + commentsArea.dataset.orderInit = "1"; + + function renderLoadingState() { + showListLoading(commentsArea); + } + function applyCommentOrder(order) { + if (order !== "newest" && order !== "oldest" && order !== "hottest") { + order = "newest"; + } + renderLoadingState(); + var authorOnly = getCurrentAuthorOnly(); + var url = new URL(window.location.href); + url.searchParams.set("aksrt_comment_order", order); + url.searchParams.delete("cpage"); + if (authorOnly) url.searchParams.set("aksrt_author_only", "1"); + else url.searchParams.delete("aksrt_author_only"); + + fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 }) + .then(function (json) { + if (json && json.success && json.data) { + applyFragmentPayload(commentsArea, json.data, url.toString()); + return; + } + throw new Error("order fragment failed"); + }) + .catch(function (error) { + console.error("[AKSRT Comment] 排序刷新失败:", error); + window.location.href = url.toString(); + }); + } + + function applyAuthorFilter(authorOnlyFlag) { + renderLoadingState(); + var authorOnly = authorOnlyFlag === "1" || authorOnlyFlag === 1 || authorOnlyFlag === true; + var order = getCurrentCommentOrder(); + var url = new URL(window.location.href); + if (authorOnly) url.searchParams.set("aksrt_author_only", "1"); + else url.searchParams.delete("aksrt_author_only"); + url.searchParams.delete("cpage"); + if (order) url.searchParams.set("aksrt_comment_order", order); + + fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 }) + .then(function (json) { + if (json && json.success && json.data) { + applyFragmentPayload(commentsArea, json.data, url.toString()); + return; + } + throw new Error("author fragment failed"); + }) + .catch(function (error) { + console.error("[AKSRT Comment] Author filter failed:", error); + window.location.href = url.toString(); + }); + } + + commentsArea.addEventListener("click", function (e) { + var target = e.target; + if (!target || typeof target.closest !== "function") return; + + // 「只看作者」同时带 comment-orderby,需优先于排序按钮判断 + var authorBtn = target.closest(".btn-only-author"); + if (authorBtn && commentsArea.contains(authorBtn)) { + e.preventDefault(); + applyAuthorFilter(authorBtn.getAttribute("data-author-only") || "0"); + return; + } + + var orderBtn = target.closest(".comment-orderby[data-order]"); + if (orderBtn && commentsArea.contains(orderBtn)) { + e.preventDefault(); + applyCommentOrder(orderBtn.getAttribute("data-order") || "newest"); + } + }); + } + + + /* ====== 数字分页跳页(legacy pag-jump) ====== */ + function jumpToCommentPage(page) { + page = parseInt(page, 10) || 1; + var url = new URL(window.location.href); + url.searchParams.set("cpage", String(page)); + url.hash = "comments"; + window.location.href = url.toString(); + } + + function initCommentPageJump() { + document.addEventListener("click", function (e) { + var jump = e.target.closest ? e.target.closest("#comments .pag-jump") : null; + if (!jump) return; + e.preventDefault(); + var input = jump.querySelector(".jump-input"); + if (input) jumpToCommentPage(input.value); + }); + document.addEventListener("keydown", function (e) { + if (e.key !== "Enter") return; + var input = e.target; + if (!input || !input.classList || !input.classList.contains("jump-input")) return; + if (!input.closest("#comments .pagenav")) return; + e.preventDefault(); + jumpToCommentPage(input.value); + }); + } + + window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); initCommentPageJump(); }); + window.AksrtFrontend.register("aksrt-comment-order", function (root) { initCommentOrder(root); }); +})(); +; + +/* source: assets/js/core/comment-fixed-input.js */ +/** + * Mobile FixedInput + autoheight for comment respond (legacy parity). + */ +(function () { + 'use strict'; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function FixedInput(el, action) { + if (!el) return; + if (!el.dataset.fixedBound) { + el.addEventListener('click', function (e) { + if (e.target && e.target.closest && e.target.closest('.fixed-body')) { + FixedInput(el, 'hide'); + } + }); + el.dataset.fixedBound = '1'; + } + if (action === 'show') { + el.classList.add('show'); + window.setTimeout(function () { + var field = el.querySelector('textarea, input'); + if (field) field.focus(); + }, 100); + return; + } + if (action === 'hide') { + var cancel = el.querySelector('#cancel-comment-reply-link'); + if (cancel) cancel.click(); + el.querySelectorAll('input,textarea').forEach(function (n) { + try { n.blur(); } catch (e) {} + }); + el.querySelectorAll('.dropup.open, .open').forEach(function (n) { + n.classList.remove('open'); + }); + el.classList.remove('show'); + } + } + + document.addEventListener('click', function (e) { + var trigger = e.target && e.target.closest ? e.target.closest('[fixed-input]') : null; + if (!trigger) return; + e.preventDefault(); + var targetSel = trigger.getAttribute('fixed-input') || '#respond'; + var panel = document.querySelector(targetSel); + var comments = document.getElementById('comments'); + if (comments && comments.scrollIntoView) { + comments.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + FixedInput(panel, 'show'); + }); + + // Autoheight textareas. + function bindAutoheight(ta) { + if (!ta || ta.dataset.autoheightBound) return; + ta.dataset.autoheightBound = '1'; + var max = parseInt(ta.getAttribute('maxheight') || '188', 10) || 188; + var resize = function () { + ta.style.height = 'auto'; + var h = Math.min(ta.scrollHeight, max); + ta.style.height = h + 'px'; + ta.style.overflowY = ta.scrollHeight > max ? 'auto' : 'hidden'; + }; + ta.addEventListener('input', resize); + resize(); + } + + function scan() { + document.querySelectorAll('textarea[autoheight], textarea.grin').forEach(bindAutoheight); + } + + document.addEventListener('DOMContentLoaded', scan); + if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') { + window.AksrtFrontend.register('aksrt-comment-fixed-input', function (root) { + scan(); + // Expose for submit hide. + root = root || document; + var respond = root.querySelector ? root.querySelector('#respond') : document.getElementById('respond'); + if (respond) { + respond.aksrtFixedInput = function (action) { + FixedInput(respond, action); + }; + } + }); + } + + window.AksrtFixedInput = FixedInput; +})(); +; + +/* source: assets/js/core/comment-move-form.js */ +/** + * Comment moveForm for reply/edit (legacy parity) — REST-backed edit fetch. + */ +(function () { + 'use strict'; + + var respondId = 'respond'; + var tempId = 'aksrt-wp-temp-form-div'; + var editingId = 0; + + function getRespond() { + return document.getElementById(respondId); + } + + function restRoot() { + var d = window.aksrtData || {}; + return (d.restUrl || d.rest_url || '/wp-json/aksrt/v1').replace(/\/$/, ''); + } + + function nonce() { + var d = window.aksrtData || {}; + return d.restNonce || d.nonce || ''; + } + + function setActionText(text) { + var el = document.querySelector('#' + respondId + ' .action-text'); + if (!el) return; + if (text) { + el.style.display = ''; + el.textContent = text; + } else { + el.style.display = 'none'; + el.textContent = ''; + } + } + + function ensureParentField(respond) { + var form = respond.querySelector('#commentform') || respond; + var parent = form.querySelector('[name="comment_parent"]'); + if (!parent) { + parent = document.createElement('input'); + parent.type = 'hidden'; + parent.name = 'comment_parent'; + parent.id = 'comment_parent'; + parent.value = '0'; + form.appendChild(parent); + } + return parent; + } + + function ensureEditField(respond, commentId) { + var form = respond.querySelector('#commentform') || respond; + var existing = form.querySelector('[name="edit_comment_ID"]'); + if (commentId) { + if (!existing) { + existing = document.createElement('input'); + existing.type = 'hidden'; + existing.name = 'edit_comment_ID'; + existing.id = 'edit_comment_ID'; + form.appendChild(existing); + } + existing.value = String(commentId); + // Hide parent while editing. + var parent = form.querySelector('[name="comment_parent"]'); + if (parent) parent.name = 'comment_parent_bak'; + } else if (existing) { + existing.remove(); + var bak = form.querySelector('[name="comment_parent_bak"]'); + if (bak) bak.name = 'comment_parent'; + } + } + + function moveForm(commId, parentId, isEdit) { + var respond = getRespond(); + if (!respond) return false; + var comm = document.getElementById(commId); + var parent = ensureParentField(respond); + var cancel = document.getElementById('cancel-comment-reply-link'); + var submit = respond.querySelector('#submit'); + var textarea = respond.querySelector('#comment'); + + if (!document.getElementById(tempId)) { + var temp = document.createElement('div'); + temp.id = tempId; + temp.style.display = 'none'; + respond.parentNode.insertBefore(temp, respond); + } + + if (comm && comm.parentNode) { + comm.parentNode.insertBefore(respond, comm.nextSibling); + } + + parent.value = isEdit ? '0' : String(parentId || '0'); + editingId = isEdit ? parseInt(parentId, 10) || 0 : 0; + + if (cancel) { + cancel.style.display = ''; + cancel.onclick = function () { + var t = document.getElementById(tempId); + var r = getRespond(); + if (t && r && t.parentNode) { + t.parentNode.insertBefore(r, t); + t.parentNode.removeChild(t); + } + ensureEditField(r, 0); + if (parent) parent.value = '0'; + setActionText(''); + if (textarea) textarea.value = ''; + editingId = 0; + cancel.style.display = 'none'; + if (window.AksrtFixedInput) window.AksrtFixedInput(r, 'hide'); + else if (r) r.classList.remove('show'); + return false; + }; + } + + if (isEdit && editingId) { + setActionText('编辑评论'); + ensureEditField(respond, editingId); + if (textarea) { + textarea.disabled = true; + textarea.value = '加载中…'; + } + if (submit) submit.disabled = true; + fetch(restRoot() + '/comment/get?comment_id=' + encodeURIComponent(String(editingId)), { + headers: { 'X-WP-Nonce': nonce() }, + credentials: 'same-origin', + }) + .then(function (r) { return r.json(); }) + .then(function (json) { + var content = (json && (json.content || (json.data && json.data.content))) || ''; + if (textarea) { + textarea.disabled = false; + textarea.value = content; + textarea.dispatchEvent(new Event('input')); + } + if (submit) submit.disabled = false; + }) + .catch(function () { + notyf('加载评论失败', 'danger'); + if (textarea) { + textarea.disabled = false; + textarea.value = ''; + } + if (submit) submit.disabled = false; + }); + } else { + var nameEl = comm ? comm.querySelector('.comment-header name') : null; + var author = nameEl ? (nameEl.textContent || '').trim() : ''; + setActionText(author ? ('回复 @' + author) : '回复评论'); + ensureEditField(respond, 0); + if (textarea) { + textarea.focus(); + } + } + + if (window.AksrtFixedInput) { + window.AksrtFixedInput(respond, 'show'); + } else { + respond.classList.add('show'); + } + + if (comm && comm.scrollIntoView) { + comm.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + return false; + } + + document.addEventListener('click', function (e) { + var reply = e.target && e.target.closest ? e.target.closest('.comment-reply-link') : null; + var edit = e.target && e.target.closest ? e.target.closest('.comment-edit-link, [data-comment-action="edit"]') : null; + if (reply) { + e.preventDefault(); + var cid = reply.getAttribute('data-commentid') || reply.getAttribute('data-comment-id') || ''; + var postid = reply.getAttribute('data-postid') || ''; + if (!cid) { + var li = reply.closest('[data-comment-id], #comment-' + (cid || '')); + if (li && li.getAttribute('data-comment-id')) cid = li.getAttribute('data-comment-id'); + } + if (!cid) { + var m = (reply.closest('li[id^="comment-"]') || {}).id || ''; + var mm = /comment-(\d+)/.exec(m); + if (mm) cid = mm[1]; + } + moveForm('div-comment-' + cid, cid, false); + return; + } + if (edit) { + e.preventDefault(); + var eid = edit.getAttribute('data-commentid') || edit.getAttribute('data-comment-id') || ''; + if (!eid) { + var row = edit.closest('[data-comment-id]'); + if (row) eid = row.getAttribute('data-comment-id'); + } + if (!eid) return; + moveForm('div-comment-' + eid, eid, true); + } + }); + + // Dropup / dropdown toggles (guest info, expand toolbar, action menu). + document.addEventListener('click', function (e) { + var t = e.target && e.target.closest ? e.target.closest('[data-toggle-class="open"], span.dropup > a.but, [data-toggle="dropdown"]') : null; + if (!t) return; + var wrap = t.closest('span.dropup, span.dropdown'); + if (!wrap) return; + // 点击已在打开的下拉内部(表情/快捷项等)不切换 + if (wrap.classList.contains('open') && t.parentElement !== wrap) return; + e.preventDefault(); + wrap.classList.toggle('open'); + }); + + // Close dropups on outside click. + document.addEventListener('click', function (e) { + if (e.target.closest && e.target.closest('span.dropup.open, span.dropdown.open')) return; + document.querySelectorAll('span.dropup.open, span.dropdown.open').forEach(function (n) { + n.classList.remove('open'); + }); + }); + + // Scroll helper for @parent. + document.addEventListener('click', function (e) { + var a = e.target && e.target.closest ? e.target.closest('[data-scroll-to]') : null; + if (!a) return; + e.preventDefault(); + var sel = a.getAttribute('data-scroll-to'); + var el = sel ? document.querySelector(sel) : null; + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }); + + window.aksrtCommentMoveForm = moveForm; + window.aksrtGetEditingCommentId = function () { return editingId; }; +})(); +; + +/* source: assets/js/core/ajax-list.js */ +/** + * AKSRT Ajax List Engine — legacy .ajax-next / ajax-replace / route parity. + * + * DOM contract: + * .ajaxpager list container + * .ajax-item replace/append unit + * .ajax-pag .next-page.ajax-next paginate control + * ajax-replace="true" replace mode + * route / route-title history + document.title + * [win-ajax-replace] partial header sync from response HTML + * + * Transport: + * - Prefer data-aksrt-ajax-url JSON { html, header_html, paginate_html, title } + * - Else fetch href as HTML fragment and parse .ajaxpager / .ajax-item + * + * Events: post_ajax.before / post_ajax.ed on container (bubbles). + * + * @package AKSRT + */ +(function (window, document) { + "use strict"; + + var loadingMap = new WeakMap(); + + function restHeaders() { + var h = { Accept: "application/json, text/html", "X-AKSRT-Ajax": "list" }; + if (window.aksrtData && aksrtData.restNonce) { + h["X-WP-Nonce"] = aksrtData.restNonce; + } + return h; + } + + function closestPager(el) { + if (!el) return null; + return ( + el.closest(".ajaxpager") || + el.closest("[data-aksrt-ajaxpager]") || + el.closest(".aksrt-ajaxpager") + ); + } + + function dispatch(container, name, detail) { + if (!container) return; + try { + container.dispatchEvent( + new CustomEvent(name, { bubbles: true, detail: detail || {} }) + ); + } catch (err) {} + } + + function showLoader(container) { + if (!container) return; + var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]"); + if (loader) { + loader.classList.add("is-loading"); + loader.hidden = false; + loader.style.display = ""; + return; + } + container.classList.add("is-ajax-loading"); + } + + function hideLoader(container) { + if (!container) return; + var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]"); + if (loader) { + loader.classList.remove("is-loading"); + if (loader.hasAttribute("data-aksrt-ajax-loader-hide")) { + loader.hidden = true; + loader.style.display = "none"; + } + } + container.classList.remove("is-ajax-loading"); + } + + function parseHtml(html) { + var wrap = document.createElement("div"); + wrap.innerHTML = html || ""; + return wrap; + } + + function extractFromHtml(html, container) { + var doc = parseHtml(html); + var pager = + doc.querySelector(".ajaxpager") || + doc.querySelector("[data-aksrt-ajaxpager]") || + doc.querySelector(".aksrt-ajaxpager"); + var items = []; + var scope = pager || doc; + scope.querySelectorAll(".ajax-item").forEach(function (n) { + items.push(n.outerHTML); + }); + if (!items.length && pager) { + items.push(pager.innerHTML); + } + var pag = scope.querySelector(".ajax-pag"); + var headerBits = {}; + doc.querySelectorAll("[win-ajax-replace]").forEach(function (n) { + var key = n.getAttribute("win-ajax-replace"); + if (key) headerBits[key] = n.outerHTML; + }); + var titleEl = doc.querySelector("title"); + return { + itemsHtml: items.join(""), + paginateHtml: pag ? pag.outerHTML : "", + headerBits: headerBits, + title: titleEl ? titleEl.textContent : "", + fullHtml: pager ? pager.innerHTML : html, + }; + } + + function syncWinReplace(bits) { + if (!bits) return; + Object.keys(bits).forEach(function (key) { + var live = document.querySelector('[win-ajax-replace="' + key + '"]'); + if (!live) return; + var tmp = document.createElement("div"); + tmp.innerHTML = bits[key]; + var next = tmp.firstElementChild; + if (next) live.replaceWith(next); + }); + } + + function applyRoute(link, title) { + if (!link) return; + var route = link.getAttribute("route") || link.getAttribute("data-route"); + if (!(route === "1" || route === "true" || link.hasAttribute("route"))) return; + var href = link.getAttribute("href") || link.getAttribute("ajax-href") || ""; + if (!href) return; + try { + window.history.replaceState({}, "", href); + } catch (err) {} + var rt = + title || + link.getAttribute("route-title") || + link.getAttribute("data-route-title") || + ""; + if (rt) { + try { + document.title = rt; + } catch (e2) {} + } + } + + function replacePaginate(container, paginateHtml) { + if (!container || !paginateHtml) return; + var old = container.querySelector(".ajax-pag"); + if (!old) { + container.insertAdjacentHTML("beforeend", paginateHtml); + return; + } + var tmp = document.createElement("div"); + tmp.innerHTML = paginateHtml; + var next = tmp.firstElementChild; + if (next) old.replaceWith(next); + } + + // Some widgets keep the rendered post grid inside an outer ajaxpager that + // also owns pagination/loading controls. Preserve that grid when replacing + // or appending AJAX items instead of flattening cards into the outer panel. + function findItemHost(container) { + if (!container || !container.children) return container; + for (var i = 0; i < container.children.length; i++) { + var child = container.children[i]; + if ( + child.matches && + child.matches(".posts-row.post-grid, .aksrt-home-widget__post-grid") + ) { + return child; + } + } + return container; + } + + function inject(container, payload, options) { + options = options || {}; + var replace = options.replace !== false; + if (!container) return; + + var itemsHtml = payload.itemsHtml || payload.html || ""; + var itemHost = findItemHost(container); + if (replace) { + itemHost.querySelectorAll(".ajax-item").forEach(function (n) { + n.remove(); + }); + // If container itself holds only items, clear non-loader children. + if (container.getAttribute("data-aksrt-ajax-clear") === "1") { + Array.prototype.slice.call(container.children).forEach(function (child) { + if ( + child.classList.contains("post_ajax_loader") || + child.classList.contains("post-ajax-loader") || + child.hasAttribute("data-aksrt-ajax-loader") || + child.classList.contains("ajax-pag") + ) { + return; + } + child.remove(); + }); + } + if (itemsHtml) { + var pag = itemHost.querySelector(".ajax-pag"); + if (pag) { + pag.insertAdjacentHTML("beforebegin", itemsHtml); + } else { + itemHost.insertAdjacentHTML("beforeend", itemsHtml); + } + } + } else if (itemsHtml) { + var pag2 = itemHost.querySelector(".ajax-pag"); + if (pag2) { + pag2.insertAdjacentHTML("beforebegin", itemsHtml); + } else { + itemHost.insertAdjacentHTML("beforeend", itemsHtml); + } + } + + if (payload.paginateHtml) { + replacePaginate(container, payload.paginateHtml); + } + if (payload.headerBits) { + syncWinReplace(payload.headerBits); + } + if (payload.headerHtml) { + var headerHost = document.querySelector( + options.headerSelector || "[data-aksrt-ajax-header]" + ); + if (headerHost) headerHost.innerHTML = payload.headerHtml; + } + } + + function normalizeJson(json) { + var data = json && json.data ? json.data : json; + if (!data) return null; + var html = data.html || data.list_html || ""; + var itemsHtml = data.items_html || ""; + if (!itemsHtml && html) { + // If html is only item nodes, use as-is; else extract. + if (html.indexOf("ajax-item") !== -1) { + var parsed = extractFromHtml("
" + html + "
"); + itemsHtml = parsed.itemsHtml || html; + } else { + itemsHtml = html; + } + } + return { + itemsHtml: itemsHtml, + html: html, + paginateHtml: data.paginate_html || data.paginateHtml || "", + headerHtml: data.header_html || data.headerHtml || "", + headerBits: data.header_bits || null, + title: data.title || "", + emptyHtml: data.empty_html || "", + hasMore: typeof data.has_more !== "undefined" ? !!data.has_more : null, + }; + } + + function loadFromLink(link, override) { + override = override || {}; + if (!link) return Promise.resolve(); + + var container = + override.container || + (link.getAttribute("ajaxpager-target") + ? document.querySelector(link.getAttribute("ajaxpager-target")) + : null) || + closestPager(link); + if (!container) { + // Fall back: parent stream list. + container = link.closest("[data-aksrt-ajax-list]"); + } + if (!container) return Promise.resolve(); + + if (loadingMap.get(container)) return Promise.resolve(); + loadingMap.set(container, true); + + var replaceAttr = link.getAttribute("ajax-replace"); + var replace = + typeof override.replace === "boolean" + ? override.replace + : replaceAttr === "true" || replaceAttr === "1" || replaceAttr === "replace"; + + var href = + override.url || + link.getAttribute("data-aksrt-ajax-url") || + link.getAttribute("ajax-href") || + link.getAttribute("href") || + ""; + + dispatch(container, "post_ajax.before", { link: link, replace: replace, url: href }); + showLoader(container); + if (replace) { + // Soft clear items while loading (keep skeleton if present). + container.querySelectorAll(".ajax-item").forEach(function (n) { + n.classList.add("is-fading"); + }); + } + + return fetch(href, { + method: override.method || "GET", + credentials: "same-origin", + headers: restHeaders(), + }) + .then(function (res) { + var type = (res.headers.get("content-type") || "").toLowerCase(); + if (type.indexOf("application/json") !== -1) { + return res.json().then(function (json) { + if (!res.ok || (json && json.success === false)) { + throw new Error( + (json && (json.message || (json.data && json.data.message))) || "加载失败" + ); + } + return normalizeJson(json); + }); + } + return res.text().then(function (text) { + if (!res.ok) throw new Error("加载失败"); + return extractFromHtml(text, container); + }); + }) + .then(function (payload) { + if (!payload) throw new Error("空响应"); + if (replace && !payload.itemsHtml && payload.emptyHtml) { + inject(container, { itemsHtml: payload.emptyHtml, paginateHtml: payload.paginateHtml }, { replace: true }); + } else { + inject(container, payload, { replace: replace }); + } + applyRoute(link, payload.title); + hideLoader(container); + loadingMap.set(container, false); + dispatch(container, "post_ajax.ed", { + link: link, + replace: replace, + url: href, + payload: payload, + }); + document.dispatchEvent( + new CustomEvent("aksrt:posts:rendered", { + bubbles: true, + detail: { container: container, replace: replace }, + }) + ); + return payload; + }) + .catch(function (err) { + hideLoader(container); + loadingMap.set(container, false); + container.querySelectorAll(".ajax-item.is-fading").forEach(function (n) { + n.classList.remove("is-fading"); + }); + notyf((err && err.message) || "加载失败", "danger"); + dispatch(container, "post_ajax.error", { link: link, error: err }); + throw err; + }); + } + + document.addEventListener("click", function (e) { + var link = e.target.closest("a.ajax-next, .ajax-next a, button.ajax-next, [data-aksrt-ajax-next]"); + if (!link) return; + // Ignore if inside a component that opts out. + if (link.closest("[data-aksrt-ajax-list='off']")) return; + // The canonical pagination engine exclusively owns these controls. + if (link.closest("[data-aksrt-pagination], .aksrt-comment-pagination")) return; + + var href = link.getAttribute("href") || link.getAttribute("ajax-href") || link.getAttribute("data-aksrt-ajax-url"); + if (!href && !link.getAttribute("data-aksrt-ajax-url")) return; + + e.preventDefault(); + loadFromLink(link.tagName === "A" || link.classList.contains("ajax-next") ? link : link.closest(".ajax-next") || link); + }); + + window.AksrtAjaxList = { + load: loadFromLink, + inject: inject, + extractFromHtml: extractFromHtml, + closestPager: closestPager, + }; +})(window, document); +; + +/* source: assets/js/core/pagination.js */ +/** + * AKSRT AJAX Pagination & Lazy Load + */ +(function () { + "use strict"; + + function hasAjaxConfig() { + return !!(window.aksrtData && aksrtData.ajaxUrl && aksrtData.nonce); + } + + function initAjaxPagination(root) { + var containers = (root || document).querySelectorAll("[data-aksrt-pagination], .aksrt-comment-pagination"); + if (!containers.length) return; + + var triggerText = (window.aksrtData && aksrtData.ajaxTrigger) || "加载更多"; + var nomoreText = (window.aksrtData && aksrtData.ajaxNomore) || "没有更多了"; + var loadingText = (window.aksrtData && aksrtData.ajaxLoading) || "正在加载..."; + var errorText = (window.aksrtData && aksrtData.ajaxLoadError) || "加载失败,请重试"; + + containers.forEach(function (container) { + if (container.dataset.aksrtPaginationBound === "1") return; + container.dataset.aksrtPaginationBound = "1"; + + var mode = container.dataset.mode || "load_more"; + var nextPage = parseInt(container.dataset.nextPage, 10) || 2; + var maxPages = parseInt(container.dataset.maxPages, 10) || 1; + var isComment = container.classList.contains("aksrt-comment-pagination"); + var postId = container.dataset.postId || ""; + var commentOrder = container.dataset.commentOrder || "newest"; + var loading = false; + var failed = false; + var clickTarget = container.querySelector(".next-page.ajax-next, .ias-btn") || container; + var loadMoreBtn = container.querySelector("[data-aksrt-load-more]") || clickTarget; + var indicator = container.querySelector(".post_ajax_loader"); + var successfulAutoLoads = 0; + var iasMax = isComment + ? ((window.aksrtData && typeof aksrtData.commentIasMax !== "undefined") ? parseInt(aksrtData.commentIasMax, 10) : 0) + : ((window.aksrtData && typeof aksrtData.iasMax !== "undefined") ? parseInt(aksrtData.iasMax, 10) : 0); + var homepagePanel = container.closest("[data-home-tab-panel]"); + var target = homepagePanel || container.previousElementSibling; + if (!homepagePanel && target && target.classList && target.classList.contains("content-stream__footer")) { + target = target.previousElementSibling; + } + if (isComment) { + var commentList = container.closest("#comment-list, .commentlist, ol.commentlist") || + document.getElementById("comment-list"); + if (commentList) target = commentList; + } + if (!target && container.parentElement) { + target = container.parentElement.querySelector(".post-grid, .comment-list, .commentlist, .content-stream__list"); + } + + function showSkeleton() { + if (!target) return; + var loader = target.querySelector(".post-ajax-loader"); + if (loader) { + loader.classList.add("is-loading"); + } + } + + function hideSkeleton() { + if (!target) return; + var loader = target.querySelector(".post-ajax-loader"); + if (loader) { + loader.classList.remove("is-loading"); + } + } + + function stopInfinite(markNomore) { + container.classList.remove("ias-pending"); + if (markNomore && loadMoreBtn) { + loadMoreBtn.innerHTML = nomoreText; + loadMoreBtn.classList.remove("is-loading"); + loadMoreBtn.classList.add("ajax-nomore"); + var nextWrap = loadMoreBtn.closest(".next-page"); + if (nextWrap) nextWrap.classList.add("no-more"); + loadMoreBtn.style.pointerEvents = "none"; + } else if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore")) { + loadMoreBtn.classList.remove("is-loading"); + if (failed) { + loadMoreBtn.textContent = errorText; + } else { + loadMoreBtn.innerHTML = triggerText; + } + loadMoreBtn.style.pointerEvents = ""; + } + } + + function markFailed(message) { + failed = true; + stopInfinite(false); + if (loadMoreBtn) { + loadMoreBtn.textContent = message || errorText; + loadMoreBtn.classList.remove("is-loading"); + loadMoreBtn.classList.remove("ajax-nomore"); + loadMoreBtn.style.pointerEvents = ""; + } + } + + function loadNextPage(fromAuto) { + if (loading || failed || nextPage > maxPages || !target) return Promise.resolve(false); + if (!hasAjaxConfig()) { + markFailed(errorText); + return Promise.resolve(false); + } + + loading = true; + if (indicator) indicator.style.display = "flex"; + if (loadMoreBtn) { + loadMoreBtn.classList.add("is-loading"); + loadMoreBtn.textContent = loadingText; + } + showSkeleton(); + + var action = isComment ? "aksrt_load_more_comments" : "aksrt_load_more_posts"; + var queryContext = container.dataset.queryContext || ""; + var queryVars = container.dataset.queryVars || ""; + var renderConfig = container.dataset.renderConfig || ""; + var body = new URLSearchParams(); + body.set("action", action); + body.set("nonce", aksrtData.nonce); + body.set("page", String(nextPage)); + if (queryContext) { + body.set("query_context", queryContext); + } + if (!isComment && queryVars) { + body.set("query_vars", queryVars); + } + if (!isComment && renderConfig) { + body.set("render_config", renderConfig); + } + if (isComment && postId) { + body.set("post_id", postId); + body.set("comment_order", commentOrder); + var authorOnly = container.dataset.authorOnly || "0"; + if (authorOnly === "1") body.set("author_only", "1"); + } + + return fetch(aksrtData.ajaxUrl, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, + credentials: "same-origin", + body: body.toString(), + }) + .then(function (r) { + if (!r.ok) { + throw new Error("HTTP " + r.status); + } + return r.text().then(function (text) { + try { + return JSON.parse(text); + } catch (err) { + throw new Error("Invalid JSON"); + } + }); + }) + .then(function (res) { + if (!(res && res.success && res.data && typeof res.data.html === "string")) { + var msg = res && res.data && res.data.message ? res.data.message : errorText; + markFailed(msg); + return false; + } + + var temp = document.createElement("div"); + temp.innerHTML = res.data.html; + var newItems = temp.children; + while (newItems.length > 0) { + if (homepagePanel && container.parentNode === target) { + target.insertBefore(newItems[0], container); + } else if (container.parentNode === target) { + target.insertBefore(newItems[0], container); + } else { + target.appendChild(newItems[0]); + } + } + if (window.AksrtFrontend) { + AksrtFrontend.rehydrate(target, "ajax"); + } + nextPage++; + container.dataset.nextPage = String(nextPage); + if (fromAuto) { + successfulAutoLoads++; + } + + if (nextPage > maxPages || !res.data.has_more) { + stopInfinite(true); + if (!loadMoreBtn) { + container.innerHTML = '' + nomoreText + ''; + } + } + return true; + }) + .catch(function () { + markFailed(errorText); + return false; + }) + .finally(function () { + loading = false; + hideSkeleton(); + if (indicator) indicator.style.display = "none"; + if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore") && !failed) { + loadMoreBtn.classList.remove("is-loading"); + loadMoreBtn.innerHTML = triggerText; + if (mode === "infinite") { + var lazyTrigger = clickTarget.closest(".next-page") || clickTarget; + if (iasMax > 0 && successfulAutoLoads >= iasMax) { + container.classList.remove("ias-pending"); + lazyTrigger.classList.remove("lazyload", "lazyloaded", "lazyloading"); + lazyTrigger.removeAttribute("lazyload-action"); + } else { + lazyTrigger.classList.remove("lazyloaded", "lazyloading"); + lazyTrigger.classList.add("lazyload"); + if (window.lazySizes && lazySizes.loader) { + lazySizes.loader.checkElems(); + } + } + } + } + }); + } + + if (loadMoreBtn) { + clickTarget.addEventListener("click", function(e) { + e.preventDefault(); + if (loadMoreBtn.dataset.jumpComments === "1") { + var jumpInput = container.querySelector(".jump-input"); + if (!jumpInput) return; + var targetPage = parseInt(jumpInput.value, 10) || 1; + var max = parseInt(jumpInput.dataset.maxPages || String(maxPages), 10) || maxPages; + targetPage = Math.max(1, Math.min(max, targetPage)); + var url = new URL(window.location.href); + url.searchParams.set("cpage", String(targetPage)); + window.location.href = url.toString(); + return; + } + if (loadMoreBtn.dataset.jumpArchive === "1") { + var archiveJumpInput = container.querySelector(".jump-input"); + if (!archiveJumpInput) return; + var archiveTargetPage = parseInt(archiveJumpInput.value, 10) || 1; + var archiveMax = parseInt(archiveJumpInput.dataset.maxPages || String(maxPages), 10) || maxPages; + archiveTargetPage = Math.max(1, Math.min(archiveMax, archiveTargetPage)); + var archiveUrl = new URL(window.location.href); + archiveUrl.searchParams.set(loadMoreBtn.dataset.pageVar || "paged", String(archiveTargetPage)); + window.location.href = archiveUrl.toString(); + return; + } + var fromAuto = clickTarget.dataset.lazyAuto === "1"; + delete clickTarget.dataset.lazyAuto; + if (fromAuto && iasMax > 0 && successfulAutoLoads >= iasMax) { + container.classList.remove("ias-pending"); + return; + } + // Manual click can recover from a previous auto-load failure. + failed = false; + loadNextPage(fromAuto); + }); + } + + // The runtime lazySizes dispatches lazybeforeunveil natively but follows up + // with a jQuery-simulated click, which native listeners never receive. + // Route the unveil trigger through the same click pipeline. + if (container.classList.contains("lazyload") || clickTarget.classList.contains("lazyload")) { + container.addEventListener("lazybeforeunveil", function (e) { + if (e.target !== container && e.target !== clickTarget) return; + clickTarget.dataset.lazyAuto = "1"; + clickTarget.click(); + }); + } + + if (mode === "infinite") { + if ((isComment && window.aksrtData && aksrtData.commentIasAuto === false) || !hasAjaxConfig()) { + var autoTrigger = clickTarget.closest(".next-page") || clickTarget; + autoTrigger.classList.remove("lazyload"); + autoTrigger.removeAttribute("lazyload-action"); + return; + } + container.classList.add("ias-pending"); + } + }); + } + + window.AksrtFrontend.register("aksrt-ajax-pagination", function (root) { initAjaxPagination(root); }); +})(); +; + +/* source: assets/js/core/archive.js */ +/** + * AKSRT Archive AJAX Filter + */ +(function () { + "use strict"; + + function initArchiveFilter(root) { + var scope = root || document; + var bar = scope.querySelector(".archive-filter-bar--ajax[data-ajax-archive]"); + if (!bar) return; + + var archiveType = bar.dataset.ajaxArchive || "category"; + var termId = parseInt(bar.dataset.termId, 10) || 0; + var wrap = scope.querySelector("[data-ajax-wrap]"); + if (!wrap) return; + + var loading = wrap.querySelector(".archive__ajax-loading"); + var REST_BASE = "/wp-json/aksrt/v1/archive/posts"; + var emptyText = archiveType === "tag" ? "该标签下暂无文章" : "该分类下暂无文章"; + + var state = { + filter_cat: "", + filter_tag: "", + sort: "date", + page: 1 + }; + + var activeCat = bar.querySelector("[data-filter-cat].is-active"); + if (activeCat) state.filter_cat = activeCat.dataset.filterCat || ""; + var activeTag = bar.querySelector("[data-filter-tag].is-active"); + if (activeTag) state.filter_tag = activeTag.dataset.filterTag || ""; + var activeSort = bar.querySelector("[data-filter-sort].is-active"); + if (activeSort) state.sort = activeSort.dataset.filterSort || "date"; + + function buildUrl() { + var params = new URLSearchParams(); + params.set("archive_type", archiveType); + params.set("term_id", String(termId)); + if (state.filter_cat) params.set("filter_cat", state.filter_cat); + if (state.filter_tag) params.set("filter_tag", state.filter_tag); + + params.set("sort", state.sort || "date"); + params.set("page", String(state.page)); + return REST_BASE + "?" + params.toString(); + } + + function updateActiveChips() { + bar.querySelectorAll("[data-filter-cat]").forEach(function (el) { + el.classList.toggle("is-active", (el.dataset.filterCat || "") === state.filter_cat); + }); + bar.querySelectorAll("[data-filter-tag]").forEach(function (el) { + el.classList.toggle("is-active", (el.dataset.filterTag || "") === state.filter_tag); + }); + bar.querySelectorAll("[data-filter-sort]").forEach(function (el) { + el.classList.toggle("is-active", (el.dataset.filterSort || "date") === state.sort); + }); + } + + function showLoading() { + if (loading) loading.classList.add("is-loading"); + wrap.classList.add("is-loading"); + } + + function hideLoading() { + if (loading) loading.classList.remove("is-loading"); + wrap.classList.remove("is-loading"); + } + + function rebuildEmptyState() { + var noResults = document.createElement("div"); + noResults.className = "no-results content-stream__empty"; + noResults.innerHTML = "

" + emptyText + "

"; + wrap.appendChild(noResults); + } + + function fetchPosts(pushState) { + showLoading(); + fetch(buildUrl(), { + method: "GET", + headers: { Accept: "application/json" }, + credentials: "same-origin" + }) + .then(function (r) { + if (!r.ok) throw new Error("HTTP " + r.status); + return r.json(); + }) + .then(function (data) { + hideLoading(); + if (data.html !== undefined) { + var children = Array.from(wrap.children); + children.forEach(function (child) { + if (child !== loading) wrap.removeChild(child); + }); + if (data.html) { + var temp = document.createElement("div"); + temp.innerHTML = data.html; + while (temp.firstChild) wrap.appendChild(temp.firstChild); + } else { + rebuildEmptyState(); + } + if (window.AksrtFrontend) AksrtFrontend.rehydrate(wrap, "ajax"); + } + if (pushState !== false) { + var url = new URL(window.location.href); + url.searchParams.delete("orderby"); + url.searchParams.delete("order"); + url.searchParams.delete("paged"); + url.searchParams.delete("aksrt_filter_cat"); + url.searchParams.delete("aksrt_filter_tag"); + if (state.filter_cat) url.searchParams.set("aksrt_filter_cat", state.filter_cat); + if (state.filter_tag) url.searchParams.set("aksrt_filter_tag", state.filter_tag); + if (state.sort) url.searchParams.set("orderby", state.sort); + if (state.page > 1) url.searchParams.set("paged", String(state.page)); + history.pushState({ aksrtArchive: true, state: Object.assign({}, state) }, "", url.toString()); + } + }) + .catch(function () { + hideLoading(); + }); + } + + bar.addEventListener("click", function (e) { + var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort]"); + if (!chip) return; + e.preventDefault(); + + var changed = false; + if (chip.dataset.filterCat !== undefined) { + var newCat = chip.dataset.filterCat; + if (newCat !== state.filter_cat) { + state.filter_cat = newCat; + changed = true; + } + } + if (chip.dataset.filterTag !== undefined) { + var newTag = chip.dataset.filterTag; + if (newTag !== state.filter_tag) { + state.filter_tag = newTag; + changed = true; + } + } + if (chip.dataset.filterSort !== undefined) { + var newSort = chip.dataset.filterSort || "date"; + if (newSort !== state.sort) { + state.sort = newSort; + changed = true; + } + } + + if (changed) { + state.page = 1; + updateActiveChips(); + fetchPosts(true); + } + }); + + wrap.addEventListener("click", function (e) { + var pageLink = e.target.closest(".pagination a, .nav-links a, a.page-numbers"); + if (!pageLink || !wrap.contains(pageLink)) return; + var href = pageLink.getAttribute("href"); + if (!href) return; + + var match = href.match(/[?&]paged=(\d+)/); + if (match) { + e.preventDefault(); + state.page = parseInt(match[1], 10) || 1; + fetchPosts(true); + } + }); + + window.addEventListener("popstate", function (e) { + if (e.state && e.state.aksrtArchive) { + state = Object.assign(state, e.state.state); + updateActiveChips(); + fetchPosts(false); + } + }); + } + + window.AksrtFrontend.register("aksrt-archive-filter", function (root) { + initArchiveFilter(root); + }); +})(); +; + +/* source: assets/js/core/footer-tabbar.js */ +/** + * AKSRT Footer Tabbar + */ +(function () { + "use strict"; + + function initFooterTabbar() { + var tabbar = document.getElementById("footer-tabbar"); + if (!tabbar) return; + var addBackdrop = document.getElementById("tabbar-add-backdrop"); + + var ticking = false; + var scrollThreshold = 100; + var isHidden = false; + + // Use body scroll bus classes from aksrt-runtime.js + function handleScrollDownHide() { + var body = document.body; + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; + + if (scrollTop > scrollThreshold) { + if (body.classList.contains("scroll-down") && !isHidden) { + // 向下滚动 - 隐藏 + tabbar.classList.add("is-hidden"); + isHidden = true; + } else if (body.classList.contains("scroll-up") && isHidden) { + tabbar.classList.remove("is-hidden"); + isHidden = false; + } + } else { + tabbar.classList.remove("is-hidden"); + isHidden = false; + } + } + + // 返回顶部按钮切换 + function handleOntopWrap() { + var ontopWraps = tabbar.querySelectorAll(".tabbar-ontop-wrap"); + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; + + ontopWraps.forEach(function (wrap) { + if (scrollTop > scrollThreshold) { + wrap.classList.add("is-scrolled"); + } else { + wrap.classList.remove("is-scrolled"); + } + }); + } + + // 滚动事件处理 + function onScroll() { + if (!ticking) { + requestAnimationFrame(function () { + if (tabbar.classList.contains("scroll-down-hide")) { + handleScrollDownHide(); + } + handleOntopWrap(); + ticking = false; + }); + ticking = true; + } + } + + window.addEventListener("scroll", onScroll, { passive: true }); + + function closeAddPanels() { + document.querySelectorAll(".tabbar-add-panel").forEach(function (panel) { + panel.setAttribute("hidden", ""); + }); + if (addBackdrop) addBackdrop.setAttribute("hidden", ""); + document.body.classList.remove("tabbar-add-open"); + } + + function openAddPanel(panelId) { + closeAddPanels(); + var panel = document.getElementById(panelId); + if (!panel) return; + panel.removeAttribute("hidden"); + if (addBackdrop) addBackdrop.removeAttribute("hidden"); + document.body.classList.add("tabbar-add-open"); + } + + if (addBackdrop) { + addBackdrop.addEventListener("click", closeAddPanels); + } + + document.addEventListener("keydown", function (e) { + if (e.key === "Escape") closeAddPanels(); + }); + + // 返回顶部点击事件 + 双击首页刷新 + var homeClickTimer = null; + tabbar.addEventListener("click", function (e) { + var addBtn = e.target.closest('.tabbar-item[data-type="add"]'); + if (addBtn) { + e.preventDefault(); + openAddPanel(addBtn.getAttribute("data-panel-id")); + return; + } + + var searchBtn = e.target.closest('.tabbar-item[data-type="search"]'); + if (searchBtn) { + e.preventDefault(); + if (typeof window.__aksrtOpenSearchPanel === "function") { + window.__aksrtOpenSearchPanel(); + } + return; + } + + var ontopBtn = e.target.closest(".tabbar-ontop-btn"); + if (ontopBtn) { + e.preventDefault(); + window.scrollTo({ top: 0, behavior: "smooth" }); + return; + } + + // 双击首页Tab刷新页面 + var homeBtn = e.target.closest('.tabbar-item[data-type="home"]'); + if (homeBtn && tabbar.classList.contains("has-animation")) { + if (homeClickTimer) { + clearTimeout(homeClickTimer); + homeClickTimer = null; + window.location.reload(); + return; + } + homeClickTimer = setTimeout(function () { + homeClickTimer = null; + }, 350); + } + }); + + document.addEventListener("click", function (e) { + if (e.target.closest("[data-close-tabbar-add-panel]")) { + closeAddPanels(); + } + }); + } + + window.AksrtFrontend.register("aksrt-footer-tabbar", function () { initFooterTabbar(); }); +})(); +; + +/* source: assets/js/core/misc.js */ +/** + * AKSRT Misc — related swiper, slide covers, hidden content + */ +(function () { + "use strict"; + + function renderGradientBackground(container) { + if (!container || container.dataset.aksrtGradientState || typeof window.Grade !== "function") return; + + var source = container.querySelector("img"); + if (!source || (!source.classList.contains("lazyloaded") && !(source.complete && source.naturalWidth > 0))) return; + + container.dataset.aksrtGradientState = "loading"; + window.Grade(container, null, function (result) { + var gradientData = result && result[0]; + var colors = gradientData && gradientData.gradientData; + if (!gradientData || !colors || !colors[0] || !colors[1]) { + delete container.dataset.aksrtGradientState; + return; + } + + var opacity = container.getAttribute("data-opacity"); + var colorName = opacity ? "rgba" : "rgb"; + var alpha = opacity ? "," + opacity : ""; + var color1 = colorName + "(" + colors[0].rgba.slice(0, 3).join(",") + alpha + ") 10%"; + var color2 = colorName + "(" + colors[1].rgba.slice(0, 3).join(",") + alpha + ") 80%"; + + gradientData.element.classList.add("gradient-bg-loaded"); + gradientData.element.style.backgroundImage = "linear-gradient(135deg, " + color1 + ", " + color2 + ")"; + container.dataset.aksrtGradientState = "loaded"; + }); + } + + function initGradientBackgrounds(root) { + var scope = root || document; + if (scope.nodeType === 1 && scope.matches(".gradient-bg")) { + renderGradientBackground(scope); + } + scope.querySelectorAll(".gradient-bg").forEach(renderGradientBackground); + } + + document.addEventListener("lazyloaded", function (event) { + if (!(event.target instanceof Element) || !event.target.matches(".gradient-bg img")) return; + renderGradientBackground(event.target.closest(".gradient-bg")); + }, true); + + function initPostThumbMedia(root) { + var scope = root || document; + + scope.querySelectorAll("[data-post-thumb-video]").forEach(function (box) { + if (box.dataset.aksrtThumbVideoBound === "1") return; + box.dataset.aksrtThumbVideoBound = "1"; + var video = box.querySelector("video"); + if (!video) return; + + function loadVideo() { + if (!video.getAttribute("src") && video.dataset.src) { + video.setAttribute("src", video.dataset.src); + video.load(); + } + } + + function playVideo() { + loadVideo(); + var result = video.play(); + var postItem = box.closest(".posts-item"); + if (postItem) postItem.classList.add("thumb-dplayer-playing"); + if (result && typeof result.catch === "function") result.catch(function () {}); + } + + function pauseVideo() { + video.pause(); + var postItem = box.closest(".posts-item"); + if (postItem) postItem.classList.remove("thumb-dplayer-playing"); + } + + var item = box.closest(".item-thumbnail") || box; + item.addEventListener("mouseenter", playVideo); + item.addEventListener("mouseleave", pauseVideo); + item.addEventListener("focusin", playVideo); + item.addEventListener("focusout", pauseVideo); + }); + } + + window.AksrtFrontend.register("aksrt-post-thumb-media", function (root) { + initPostThumbMedia(root); + initGradientBackgrounds(root); + }); + +})(); + +(function () { + "use strict"; + + function initSlideCovers() { + var covers = document.querySelectorAll(".single-cover--slide"); + covers.forEach(function (cover) { + var slides = cover.querySelectorAll(".single-cover__slide"); + var dots = cover.querySelectorAll(".single-cover__dot"); + if (slides.length < 2) return; + + var current = 0; + var interval = 5000; + + function goTo(idx) { + slides[current].classList.remove("is-active"); + if (dots[current]) dots[current].classList.remove("is-active"); + current = idx; + slides[current].classList.add("is-active"); + if (dots[current]) dots[current].classList.add("is-active"); + } + + var timer = setInterval(function () { + goTo((current + 1) % slides.length); + }, interval); + + dots.forEach(function (dot, i) { + dot.addEventListener("click", function () { + clearInterval(timer); + goTo(i); + timer = setInterval(function () { + goTo((current + 1) % slides.length); + }, interval); + }); + }); + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initSlideCovers); + } else { + initSlideCovers(); + } +})(); + +(function () { + "use strict"; + + function initHiddenContent() { + document.addEventListener("click", function (e) { + var btn = e.target.closest(".aksrt-hidden-content__submit-btn"); + if (!btn) return; + + e.preventDefault(); + var wrapper = btn.closest(".aksrt-hidden-content--locked"); + if (!wrapper) return; + + var mode = wrapper.getAttribute("data-aksrt-visibility-mode"); + var pid = wrapper.getAttribute("data-aksrt-visibility-pid"); + var input = wrapper.querySelector(".aksrt-hidden-content__password-input"); + var password = input ? input.value.trim() : ""; + + if (mode === "password" && !password) { + if (input) input.focus(); + return; + } + + var formData = new FormData(); + formData.append("action", "aksrt_check_visibility"); + formData.append("_nonce", (window.aksrtData && aksrtData.visibilityNonce) || ""); + formData.append("mode", mode); + formData.append("pid", pid); + if (password) formData.append("password", password); + + btn.disabled = true; + btn.textContent = "..."; + + fetch((window.aksrtData && aksrtData.ajaxUrl) || "/wp-admin/admin-ajax.php", { + method: "POST", + body: formData, + credentials: "same-origin", + }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (data.success) { + window.location.reload(); + } else { + var msg = (data.data && data.data.message) ? data.data.message : "验证失败"; + alert(msg); + btn.disabled = false; + btn.textContent = "验证"; + } + }) + .catch(function () { + alert("网络错误,请重试"); + btn.disabled = false; + btn.textContent = "验证"; + }); + }); + + // Handle Enter key on password input + document.addEventListener("keydown", function (e) { + if (e.key === "Enter" && e.target.classList.contains("aksrt-hidden-content__password-input")) { + e.preventDefault(); + var wrapper = e.target.closest(".aksrt-hidden-content--locked"); + if (wrapper) { + var btn = wrapper.querySelector(".aksrt-hidden-content__submit-btn"); + if (btn) btn.click(); + } + } + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initHiddenContent); + } else { + initHiddenContent(); + } +})(); +; + +/* source: assets/js/follow.js */ +/** + * AKSRT Follow — 前台关注交互 + * + * 监听 [data-aksrt-follow] 按钮点击,切换关注状态。 + * + * @package AKSRT + */ +(function () { + 'use strict'; + + var API_ROOT = (function () { + var d = window.aksrtData || {}; + var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1'; + return String(raw).replace(/\/$/, ''); + })(); + var NONCE = (window.aksrtData && window.aksrtData.restNonce) || ''; + + // 初始关注状态由服务端渲染(is-following class + 文案),无需逐按钮查询。 + function initFollowButtons() { + document.addEventListener('click', function (e) { + var btn = e.target.closest('[data-aksrt-follow]'); + if (!btn) return; + e.preventDefault(); + + var targetUserId = parseInt(btn.getAttribute('data-aksrt-follow')); + if (!targetUserId) return; + + btn.disabled = true; + + fetch(API_ROOT + '/follow/toggle', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': NONCE, + }, + body: JSON.stringify({ target_user_id: targetUserId }), + }) + .then(function (res) { return res.json(); }) + .then(function (result) { + if (result.success && result.data) { + var isFollowing = result.data.is_following; + updateFollowButton(btn, isFollowing, result.data.follower_count); + notyf(isFollowing ? '关注成功' : '已取消关注', 'success'); + } else { + notyf(result.message || '操作失败', 'danger'); + } + }) + .catch(function () { + notyf('网络错误,请重试', 'danger'); + }) + .finally(function () { + btn.disabled = false; + }); + }); + } + + function updateFollowButton(btn, isFollowing, count) { + var icon = btn.querySelector('i'); + var text = btn.querySelector('span'); + function setLabel(label) { + if (text) { + text.textContent = label; + return; + } + // Fallback when markup has bare text nodes next to the icon. + var nodes = Array.prototype.slice.call(btn.childNodes); + var textNode = nodes.filter(function (n) { return n.nodeType === 3 && n.textContent.trim() !== ''; })[0]; + if (textNode) { + textNode.textContent = ' ' + label + ' '; + } + } + + if (isFollowing) { + btn.classList.add('is-following'); + if (icon) icon.className = 'fa fas fa-heart'; + setLabel('已关注'); + btn.title = '点击取消关注'; + } else { + btn.classList.remove('is-following'); + if (icon) icon.className = 'fa fas fa-heart'; + setLabel('关注'); + btn.title = '关注该用户'; + } + + if (!isFollowing && btn.closest('.uc-follow-management__item')) { + btn.closest('.uc-follow-management__item').remove(); + } + + // Update follower count display if exists + var countEl = document.querySelector('.aksrt-user-home__stat-value[data-followers]'); + if (countEl && count !== undefined) { + countEl.textContent = count; + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initFollowButtons); + } else { + initFollowButtons(); + } +})(); +; + +/* source: assets/js/enhancements.js */ +/** + * AKSRT Enhancements — 全局 UI 增强 + * + * 包含:图片灯箱、滚动方向感知 + * + * @package AKSRT + */ + +(function () { + 'use strict'; + + /* ============================================================ + * 1. 图片灯箱 — zoom / thumbnails / slideshow / fullscreen / + * mousewheel / origin animation / smart download + * ============================================================ */ + var lightboxEl = null; + var lightboxImg = null; + var lightboxCaption = null; + var lightboxCounter = null; + var lightboxThumbsWrap = null; + var currentGallery = []; + var currentIndex = 0; + var currentZoom = 1; + var autoplayTimer = null; + var isAutoplay = false; + var EXCLUDE_SEL = '.no-imgbox,.no-lightbox,.avatar,.img-icon,.avatar-badge,.smilie-icon,.aksrt-avatar'; + var TRIGGER_SEL = '.entry-content img, .wp-posts-content img, .aksrt-lightbox-trigger img, [data-aksrt-lightbox] img, .imgbox-container img, .comment-content .box-img img, a[data-imgbox] img'; + var CONTAINER_SEL = '.entry-content, .wp-posts-content, .aksrt-lightbox-trigger, [data-aksrt-lightbox], .imgbox-container, .comment-content'; + var MAX_ZOOM_DESKTOP = 2; + var MAX_ZOOM_MOBILE = 3; + + function lightboxFlag(key, fallback) { + var w = window._win || (window.aksrtData && window.aksrtData.imgbox) || {}; + if (w[key] === undefined || w[key] === null || w[key] === '') return fallback; + return w[key] === true || w[key] === 1 || w[key] === '1'; + } + function lightboxEnabled() { + return lightboxFlag('imgbox', true); + } + + + function resolveFullUrl(img) { + var full = img.getAttribute('data-full-url') || img.getAttribute('data-src') || ''; + if (full) return full; + var src = img.src || ''; + // thumbnail → original: strip -NNNxNNN suffix + return src.replace(/-\d+x\d+(\.[a-z]+)$/i, '$1'); + } + + function isNoLightbox(img) { + if (img.closest(EXCLUDE_SEL)) return true; + // parent anchor that does NOT point to an image + var parentA = img.closest('a'); + if (parentA) { + var href = parentA.getAttribute('href') || ''; + if (href && !/\.(jpe?g|png|gif|webp|avif|svg|bmp)(\?|$)/i.test(href) && !parentA.hasAttribute('data-imgbox')) { + return true; + } + } + return false; + } + + function createLightbox() { + if (!lightboxEnabled()) return; + if (lightboxEl) return; + + lightboxEl = document.createElement('div'); + lightboxEl.className = 'aksrt-lightbox'; + lightboxEl.innerHTML = + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '
' + + ''; + + document.body.appendChild(lightboxEl); + + lightboxImg = lightboxEl.querySelector('.aksrt-lightbox__img'); + lightboxCaption = lightboxEl.querySelector('.aksrt-lightbox__caption'); + lightboxCounter = lightboxEl.querySelector('.aksrt-lightbox__counter'); + lightboxThumbsWrap = lightboxEl.querySelector('.aksrt-lightbox__thumbs'); + + // Apply window._win imgbox feature flags + var zoomBtn = lightboxEl.querySelector('.toggle-zoom'); + var thumbsBtn = lightboxEl.querySelector('.toggle-thumbs'); + var playBtn = lightboxEl.querySelector('.toggle-play'); + var fullBtn = lightboxEl.querySelector('.toggle-full'); + var downBtn = lightboxEl.querySelector('.aksrt-lightbox__download'); + if (zoomBtn) zoomBtn.hidden = !lightboxFlag('imgbox_zoom', true); + if (thumbsBtn) thumbsBtn.hidden = !lightboxFlag('imgbox_thumbs', true); + if (playBtn) playBtn.hidden = !lightboxFlag('imgbox_play', false); + if (fullBtn) fullBtn.hidden = !lightboxFlag('imgbox_full', true); + if (downBtn) downBtn.hidden = !lightboxFlag('imgbox_down', false); + + // Close + lightboxEl.querySelector('.aksrt-lightbox__close').addEventListener('click', function (e) { e.stopPropagation(); closeLightbox(); }); + lightboxEl.addEventListener('click', function (e) { if (e.target === lightboxEl || e.target.classList.contains('aksrt-lightbox__stage')) closeLightbox(); }); + + // Nav + lightboxEl.querySelector('.aksrt-lightbox__nav--prev').addEventListener('click', function (e) { e.stopPropagation(); navigateLightbox(-1); }); + lightboxEl.querySelector('.aksrt-lightbox__nav--next').addEventListener('click', function (e) { e.stopPropagation(); navigateLightbox(1); }); + + // Keyboard + document.addEventListener('keydown', function (e) { + if (!lightboxEl.classList.contains('is-open')) return; + if (e.key === 'Escape') closeLightbox(); + if (e.key === 'ArrowLeft') navigateLightbox(-1); + if (e.key === 'ArrowRight') navigateLightbox(1); + }); + + // Mousewheel nav + lightboxEl.addEventListener('wheel', function (e) { + if (!lightboxEl.classList.contains('is-open')) return; + if (currentZoom > 1) return; // don't nav when zoomed + e.preventDefault(); + if (e.deltaY > 0) navigateLightbox(1); + else navigateLightbox(-1); + }, { passive: false }); + + // Image click → toggle zoom + lightboxImg.addEventListener('click', function (e) { e.stopPropagation(); toggleZoom(); }); + + // Double-click → zoom in + lightboxImg.addEventListener('dblclick', function (e) { + e.stopPropagation(); + var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP; + if (currentZoom < maxZoom) setZoom(maxZoom); + else setZoom(1); + }); + + // Pinch zoom (mobile) + var pinchStart = 0; + lightboxEl.addEventListener('touchstart', function (e) { + if (e.touches.length === 2) { + pinchStart = Math.hypot( + e.touches[1].clientX - e.touches[0].clientX, + e.touches[1].clientY - e.touches[0].clientY + ); + } + }, { passive: true }); + + lightboxEl.addEventListener('touchmove', function (e) { + if (e.touches.length === 2 && pinchStart > 0) { + var dist = Math.hypot( + e.touches[1].clientX - e.touches[0].clientX, + e.touches[1].clientY - e.touches[0].clientY + ); + var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP; + var newZoom = Math.min(maxZoom, Math.max(1, currentZoom * (dist / pinchStart))); + setZoom(newZoom); + pinchStart = dist; + } + }, { passive: true }); + + lightboxEl.addEventListener('touchend', function () { pinchStart = 0; }, { passive: true }); + + // Touch swipe/pull-dismiss (skip when zoomed) + var touchStartY = 0, touchStartX = 0, isSwiping = false; + lightboxEl.addEventListener('touchstart', function (e) { + if (e.touches.length !== 1 || currentZoom > 1.05) return; + touchStartY = e.touches[0].clientY; + touchStartX = e.touches[0].clientX; + isSwiping = false; + }, { passive: true }); + + lightboxEl.addEventListener('touchmove', function (e) { + if (e.touches.length !== 1 || currentZoom > 1.05) return; + var dy = e.touches[0].clientY - touchStartY; + var dx = e.touches[0].clientX - touchStartX; + if (Math.abs(dy) > Math.abs(dx) && dy > 30) { + isSwiping = true; + var opacity = Math.max(0.2, (150 - dy) / 80); + lightboxImg.style.transform = 'translate3d(0, ' + dy + 'px, 0) scale(' + currentZoom + ')'; + lightboxImg.style.opacity = String(Math.min(1, opacity)); + } + }, { passive: true }); + + lightboxEl.addEventListener('touchend', function () { + if (isSwiping) { + closeLightbox(); + lightboxImg.style.transform = ''; + lightboxImg.style.opacity = ''; + } + isSwiping = false; + }, { passive: true }); + + // Download + lightboxEl.querySelector('.aksrt-lightbox__download').addEventListener('click', function (e) { + e.stopPropagation(); + var item = currentGallery[currentIndex]; + if (!item || !item.src) return; + var a = document.createElement('a'); + var fullSrc = item.fullSrc || item.src; + a.href = fullSrc; + a.download = fullSrc.substring(fullSrc.lastIndexOf('/') + 1) || 'image'; + document.body.appendChild(a); + a.click(); + a.remove(); + }); + + // Zoom button + lightboxEl.querySelector('.toggle-zoom').addEventListener('click', function (e) { e.stopPropagation(); toggleZoom(); }); + + // Thumbnails toggle + lightboxEl.querySelector('.toggle-thumbs').addEventListener('click', function (e) { + e.stopPropagation(); + lightboxThumbsWrap.hidden = !lightboxThumbsWrap.hidden; + }); + + // Slideshow toggle + lightboxEl.querySelector('.toggle-play').addEventListener('click', function (e) { + e.stopPropagation(); + toggleAutoplay(); + }); + + // Fullscreen + lightboxEl.querySelector('.toggle-full').addEventListener('click', function (e) { + e.stopPropagation(); + toggleFullscreen(); + }); + + document.addEventListener('fullscreenchange', function () { + var icon = lightboxEl.querySelector('.toggle-full i'); + if (document.fullscreenElement) { + lightboxEl.classList.add('full-screen'); + icon.className = 'fa fas fa-compress'; + } else { + lightboxEl.classList.remove('full-screen'); + icon.className = 'fa fas fa-expand'; + } + }); + } + + function setZoom(z) { + currentZoom = z; + lightboxImg.style.transform = 'scale(' + z + ')'; + lightboxEl.classList.toggle('is-zoomed', z > 1); + } + + function toggleZoom() { + var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP; + setZoom(currentZoom > 1 ? 1 : maxZoom); + } + + function toggleAutoplay() { + var icon = lightboxEl.querySelector('.toggle-play i'); + if (isAutoplay) { + clearInterval(autoplayTimer); + isAutoplay = false; + icon.className = 'fa fas fa-play'; + } else { + if (currentGallery.length < 2) return; + isAutoplay = true; + icon.className = 'fa fas fa-pause'; + autoplayTimer = setInterval(function () { navigateLightbox(1); }, 3000); + } + } + + function toggleFullscreen() { + var rfs = lightboxEl.requestFullscreen || lightboxEl.webkitRequestFullscreen || lightboxEl.msRequestFullscreen; + if (document.fullscreenElement || document.webkitFullscreenElement) { + (document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document); + } else if (rfs) { + rfs.call(lightboxEl); + } + } + + function buildThumbnails() { + if (!lightboxThumbsWrap) return; + lightboxThumbsWrap.innerHTML = ''; + currentGallery.forEach(function (item, i) { + var thumb = document.createElement('img'); + thumb.src = item.src; + thumb.className = 'aksrt-lightbox__thumb' + (i === currentIndex ? ' is-active' : ''); + thumb.addEventListener('click', function (e) { + e.stopPropagation(); + showLightboxImage(i); + }); + lightboxThumbsWrap.appendChild(thumb); + }); + } + + function syncThumbActive() { + if (!lightboxThumbsWrap) return; + var thumbs = lightboxThumbsWrap.querySelectorAll('.aksrt-lightbox__thumb'); + thumbs.forEach(function (t, i) { t.classList.toggle('is-active', i === currentIndex); }); + var active = thumbs[currentIndex]; + if (active) active.scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' }); + } + + function openLightbox(src, caption, gallery, index, originEl) { + createLightbox(); + + currentGallery = gallery || [{ src: src, caption: caption || '' }]; + currentIndex = index || 0; + currentZoom = 1; + + // Origin animation + if (originEl) { + var rect = originEl.getBoundingClientRect(); + lightboxEl.style.setProperty('--imgbox-origin-top', (rect.top + rect.height / 2) + 'px'); + lightboxEl.style.setProperty('--imgbox-origin-left', (rect.left + rect.width / 2) + 'px'); + lightboxEl.classList.add('from-origin'); + } else { + lightboxEl.classList.remove('from-origin'); + } + + showLightboxImage(currentIndex); + buildThumbnails(); + + lightboxEl.classList.add('is-open'); + document.body.style.overflow = 'hidden'; + + lightboxEl.dispatchEvent(new CustomEvent('aksrt:lightbox-open', { bubbles: true })); + } + + function showLightboxImage(index) { + if (index < 0) index = currentGallery.length - 1; + if (index >= currentGallery.length) index = 0; + currentIndex = index; + setZoom(1); + + var item = currentGallery[currentIndex]; + lightboxImg.src = item.fullSrc || item.src; + lightboxImg.alt = item.caption || ''; + lightboxCaption.textContent = item.caption || ''; + lightboxCaption.style.display = item.caption ? '' : 'none'; + + if (currentGallery.length > 1) { + lightboxCounter.textContent = (currentIndex + 1) + ' / ' + currentGallery.length; + lightboxCounter.style.display = ''; + } else { + lightboxCounter.style.display = 'none'; + } + + syncThumbActive(); + } + + function navigateLightbox(dir) { + showLightboxImage(currentIndex + dir); + } + + function closeLightbox() { + if (!lightboxEl) return; + if (isAutoplay) toggleAutoplay(); + lightboxEl.classList.remove('is-open', 'is-zoomed', 'from-origin'); + document.body.style.overflow = ''; + lightboxThumbsWrap.hidden = true; + if (document.fullscreenElement) { + (document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document); + } + } + + // Auto-bind: expanded trigger scope + exclusion rules + function initLightbox() { + document.addEventListener('click', function (e) { + // Also support a[data-imgbox] + var anchor = e.target.closest('a[data-imgbox]'); + if (anchor) { + e.preventDefault(); + var href = anchor.getAttribute('href') || anchor.getAttribute('data-imgbox'); + openLightbox(href, '', [{ src: href, fullSrc: href, caption: '' }], 0, anchor); + return; + } + + var img = e.target.closest(TRIGGER_SEL); + if (!img) return; + if (img.width < 100 || img.height < 100) return; + if (!lightboxEnabled() || isNoLightbox(img)) return; + + e.preventDefault(); + + var container = img.closest(CONTAINER_SEL); + if (!container) return; + + var allImages = container.querySelectorAll('img'); + var gallery = []; + var foundIndex = 0; + + allImages.forEach(function (el) { + if (el.width < 100 || el.height < 100) return; + if (isNoLightbox(el)) return; + var full = resolveFullUrl(el); + var caption = el.alt || ''; + var figcaption = el.closest('figure') ? el.closest('figure').querySelector('figcaption') : null; + if (figcaption) caption = figcaption.textContent; + gallery.push({ src: el.dataset.src || el.src, fullSrc: full, caption: caption }); + if (el === img) foundIndex = gallery.length - 1; + }); + + if (gallery.length > 0) { + openLightbox(img.dataset.src || img.src, img.alt, gallery, foundIndex, img); + } + }); + } + + // 暴露到全局 + window.AksrtLightbox = { + open: openLightbox, + close: closeLightbox, + }; + + /* ============================================================ + * 2. 滚动方向感知 + * ============================================================ */ + // Scroll direction is now handled by aksrt-runtime.js body scroll bus + // (body.scroll-down / body.scroll-up / body.body-scroll). + // This stub syncs the legacy aksrt-* prefixed classes for backward compat. + function initScrollDirection() { + var body = document.body; + + function sync() { + body.classList.toggle('aksrt-body-scroll', body.classList.contains('body-scroll')); + body.classList.toggle('aksrt-scroll-down', body.classList.contains('scroll-down')); + body.classList.toggle('aksrt-scroll-up', body.classList.contains('scroll-up')); + + body.classList.add('aksrt-scrolling'); + clearTimeout(body._aksrtScrollTimer); + body._aksrtScrollTimer = setTimeout(function () { + body.classList.remove('aksrt-scrolling'); + }, 300); + } + + function onScroll() { + requestAnimationFrame(sync); + } + + window.addEventListener('scroll', onScroll, { passive: true }); + } + + /* ============================================================ + * 3. 导航溢出折叠 + * 由 legacy main.js 的 navFolding 负责。 + * 此处不再重复实现,避免两套逻辑争抢 .aksrt-nav-more 节点 + * 触发 NotFoundError: insertBefore (moreLi 已被对方 remove)。 + * ============================================================ */ + + /* ============================================================ + * 初始化入口 + * ============================================================ */ + document.addEventListener('DOMContentLoaded', function () { + initLightbox(); + initScrollDirection(); + }); + +})(); +; + +/* source: assets/js/enhancements2.js */ +/** + * AKSRT Enhancements Part 2 — 高级交互组件 + * + * 包含:3D 悬停倾斜、滚动触发动画、封面轮播、打赏按钮 + * + * @package AKSRT + */ + +(function () { + 'use strict'; + + /* ============================================================ + * 1. 3D 悬停倾斜效果 (vanilla-tilt 轻量版) + * ============================================================ */ + function initTilt() { + document.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) { + if (el.dataset.tiltInit) return; + el.dataset.tiltInit = '1'; + + var maxTilt = parseInt(el.dataset.aksrtTiltMax || '15', 10); + var perspective = parseInt(el.dataset.aksrtTiltPerspective || '1000', 10); + var scale = parseFloat(el.dataset.aksrtTiltScale || '1.03'); + var speed = parseInt(el.dataset.aksrtTiltSpeed || '400', 10); + var glare = el.dataset.aksrtTiltGlare === '1'; + + el.style.transformStyle = 'preserve-3d'; + el.style.transition = 'transform ' + speed + 'ms ease'; + + var glareEl = null; + if (glare) { + glareEl = document.createElement('div'); + glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity ' + speed + 'ms;'; + el.style.position = el.style.position || 'relative'; + el.style.overflow = 'hidden'; + el.appendChild(glareEl); + } + + el.addEventListener('mousemove', function (e) { + var rect = el.getBoundingClientRect(); + var x = (e.clientX - rect.left) / rect.width; + var y = (e.clientY - rect.top) / rect.height; + var tiltX = (0.5 - y) * maxTilt; + var tiltY = (x - 0.5) * maxTilt; + + el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + tiltX + 'deg) rotateY(' + tiltY + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')'; + + if (glareEl) { + glareEl.style.opacity = '1'; + glareEl.style.background = 'linear-gradient(135deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%)'; + glareEl.style.transform = 'translate(' + ((x - 0.5) * 100) + '%, ' + ((y - 0.5) * 100) + '%)'; + } + }); + + el.addEventListener('mouseleave', function () { + el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)'; + if (glareEl) glareEl.style.opacity = '0'; + }); + }); + } + + /* ============================================================ + * 2. 滚动触发动画 (IntersectionObserver) + * ============================================================ */ + function initScrollAnimations() { + if (!('IntersectionObserver' in window)) { + // 不支持时直接显示所有元素 + document.querySelectorAll('[data-aksrt-animate]').forEach(function (el) { + el.style.opacity = '1'; + el.style.transform = 'none'; + }); + return; + } + + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + + var el = entry.target; + var animation = el.dataset.aksrtAnimate || 'fadeInUp'; + var delay = parseInt(el.dataset.aksrtAnimateDelay || '0', 10); + var duration = parseInt(el.dataset.aksrtAnimateDuration || '600', 10); + + setTimeout(function () { + el.style.transition = 'opacity ' + duration + 'ms ease, transform ' + duration + 'ms ease'; + el.style.opacity = '1'; + el.style.transform = 'translateY(0) translateX(0) scale(1)'; + el.classList.add('aksrt-animated'); + }, delay); + + observer.unobserve(el); + }); + }, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px', + }); + + document.querySelectorAll('[data-aksrt-animate]').forEach(function (el) { + var animation = el.dataset.aksrtAnimate || 'fadeInUp'; + + // 设置初始状态 + el.style.opacity = '0'; + switch (animation) { + case 'fadeIn': + el.style.transform = 'none'; + break; + case 'fadeInUp': + el.style.transform = 'translateY(30px)'; + break; + case 'fadeInDown': + el.style.transform = 'translateY(-30px)'; + break; + case 'fadeInLeft': + el.style.transform = 'translateX(-30px)'; + break; + case 'fadeInRight': + el.style.transform = 'translateX(30px)'; + break; + case 'zoomIn': + el.style.transform = 'scale(0.9)'; + break; + default: + el.style.transform = 'translateY(30px)'; + } + + observer.observe(el); + }); + } + + /* ============================================================ + * 5. 粘性元素 (Sticky) + * ============================================================ */ + function initSticky() { + document.querySelectorAll('[data-aksrt-sticky]').forEach(function (el) { + var top = parseInt(el.dataset.aksrtStickyTop || '80', 10); + var offset = parseInt(el.dataset.aksrtStickyOffset || '0', 10); + + function update() { + var rect = el.getBoundingClientRect(); + var parentRect = el.parentElement.getBoundingClientRect(); + + if (parentRect.top < top && parentRect.bottom > el.offsetHeight + top) { + el.style.position = 'sticky'; + el.style.top = top + 'px'; + } + } + + window.addEventListener('scroll', update, { passive: true }); + }); + } + + /* ============================================================ + * 6. 视差滚动 + * ============================================================ */ + function initParallax() { + var elements = document.querySelectorAll('[data-aksrt-parallax]'); + if (elements.length === 0) return; + + var ticking = false; + + function update() { + var scrollTop = window.pageYOffset; + elements.forEach(function (el) { + var speed = parseFloat(el.dataset.aksrtParallax || '0.3'); + var rect = el.getBoundingClientRect(); + var center = rect.top + rect.height / 2; + var windowCenter = window.innerHeight / 2; + var offset = (center - windowCenter) * speed; + + el.style.transform = 'translateY(' + offset + 'px)'; + }); + ticking = false; + } + + window.addEventListener('scroll', function () { + if (!ticking) { + requestAnimationFrame(update); + ticking = true; + } + }, { passive: true }); + } + + /* ============================================================ + * 7. 涟漪点击效果 + * ============================================================ */ + function initRipple() { + document.addEventListener('click', function (e) { + var el = e.target.closest('.aksrt-ripple'); + if (!el) return; + + var rect = el.getBoundingClientRect(); + var x = e.clientX - rect.left; + var y = e.clientY - rect.top; + var size = Math.max(rect.width, rect.height) * 2; + + var wave = document.createElement('span'); + wave.className = 'aksrt-ripple__wave'; + wave.style.width = wave.style.height = size + 'px'; + wave.style.left = (x - size / 2) + 'px'; + wave.style.top = (y - size / 2) + 'px'; + + el.appendChild(wave); + + wave.addEventListener('animationend', function () { + wave.remove(); + }); + }); + } + + /* ============================================================ + * 8. 表单浮动标签自动初始化 + * ============================================================ */ + function initFloatLabels() { + document.querySelectorAll('.aksrt-float-label__input').forEach(function (input) { + // 检查初始值 + if (input.value) { + input.setAttribute('data-has-value', '1'); + } + input.addEventListener('input', function () { + if (this.value) { + this.setAttribute('data-has-value', '1'); + } else { + this.removeAttribute('data-has-value'); + } + }); + }); + } + + /* ============================================================ + * 8. 签到日历可视化 + * ============================================================ */ + function initSigninCalendar() { + var calendar = document.querySelector('[data-aksrt-signin-calendar]'); + if (!calendar) return; + + var data = []; + try { + data = JSON.parse(calendar.dataset.aksrtSigninCalendar || '[]'); + } catch (e) {} + + // 生成最近 30 天的格子 + var today = new Date(); + var html = ''; + calendar.innerHTML = html; + } + + /* ============================================================ + * 初始化入口 + * ============================================================ */ + document.addEventListener('DOMContentLoaded', function () { + initTilt(); + initScrollAnimations(); + initRipple(); + initSticky(); + initParallax(); + initFloatLabels(); + initSigninCalendar(); + document.addEventListener('aksrt:modal:open', function () { + // Remote modal may inject calendar markup after open. + window.setTimeout(initSigninCalendar, 30); + }); + }); + + // 暴露到全局供动态内容使用 + window.AksrtEnhancements = { + initTilt: initTilt, + initScrollAnimations: initScrollAnimations, + initRipple: initRipple, + }; + +})(); +; + +/* source: assets/js/color-extract.js */ +/** + * AKSRT Color Extract + * + * Extracts dominant colors from images and applies them as dynamic accents. + * Uses canvas-based color sampling — no external dependencies. + * + * Usage: + * + * + * + * @package AKSRT + */ + +(function () { + 'use strict'; + + var canvas = null; + var ctx = null; + + function getCanvas() { + if (!canvas) { + canvas = document.createElement('canvas'); + canvas.width = 40; + canvas.height = 40; + ctx = canvas.getContext('2d', { willReadFrequently: true }); + } + return { canvas: canvas, ctx: ctx }; + } + + function extractColor(img) { + try { + var c = getCanvas(); + c.ctx.drawImage(img, 0, 0, 40, 40); + var data = c.ctx.getImageData(0, 0, 40, 40).data; + + var r = 0, g = 0, b = 0, count = 0; + var step = 4; // sample every 4th pixel for speed + + for (var i = 0; i < data.length; i += 4 * step) { + var pr = data[i], pg = data[i + 1], pb = data[i + 2]; + // Skip very dark or very light pixels + var brightness = (pr + pg + pb) / 3; + if (brightness < 20 || brightness > 240) continue; + // Skip near-transparent + if (data[i + 3] < 128) continue; + + r += pr; + g += pg; + b += pb; + count++; + } + + if (count === 0) return null; + + r = Math.round(r / count); + g = Math.round(g / count); + b = Math.round(b / count); + + return { r: r, g: g, b: b, hex: rgbToHex(r, g, b) }; + } catch (e) { + // CORS or tainted canvas — silently fail + return null; + } + } + + function rgbToHex(r, g, b) { + return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + } + + function lighten(r, g, b, amount) { + return rgbToHex( + Math.min(255, r + amount), + Math.min(255, g + amount), + Math.min(255, b + amount) + ); + } + + function darken(r, g, b, amount) { + return rgbToHex( + Math.max(0, r - amount), + Math.max(0, g - amount), + Math.max(0, b - amount) + ); + } + + function applyColor(target, color) { + if (!target || !color) return; + + var r = color.r, g = color.g, b = color.b; + target.style.setProperty('--aksrt-dynamic-color', color.hex); + target.style.setProperty('--aksrt-dynamic-color-rgb', r + ',' + g + ',' + b); + target.style.setProperty('--aksrt-dynamic-color-light', lighten(r, g, b, 60)); + target.style.setProperty('--aksrt-dynamic-color-dark', darken(r, g, b, 40)); + + // Add a subtle tinted background if target doesn't already have one + if (!target.style.background && !target.style.backgroundColor) { + target.style.setProperty('--aksrt-dynamic-bg', 'rgba(' + r + ',' + g + ',' + b + ',0.05)'); + } + } + + function processImage(img) { + if (img.dataset.aksrtColorDone) return; + + // Need crossOrigin for canvas access + if (!img.crossOrigin && img.src.indexOf(window.location.origin) !== 0) { + img.crossOrigin = 'anonymous'; + } + + function extract() { + var color = extractColor(img); + if (!color) return; + + img.dataset.aksrtColorDone = '1'; + + var targetSelector = img.dataset.aksrtColorTarget; + var target = targetSelector + ? img.closest(targetSelector) || document.querySelector(targetSelector) + : img.parentElement; + + applyColor(target, color); + } + + if (img.complete && img.naturalWidth > 0) { + extract(); + } else { + img.addEventListener('load', extract, { once: true }); + } + } + + function init() { + document.querySelectorAll('[data-aksrt-color-extract]').forEach(processImage); + } + + // Public API + window.AksrtColorExtract = { + extract: extractColor, + apply: applyColor, + process: processImage, + init: init + }; + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); +; + +/* source: assets/js/effects.js */ +/** + * AKSRT Effects — count-up / countdown / marquee / 3D tilt / FPS detection + * + * 全部渐进增强,尊重 prefers-reduced-motion。 + * + * @package AKSRT + */ + +(function () { + 'use strict'; + + var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + /* ============================================================ + * 1. 数字递增 (count-up) — IntersectionObserver 触发 + * 用法:0 + * ============================================================ */ + function initCountUp() { + if (reducedMotion) return; + if (!('IntersectionObserver' in window)) return; + + var els = document.querySelectorAll('[data-aksrt-countup]'); + if (!els.length) return; + + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + var el = entry.target; + observer.unobserve(el); + + var target = parseFloat(el.dataset.target || '0'); + var duration = parseInt(el.dataset.duration || '2000', 10); + var decimals = (el.dataset.decimals || '0|').split('|'); + var prefix = decimals[1] || ''; + var dec = parseInt(decimals[0], 10) || 0; + var start = 0; + var startTime = null; + + function step(timestamp) { + if (!startTime) startTime = timestamp; + var progress = Math.min((timestamp - startTime) / duration, 1); + // ease-out cubic + var eased = 1 - Math.pow(1 - progress, 3); + var current = start + (target - start) * eased; + el.textContent = prefix + current.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ','); + if (progress < 1) { + requestAnimationFrame(step); + } else { + el.textContent = prefix + target.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ','); + } + } + + requestAnimationFrame(step); + }); + }, { threshold: 0.3 }); + + els.forEach(function (el) { observer.observe(el); }); + } + + /* ============================================================ + * 2. 倒计时组件 + * 用法:--:--:-- + * ============================================================ */ + function initCountdown() { + var els = document.querySelectorAll('[data-aksrt-countdown]'); + if (!els.length) return; + + function pad(n) { return n < 10 ? '0' + n : '' + n; } + + function update() { + var now = Date.now(); + els.forEach(function (el) { + var end = new Date(el.dataset.end).getTime(); + var diff = end - now; + if (diff <= 0) { + el.textContent = el.dataset.expired || '已结束'; + return; + } + var d = Math.floor(diff / 86400000); + var h = Math.floor((diff % 86400000) / 3600000); + var m = Math.floor((diff % 3600000) / 60000); + var s = Math.floor((diff % 60000) / 1000); + var format = el.dataset.format || 'dhms'; + if (format === 'dhms') { + el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + ':' + pad(m) + ':' + pad(s); + } else if (format === 'hms') { + el.textContent = pad(h + d * 24) + ':' + pad(m) + ':' + pad(s); + } else { + el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + '时' + pad(m) + '分' + pad(s) + '秒'; + } + }); + } + + update(); + setInterval(update, 1000); + } + + /* ============================================================ + * 4. 3D 悬停倾斜 (vanilla tilt) + * 用法:
...
+ * ============================================================ */ + function initTilt() { + if (reducedMotion) return; + + document.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) { + if (el.dataset.tiltBound) return; + el.dataset.tiltBound = '1'; + + var maxDeg = parseFloat(el.dataset.tiltMax || '10'); + var perspective = parseFloat(el.dataset.tiltPerspective || '800'); + var scale = parseFloat(el.dataset.tiltScale || '1.03'); + var glare = el.dataset.tiltGlare === '1'; + var glareEl = null; + + if (glare) { + glareEl = document.createElement('div'); + glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(135deg,rgba(255,255,255,0) 0%,rgba(255,255,255,0.25) 100%);opacity:0;transition:opacity 0.2s;'; + el.style.position = el.style.position || 'relative'; + el.style.overflow = 'hidden'; + el.appendChild(glareEl); + } + + el.addEventListener('mousemove', function (e) { + var rect = el.getBoundingClientRect(); + var x = (e.clientX - rect.left) / rect.width; + var y = (e.clientY - rect.top) / rect.height; + var rotateX = (0.5 - y) * maxDeg; + var rotateY = (x - 0.5) * maxDeg; + el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')'; + if (glareEl) glareEl.style.opacity = '1'; + }); + + el.addEventListener('mouseleave', function () { + el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)'; + if (glareEl) glareEl.style.opacity = '0'; + }); + }); + } + + /* ============================================================ + * 5. FPS 检测 + 低端设备降级 + * ============================================================ */ + function initFpsDetection() { + if (!('requestAnimationFrame' in window)) return; + + var frames = 0; + var startTime = performance.now(); + + function countFrames() { + frames++; + var elapsed = performance.now() - startTime; + if (elapsed >= 1000) { + var fps = Math.round(frames * 1000 / elapsed); + if (fps < 24) { + document.body.classList.add('fps-accelerat'); + } + return; // 只检测一次 + } + requestAnimationFrame(countFrames); + } + + requestAnimationFrame(countFrames); + } + + /* ============================================================ + * 初始化入口 + * ============================================================ */ + document.addEventListener('DOMContentLoaded', function () { + initCountUp(); + initCountdown(); + if (typeof initMarquee === 'function') { + initMarquee(); + } + initTilt(); + initFpsDetection(); + }); + +})(); +; diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index f85f56ec..ac6dcdf2 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -46,47 +46,22 @@ trait ScriptsPart1b { wp_add_inline_style('aksrt-style', '.lazyloaded{--lazy-animation:lazy_scale!important;}'); } - $core_bundle = get_template_directory() . '/assets/css/build/core.min.css'; - if (file_exists($core_bundle)) { - wp_enqueue_style( - 'aksrt-core-bundle', - $base_uri . '/assets/css/build/core.min.css', - ['aksrt-style'], - filemtime($core_bundle) ?: $version - ); - $core_dep = 'aksrt-core-bundle'; - } else { - $tokens_path = get_template_directory() . '/assets/css/core/tokens.css'; - wp_enqueue_style( - 'aksrt-tokens', - $base_uri . '/assets/css/core/tokens.css', - ['aksrt-style'], - file_exists($tokens_path) ? (string) filemtime($tokens_path) : $version - ); - foreach (['reset', 'typography', 'buttons', 'forms', 'alerts'] as $slug) { - $core_path = get_template_directory() . '/assets/css/core/' . $slug . '.css'; - wp_enqueue_style( - 'aksrt-core-' . $slug, - $base_uri . '/assets/css/core/' . $slug . '.css', - ['aksrt-tokens'], - file_exists($core_path) ? (string) filemtime($core_path) : $version - ); - } - $core_dep = 'aksrt-tokens'; - } + $core_bundle = get_template_directory() . '/assets/css/build/core.css'; + wp_enqueue_style( + 'aksrt-core', + $base_uri . '/assets/css/build/core.css', + ['aksrt-style'], + file_exists($core_bundle) ? (string) filemtime($core_bundle) : $version + ); - $ext_css_files = ['animations', 'components-ext', 'color-utilities']; - foreach ($ext_css_files as $slug) { - $ext_path = get_template_directory() . '/assets/css/core/' . $slug . '.css'; - if (file_exists($ext_path)) { - wp_enqueue_style( - 'aksrt-ext-' . $slug, - $base_uri . '/assets/css/core/' . $slug . '.css', - [$core_dep], - filemtime($ext_path) ?: $version - ); - } - } + $public_bundle = get_template_directory() . '/assets/css/build/public.css'; + wp_enqueue_style( + 'aksrt-public', + $base_uri . '/assets/css/build/public.css', + ['aksrt-core', '_fontawesome'], + file_exists($public_bundle) ? (string) filemtime($public_bundle) : $version + ); + $core_dep = 'aksrt-public'; $deps = [$core_dep]; $enqueue_module = function ($slug) use ($base_uri, $version, $deps) { @@ -99,16 +74,6 @@ trait ScriptsPart1b { ); }; - // AKSRT domain styles load after legacy main.css. The old aggregate is - // intentionally not used because it contains the removed navigation CSS. - $enqueue_module('base'); - $enqueue_module('header-userbox'); - $enqueue_module('card-primitives'); - $enqueue_module('overlay-primitives'); - $enqueue_module('layout'); - $enqueue_module('footer'); - $enqueue_module('floating-buttons'); - $enqueue_module('enhancements'); if (!is_user_logged_in() || (bool) get_query_var('aksrt_sign')) { $auth_css = get_template_directory() . '/assets/css/routes/sign.css'; wp_enqueue_style( @@ -119,19 +84,6 @@ trait ScriptsPart1b { ); } - // 4a-b. 全局补充样式(骨架屏、空状态、分页、加载指示器) - foreach (['skeleton', 'empty-state', 'pagination', 'ajax-spinner', 'modal-runtime'] as $slug) { - $css_path = get_template_directory() . '/assets/css/frontend/' . $slug . '.css'; - if (file_exists($css_path)) { - wp_enqueue_style( - 'aksrt-' . $slug, - $base_uri . '/assets/css/frontend/' . $slug . '.css', - [$core_dep], - filemtime($css_path) ?: $version - ); - } - } - // Shared VIP purchase/poll coordinator for modal and credits surfaces. $vip_on = class_exists('\AKSRT\Settings\Settings') && !empty(\AKSRT\Settings\Settings::get_payment_vip_settings()['enabled']); @@ -152,7 +104,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-vip-coordinator', $base_uri . '/assets/js/vip-purchase-coordinator.js', - ['aksrt-core-request'], + ['aksrt-runtime'], (string) filemtime($vip_coordinator), true ); @@ -174,7 +126,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-pay-vip', $base_uri . '/assets/js/pay-vip.js', - ['loader_js', 'aksrt-lazy-image-attr', 'aksrt-core-request', 'aksrt-core-modal-runtime', 'aksrt-vip-coordinator', 'aksrt-notyf'], + ['aksrt-runtime', 'aksrt-vip-coordinator'], (string) filemtime($vip_js), true ); @@ -313,7 +265,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-report', $base_uri . '/assets/js/report.js', - ['aksrt-notyf'], + ['aksrt-runtime'], filemtime($report_js_path) ?: $version, true ); @@ -327,7 +279,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-author', $base_uri . '/assets/js/author.js', - ['loader_js', 'aksrt-notyf'], + ['aksrt-runtime'], (string) filemtime($author_js_path), true ); @@ -415,7 +367,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-order-center', $base_uri . '/assets/js/order-center.js', - ['loader_js', 'aksrt-lazy-image-attr', 'aksrt-rehydrate', 'aksrt-core-request', 'aksrt-core-modal-runtime'], + ['aksrt-runtime'], (string) filemtime($order_center_js), true ); @@ -427,14 +379,14 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-route-user-center-auth', $base_uri . '/assets/js/routes/user-center-auth.js', - ['loader_js', 'aksrt-core-request', 'aksrt-core-modal-runtime'], + ['aksrt-runtime'], (string) filemtime($identity_js), true ); } $user_center_deps = file_exists($identity_js) - ? ['loader_js', 'aksrt-route-user-center-auth', 'aksrt-notyf'] - : ['loader_js', 'aksrt-notyf']; + ? ['aksrt-runtime', 'aksrt-route-user-center-auth'] + : ['aksrt-runtime']; if ($has_orders_panel && wp_script_is('aksrt-order-center', 'registered')) { $user_center_deps[] = 'aksrt-order-center'; } @@ -455,7 +407,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-route-search', $base_uri . '/assets/css/routes/search.css', - ['aksrt-tokens', 'aksrt-post-card'], + ['aksrt-public', 'aksrt-post-card'], $version ); } @@ -465,7 +417,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-route-categories', $base_uri . '/assets/css/routes/categories.css', - ['aksrt-tokens'], + ['aksrt-public'], $version ); } @@ -475,7 +427,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-route-friends', $base_uri . '/assets/css/routes/friends.css', - ['aksrt-tokens'], + ['aksrt-public'], file_exists(get_template_directory() . '/assets/css/routes/friends.css') ? (string) filemtime(get_template_directory() . '/assets/css/routes/friends.css') : $version @@ -490,7 +442,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-page-navs', $base_uri . '/assets/js/page-navs.js', - ['aksrt-lazy-image-attr'], + ['aksrt-runtime'], (string) filemtime($page_navs_js), true ); @@ -501,7 +453,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-route-posts-navs', $base_uri . '/assets/css/routes/posts-navs.css', - ['aksrt-tokens'], + ['aksrt-public'], (string) filemtime($pn_css) ); } @@ -520,7 +472,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-posts-navs-page', $base_uri . '/assets/js/posts-navs-page.js', - ['jquery', 'aksrt-notyf'], + ['aksrt-runtime'], (string) filemtime($pn_js), true ); @@ -534,7 +486,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-route-archives', $base_uri . '/assets/css/routes/archives.css', - ['aksrt-tokens'], + ['aksrt-public'], (string) filemtime($ar_css) ); } @@ -547,7 +499,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-route-ban-appeal', $base_uri . '/assets/css/routes/ban-appeal.css', - ['aksrt-tokens'], + ['aksrt-public'], (string) filemtime($ba_css) ); } @@ -556,7 +508,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-ban-appeal-page', $base_uri . '/assets/js/ban-appeal-page.js', - ['aksrt-notyf'], + ['aksrt-runtime'], (string) filemtime($ba_js), true ); @@ -570,7 +522,7 @@ trait ScriptsPart1b { wp_enqueue_style( 'aksrt-messages', $base_uri . '/assets/css/frontend/messages.css', - ['aksrt-tokens'], + ['aksrt-public'], (string) filemtime($messages_css) ); } @@ -651,7 +603,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-cms-download', $base_uri . '/assets/js/cms-download.js', - ['loader_js', 'aksrt-runtime', 'aksrt-core-request'], + ['aksrt-runtime'], file_exists($cms_download_js_path) ? (string) filemtime($cms_download_js_path) : $version, true ); @@ -659,35 +611,19 @@ trait ScriptsPart1b { if ($is_fixed_homepage) { $enqueue_module('post-card'); - $homepage_style_deps = ['_main', 'aksrt-layout', 'aksrt-post-card']; + $homepage_style_deps = ['aksrt-public', 'aksrt-post-card']; $homepage_style_deps = array_values(array_filter( $homepage_style_deps, static fn(string $handle): bool => wp_style_is($handle, 'registered') )); - // Enqueue leaf stylesheets directly so each file receives its own cache version. - $homepage_style_parts = [ - 'base', - 'ajax-tab-skeleton-placeholder', - 'ajax-tab-error-state', - ]; - $part_deps = $homepage_style_deps; - foreach ($homepage_style_parts as $part_index => $part_name) { - $is_last_part = $part_index === count($homepage_style_parts) - 1; - $part_handle = $is_last_part - ? 'aksrt-homepage-fixed' - : 'aksrt-homepage-fixed-' . sanitize_key($part_name); - $part_relative_path = '/assets/css/frontend/homepage-fixed/' . $part_name . '.css'; - $part_path = get_template_directory() . $part_relative_path; - - wp_enqueue_style( - $part_handle, - $base_uri . $part_relative_path, - $part_deps, - file_exists($part_path) ? (string) filemtime($part_path) : $version - ); - $part_deps = [$part_handle]; - } + $homepage_bundle = get_template_directory() . '/assets/css/build/homepage.css'; + wp_enqueue_style( + 'aksrt-homepage-fixed', + $base_uri . '/assets/css/build/homepage.css', + $homepage_style_deps, + file_exists($homepage_bundle) ? (string) filemtime($homepage_bundle) : $version + ); } // 积分相关:已登录 + 启用积分功能时全站加载 @@ -700,7 +636,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-credits', $base_uri . '/assets/js/credits.js', - ['loader_js', 'aksrt-lazy-image-attr', 'aksrt-core-request', 'aksrt-core-modal-runtime', 'aksrt-vip-coordinator', 'aksrt-notyf'], + ['aksrt-runtime', 'aksrt-vip-coordinator'], file_exists($credits_js_path) ? (string) filemtime($credits_js_path) : $version, true ); @@ -713,7 +649,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-signin', $base_uri . '/assets/js/signin.js', - ['aksrt-core-request', 'aksrt-notyf'], + ['aksrt-runtime'], file_exists($signin_js_path) ? (string) filemtime($signin_js_path) : $version, true ); @@ -732,7 +668,7 @@ trait ScriptsPart1b { wp_enqueue_script( 'aksrt-user-badge-actions', $base_uri . '/assets/js/user-badge-actions.js', - ['loader_js', 'aksrt-core-modal-runtime'], + ['aksrt-runtime'], (string) filemtime($badge_actions_path), true ); diff --git a/src/Core/ScriptsPart1c.php b/src/Core/ScriptsPart1c.php index 53dc6dcc..98d34821 100644 --- a/src/Core/ScriptsPart1c.php +++ b/src/Core/ScriptsPart1c.php @@ -20,18 +20,6 @@ trait ScriptsPart1c { * Fonts / FA / global JS stack / aksrtData (split from enqueue_public). */ private function enqueue_public_fonts_js_and_data($base_uri, $core_dep, $deps, $is_fixed_homepage, $is_home_or_archive, $is_single_post, $is_single_resource, $version): void { - $notyf_css = get_template_directory() . '/assets/css/frontend/notyf.css'; - wp_enqueue_style( - 'aksrt-notyf', - $base_uri . '/assets/css/frontend/notyf.css', - [$core_dep, '_fontawesome'], - file_exists($notyf_css) ? (string) filemtime($notyf_css) : $version - ); - - // ===== 7. JS ===== - - $needs_post_list_media = wp_style_is('aksrt-post-card', 'enqueued'); - $runtime_uri = $base_uri . '/assets'; $runtime_loader_path = get_template_directory() . '/assets/js/runtime/loader.js'; $runtime_main_path = get_template_directory() . '/assets/js/runtime/main.min.js'; @@ -95,26 +83,15 @@ trait ScriptsPart1c { ] ); - $lazy_image_attr_path = get_template_directory() . '/assets/js/lazy-image-attr.js'; + $public_runtime_path = get_template_directory() . '/assets/js/build/public.js'; wp_enqueue_script( - 'aksrt-lazy-image-attr', - $base_uri . '/assets/js/lazy-image-attr.js', + 'aksrt-runtime', + $base_uri . '/assets/js/build/public.js', ['loader_js'], - file_exists($lazy_image_attr_path) ? (string) filemtime($lazy_image_attr_path) : $version, + file_exists($public_runtime_path) ? (string) filemtime($public_runtime_path) : $version, true ); - // AKSRT-only modules register after the legacy loader; navigation is owned by main.min.js. - wp_enqueue_script( - 'aksrt-rehydrate', - $base_uri . '/assets/js/rehydrate.js', - ['loader_js', 'aksrt-lazy-image-attr'], - file_exists(get_template_directory() . '/assets/js/rehydrate.js') - ? (string) filemtime(get_template_directory() . '/assets/js/rehydrate.js') - : $version, - true - ); - // AJAX 调试(WP_DEBUG 或 ?aksrt_ajax_debug=1):记录所有失败的 // jQuery ajax / fetch 请求,用于定位 vendor main.min.js 的 400 "0" 来源。 if (function_exists('aksrt_ajax_debug_enabled') && aksrt_ajax_debug_enabled()) { @@ -122,114 +99,19 @@ trait ScriptsPart1c { wp_enqueue_script( 'aksrt-ajax-debug', $base_uri . '/assets/js/core/ajax-debug.js', - ['loader_js'], + ['aksrt-runtime'], file_exists($ajax_debug_path) ? (string) filemtime($ajax_debug_path) : $version, true ); } - if ($needs_post_list_media) { - $grade_js = get_template_directory() . '/assets/js/grade.min.js'; - wp_enqueue_script( - 'aksrt-grade', - $base_uri . '/assets/js/grade.min.js', - [], - file_exists($grade_js) ? (string) filemtime($grade_js) : $version, - true - ); - } - - $notyf_js = get_template_directory() . '/assets/js/notyf.js'; - wp_enqueue_script( - 'aksrt-notyf', - $base_uri . '/assets/js/notyf.js', - ['jquery'], - file_exists($notyf_js) ? (string) filemtime($notyf_js) : $version, - true - ); - - // Runtime infrastructure: tbquire / confirm / scrollTopTo / [data-*] declarative - $runtime_js = get_template_directory() . '/assets/js/aksrt-runtime.js'; - if (file_exists($runtime_js)) { - wp_enqueue_script( - 'aksrt-runtime', - $base_uri . '/assets/js/aksrt-runtime.js', - ['aksrt-rehydrate', 'aksrt-notyf'], - (string) filemtime($runtime_js), - true - ); - } - - // Follow toggle: delegated [data-aksrt-follow] handler; the buttons render on - // user home / user center / single author bar and user cards anywhere. - // Depends on aksrt-runtime because aksrtData (REST root + nonce) is localized - // onto that handle and follow.js reads it at parse time. - $follow_js = get_template_directory() . '/assets/js/follow.js'; - if (file_exists($follow_js)) { - wp_enqueue_script( - 'aksrt-follow', - $base_uri . '/assets/js/follow.js', - ['aksrt-notyf', 'aksrt-runtime'], - (string) filemtime($follow_js), - true - ); - } - - // Core modules (split from main.js) — register with AksrtFrontend - $core_modules = [ - 'aksrt-core-article' => 'article.js', - 'aksrt-core-comments' => 'comments.js', - 'aksrt-core-comment-fixed' => 'comment-fixed-input.js', - 'aksrt-core-comment-move' => 'comment-move-form.js', - // Shared legacy-parity list/modal engines (before pagination consumers). - 'aksrt-core-ajax-list' => 'ajax-list.js', - 'aksrt-core-modal-telemetry' => 'modal-telemetry.js', - 'aksrt-core-request' => 'request.js', - 'aksrt-core-modal-cache' => 'modal-cache.js', - 'aksrt-core-modal-state' => 'modal-state.js', - 'aksrt-core-modal-runtime' => 'modal-runtime.js', - 'aksrt-core-modal-triggers' => 'modal-triggers.js', - 'aksrt-core-pagination' => 'pagination.js', - 'aksrt-core-archive' => 'archive.js', - 'aksrt-core-footer-tabbar' => 'footer-tabbar.js', - 'aksrt-core-misc' => 'misc.js', - ]; - $core_handles = []; - foreach ($core_modules as $handle => $file) { - $path = get_template_directory() . '/assets/js/core/' . $file; - if (file_exists($path)) { - // Depend on aksrt-runtime so window.aksrtData (localized below) is available. - $deps = ['aksrt-rehydrate', 'aksrt-runtime']; - if ($handle === 'aksrt-core-misc' && $needs_post_list_media) { - $deps[] = 'aksrt-grade'; - } elseif ($handle === 'aksrt-core-modal-runtime') { - $deps = array_merge($deps, [ - 'aksrt-core-modal-telemetry', - 'aksrt-core-request', - 'aksrt-core-modal-cache', - 'aksrt-core-modal-state', - ]); - } elseif ($handle === 'aksrt-core-modal-triggers') { - $deps[] = 'aksrt-core-modal-runtime'; - } - wp_enqueue_script( - $handle, - $base_uri . '/assets/js/core/' . $file, - $deps, - (string) filemtime($path), - true - ); - $core_handles[] = $handle; - } - } - // Code block enhancements (copy button + language label) $precode_js = get_template_directory() . '/assets/js/precode.js'; - if (file_exists($precode_js)) { + if (is_singular() && file_exists($precode_js)) { wp_enqueue_script( 'aksrt-precode', $base_uri . '/assets/js/precode.js', - [], + ['aksrt-runtime'], (string) filemtime($precode_js), true ); @@ -237,11 +119,15 @@ trait ScriptsPart1c { // Input field enhancements (auto-resize, counter, validation) $input_expand_js = get_template_directory() . '/assets/js/input-expand.js'; - if (file_exists($input_expand_js)) { + $needs_input_enhancements = is_singular() + || (bool) get_query_var('aksrt_sign') + || (bool) get_query_var('aksrt_user_center') + || (bool) get_query_var('aksrt_author_identifier'); + if ($needs_input_enhancements && file_exists($input_expand_js)) { wp_enqueue_script( 'aksrt-input-expand', $base_uri . '/assets/js/input-expand.js', - [], + ['aksrt-runtime'], (string) filemtime($input_expand_js), true ); @@ -261,11 +147,11 @@ trait ScriptsPart1c { // Section navs: article TOC / scrollspy $section_navs_js = get_template_directory() . '/assets/js/section-navs.js'; - if (file_exists($section_navs_js)) { + if (is_singular() && file_exists($section_navs_js)) { wp_enqueue_script( 'aksrt-section-navs', $base_uri . '/assets/js/section-navs.js', - ['aksrt-rehydrate', 'aksrt-runtime'], + ['aksrt-runtime'], (string) filemtime($section_navs_js), true ); @@ -273,92 +159,35 @@ trait ScriptsPart1c { // Video player (MP4/HLS/FLV) $video_player_js = get_template_directory() . '/assets/js/video-player.js'; - if (file_exists($video_player_js)) { + if (is_singular() && file_exists($video_player_js)) { wp_enqueue_script( 'aksrt-video-player', $base_uri . '/assets/js/video-player.js', - [], + ['aksrt-runtime'], (string) filemtime($video_player_js), true ); } - // Image color extraction - $color_extract_js = get_template_directory() . '/assets/js/color-extract.js'; - if (file_exists($color_extract_js)) { - wp_enqueue_script( - 'aksrt-color-extract', - $base_uri . '/assets/js/color-extract.js', - [], - (string) filemtime($color_extract_js), - true - ); - } - - // ===== 全局 UI 增强(骨架屏、悬停动效、灯箱、滚动感知)===== - $enhancements_css = get_template_directory() . '/assets/css/frontend/enhancements.css'; - if (file_exists($enhancements_css)) { - wp_enqueue_style( - 'aksrt-enhancements', - $base_uri . '/assets/css/frontend/enhancements.css', - [$core_dep], - (string) filemtime($enhancements_css) - ); - } - - $enhancements_js = get_template_directory() . '/assets/js/enhancements.js'; - if (file_exists($enhancements_js)) { - wp_enqueue_script( - 'aksrt-enhancements', - $base_uri . '/assets/js/enhancements.js', - ['loader_js'], - (string) filemtime($enhancements_js), - true - ); - } - - $enhancements2_js = get_template_directory() . '/assets/js/enhancements2.js'; - if (file_exists($enhancements2_js)) { - wp_enqueue_script( - 'aksrt-enhancements2', - $base_uri . '/assets/js/enhancements2.js', - ['loader_js'], - (string) filemtime($enhancements2_js), - true - ); - } - // Reward (打赏) $reward_js = get_template_directory() . '/assets/js/reward.js'; - if (file_exists($reward_js)) { + if (($is_single_post || $is_single_resource) && file_exists($reward_js)) { wp_enqueue_script( 'aksrt-reward', $base_uri . '/assets/js/reward.js', - ['loader_js', 'aksrt-core-request'], + ['aksrt-runtime'], (string) filemtime($reward_js), true ); } - // Effects (count-up, countdown, marquee, tilt, FPS detection) - $effects_js = get_template_directory() . '/assets/js/effects.js'; - if (file_exists($effects_js)) { - wp_enqueue_script( - 'aksrt-effects', - $base_uri . '/assets/js/effects.js', - ['loader_js'], - (string) filemtime($effects_js), - true - ); - } - // Yiyan (一言轮播) $yiyan_js = get_template_directory() . '/assets/js/yiyan.js'; - if (file_exists($yiyan_js)) { + if (($is_single_post || $is_single_resource) && file_exists($yiyan_js)) { wp_enqueue_script( 'aksrt-yiyan', $base_uri . '/assets/js/yiyan.js', - [], + ['aksrt-runtime'], (string) filemtime($yiyan_js), true ); @@ -366,8 +195,16 @@ trait ScriptsPart1c { // Frontend translate (默认关) // Content protection (copy notice / right-click / F12) + $content_protect = class_exists('\AKSRT\Settings\Settings') + ? [ + 'enabled' => (bool) \AKSRT\Settings\Settings::get_value('content_protect_enabled', false), + 'copy' => (bool) \AKSRT\Settings\Settings::get_value('content_protect_copy_notice', false), + 'right_click' => (bool) \AKSRT\Settings\Settings::get_value('content_protect_right_click', false), + 'devtools' => (bool) \AKSRT\Settings\Settings::get_value('content_protect_f12', false), + ] + : []; $content_protect_js = get_template_directory() . '/assets/js/content-protect.js'; - if (file_exists($content_protect_js)) { + if (in_array(true, $content_protect, true) && file_exists($content_protect_js)) { wp_enqueue_script( 'aksrt-content-protect', $base_uri . '/assets/js/content-protect.js', @@ -378,8 +215,13 @@ trait ScriptsPart1c { } // Site notice popup (global, controlled by Settings) + $site_notice_config = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_system_notice_config() + : []; + $site_notice_enabled = !empty($site_notice_config['enabled']) + && trim((string) ($site_notice_config['content'] ?? '')) !== ''; $site_notice_css = get_template_directory() . '/assets/css/frontend/site-notice.css'; - if (file_exists($site_notice_css)) { + if ($site_notice_enabled && file_exists($site_notice_css)) { wp_enqueue_style( 'aksrt-site-notice', $base_uri . '/assets/css/frontend/site-notice.css', @@ -388,11 +230,11 @@ trait ScriptsPart1c { ); } $site_notice_js = get_template_directory() . '/assets/js/site-notice.js'; - if (file_exists($site_notice_js)) { + if ($site_notice_enabled && file_exists($site_notice_js)) { wp_enqueue_script( 'aksrt-site-notice', $base_uri . '/assets/js/site-notice.js', - [], + ['aksrt-runtime'], (string) filemtime($site_notice_js), true ); @@ -400,11 +242,11 @@ trait ScriptsPart1c { // One state machine owns modal and /sign/ authentication. $auth_js = get_template_directory() . '/assets/js/auth.js'; - if (file_exists($auth_js)) { + if ((!is_user_logged_in() || (bool) get_query_var('aksrt_sign')) && file_exists($auth_js)) { wp_enqueue_script( 'aksrt-auth', $base_uri . '/assets/js/auth.js', - ['aksrt-notyf', 'aksrt-runtime'], + ['aksrt-runtime'], (string) filemtime($auth_js), true ); @@ -415,7 +257,7 @@ trait ScriptsPart1c { wp_enqueue_script( 'aksrt-homepage-fixed', $base_uri . '/assets/js/homepage-fixed.js', - ['loader_js'], + ['aksrt-runtime'], file_exists($homepage_js_path) ? (string) filemtime($homepage_js_path) : $version, true ); @@ -429,7 +271,7 @@ trait ScriptsPart1c { wp_enqueue_script( 'aksrt-comment-tools', $base_uri . '/assets/js/comment-tools.js', - ['loader_js', 'aksrt-core-comment-fixed', 'aksrt-core-comment-move'], + ['aksrt-runtime'], file_exists($comment_tools_path) ? (string) filemtime($comment_tools_path) : $version, true ); @@ -452,7 +294,7 @@ trait ScriptsPart1c { wp_enqueue_script( 'aksrt-comment-ajax', $base_uri . '/assets/js/comment-ajax.js', - ['loader_js', 'aksrt-core-comments'], + ['aksrt-runtime'], (string) filemtime($comment_ajax_path), true ); @@ -465,7 +307,7 @@ trait ScriptsPart1c { wp_enqueue_script( 'aksrt-page-template', $base_uri . '/assets/js/page-template.js', - ['loader_js'], + ['aksrt-runtime'], (string) filemtime($page_template_path), true ); diff --git a/templates/pages/page-submit.php b/templates/pages/page-submit.php index f18756f9..d8d26db7 100644 --- a/templates/pages/page-submit.php +++ b/templates/pages/page-submit.php @@ -57,7 +57,7 @@ if (file_exists($js)) { wp_enqueue_script( 'aksrt-front-submit', get_template_directory_uri() . '/assets/js/front-submit.js', - ['jquery', 'aksrt-notyf'], + ['aksrt-runtime'], (string) filemtime($js), true ); diff --git a/tests/widget-system/auth-source-contract.test.cjs b/tests/widget-system/auth-source-contract.test.cjs index 12e31ba7..75bc2003 100644 --- a/tests/widget-system/auth-source-contract.test.cjs +++ b/tests/widget-system/auth-source-contract.test.cjs @@ -22,7 +22,7 @@ test('modal and sign page use one renderer and one state machine', () => { assert.match(shared, /data-auth-panel="bind"/); assert.match(shared, /data-auth-panel="oauth"/); assert.match(scripts, /assets\/js\/auth\.js/); - assert.match(scripts, /'aksrt-notyf', 'aksrt-runtime'/); + assert.match(scripts, /'aksrt-auth',[\s\S]*?assets\/js\/auth\.js[\s\S]*?\['aksrt-runtime'\]/); assert.doesNotMatch(scripts, /modal-auth\.js|sign-register\.js/); assert.doesNotMatch(runtime, /signsubmit-loader|tbquire\(\['sign-register'\]\)|\.on\('click', '\.(?:signin|signup)-loader'/); assert.doesNotMatch(loader, /sign-register/); diff --git a/tests/widget-system/lazyload-contract.test.cjs b/tests/widget-system/lazyload-contract.test.cjs index feb45c34..fd6ebcdc 100644 --- a/tests/widget-system/lazyload-contract.test.cjs +++ b/tests/widget-system/lazyload-contract.test.cjs @@ -126,18 +126,20 @@ test('the legacy runtime owns lazySizes while AKSRT consumers share one image-at const bridge = read('assets/js/lazy-image-attr.js'); const scripts = read('src/Core/ScriptsPart1c.php'); const routeScripts = read('src/Core/ScriptsPart1b.php'); + const builder = read('tools/build-public-assets.mjs'); const pagination = read('assets/js/core/pagination.js'); const widgets = read('assets/widgets/frontend.js'); assert.match(runtime, /window\.lazySizes = lazySizes/); assert.match(bridge, /window\.aksrtGetLazyImageAttr = function/); - assert.match(scripts, /'aksrt-lazy-image-attr'/); - assert.match(scripts, /'aksrt-rehydrate'[\s\S]*?\['loader_js', 'aksrt-lazy-image-attr'\]/); + assert.match(builder, /assets\/js\/lazy-image-attr\.js/); + assert.match(builder, /assets\/js\/rehydrate\.js/); + assert.match(scripts, /assets\/js\/build\/public\.js'[\s\S]*?\['loader_js'\]/); for (const file of ['pay-vip.js', 'order-center.js', 'page-navs.js', 'credits.js']) { const blocks = enqueueBlocksFor(routeScripts, file); assert.ok(blocks.length > 0, `${file} must be enqueued`); for (const block of blocks) { - assert.match(block, /aksrt-lazy-image-attr/, `${file} must depend on the image-attribute bridge`); + assert.match(block, /aksrt-runtime/, `${file} must depend on the bundled image-attribute bridge`); } } assert.match(runtime, /lazybeforeunveil/); diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs index 0aa9a6fb..a89c7039 100644 --- a/tests/widget-system/post-visual-contract.test.cjs +++ b/tests/widget-system/post-visual-contract.test.cjs @@ -277,7 +277,7 @@ test('AJAX pagination can recover, caps auto loading and rehydrates appended med assert.match(media, /window\.Grade\(container/); assert.match(media, /linear-gradient\(135deg/); assert.match(media, /aksrtGradientState/); - assert.match(read('src/Core/ScriptsPart1c.php'), /'aksrt-grade'/); + assert.match(read('tools/build-public-assets.mjs'), /assets\/js\/grade\.min\.js/); }); test('the pinned visual baseline records all source checksums', () => { diff --git a/tests/widget-system/toast-contract.test.cjs b/tests/widget-system/toast-contract.test.cjs index f280dbb9..47f9f3fd 100644 --- a/tests/widget-system/toast-contract.test.cjs +++ b/tests/widget-system/toast-contract.test.cjs @@ -173,6 +173,7 @@ test('clicking a notification uses the legacy exit class and removal delay', () test('notyf CSS and WordPress enqueue match the reference contract', () => { const css = read('assets/css/frontend/notyf.css'); const scripts = read('src/Core/ScriptsPart1c.php'); + const builder = read('tools/build-public-assets.mjs'); assert.match(css, /\.notyn\s*\{[\s\S]*?bottom:\s*6em;[\s\S]*?max-width:\s*320px;[\s\S]*?z-index:\s*10000000;/); assert.match(css, /\.notyf\s*\{[\s\S]*?position:\s*relative;[\s\S]*?font-size:\s*14px;[\s\S]*?line-height:\s*1\.42857143;[\s\S]*?min-width:\s*200px;[\s\S]*?padding:\s*1em 3em 1em 3\.4em;/); @@ -188,9 +189,9 @@ test('notyf CSS and WordPress enqueue match the reference contract', () => { assert.match(css, /content:\s*'\\f057'/); assert.match(css, /\.notyf\.load::before[\s\S]*?huan-rotate 1s/); - assert.match(scripts, /'aksrt-notyf',[\s\S]*?'\/assets\/js\/notyf\.js',[\s\S]*?\['jquery'\]/); - assert.match(scripts, /'aksrt-runtime',[\s\S]*?\['aksrt-rehydrate', 'aksrt-notyf'\]/); - assert.match(scripts, /'\/assets\/css\/frontend\/notyf\.css',[\s\S]*?'_fontawesome'/); + assert.match(builder, /assets\/js\/notyf\.js/); + assert.match(builder, /assets\/css\/frontend\/notyf\.css/); + assert.match(scripts, /'aksrt-runtime',[\s\S]*?'\/assets\/js\/build\/public\.js',[\s\S]*?\['loader_js'\]/); }); test('balance center actions use direct fixed-id notyf flows', () => { @@ -242,8 +243,8 @@ test('balance center actions use direct fixed-id notyf flows', () => { assert.doesNotMatch(credits, /function initSignin\(|\/signin\/today|POST \/signin/); assert.match(signinController, /aksrtSigninInitialized/); assert.match(credits, /aksrtPointsExchangeInitialized[\s\S]*?aksrtWithdrawInitialized/); - assert.match(scripts, /'aksrt-credits',[\s\S]*?\/assets\/js\/credits\.js',[\s\S]*?\[[^\]]*'aksrt-notyf'[^\]]*\]/); - assert.match(scripts, /\$signin_on\s*=\s*is_user_logged_in\(\)[\s\S]*?'aksrt-signin',[\s\S]*?\/assets\/js\/signin\.js',[\s\S]*?'aksrt-core-request'[\s\S]*?'aksrt-notyf'/); + assert.match(scripts, /'aksrt-credits',[\s\S]*?\/assets\/js\/credits\.js',[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\]/); + assert.match(scripts, /\$signin_on\s*=\s*is_user_logged_in\(\)[\s\S]*?'aksrt-signin',[\s\S]*?\/assets\/js\/signin\.js',[\s\S]*?'aksrt-runtime'/); assert.ok(header.indexOf('data-signin-btn') < header.indexOf('uc-header__message-btn')); assert.match(header, /uc-header__message-btn[^>]+aria-label="<\?php esc_attr_e\('消息通知'/); assert.doesNotMatch(header, /]*><\/i>\s*<\?php esc_html_e\('消息通知'/); @@ -282,7 +283,7 @@ test('VIP purchase and points exchange use direct fixed-id notyf flows', () => { assert.doesNotMatch(vip, /setTip|aksrt-vip-modal-tip/); assert.doesNotMatch(css, /\.aksrt-vip-modal-tip/); - assert.match(scripts, /'aksrt-pay-vip',[\s\S]*?\/assets\/js\/pay-vip\.js',[\s\S]*?\[[^\]]*'aksrt-notyf'[^\]]*\]/); + assert.match(scripts, /'aksrt-pay-vip',[\s\S]*?\/assets\/js\/pay-vip\.js',[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\]/); }); test('payment entry points never load the removed legacy pay runtime', () => { @@ -324,7 +325,7 @@ test('profile save uses AJAX rehydrate and a fixed-id notyf flow without query f assert.match(profile, /notyf\([^;]+?'success'[^;]+?'aksrt-profile-save-toast'\);/); assert.match(profile, /notyf\([^;]+?'danger'[^;]+?'aksrt-profile-save-toast'\);/); assert.match(profile, /fetch\(ajaxUrl, \{ method: 'POST', credentials: 'same-origin', body: data \}\)/); - assert.match(scripts, /\$user_center_deps[\s\S]*?\[[^\]]*'aksrt-notyf'[^\]]*\][\s\S]*?\/assets\/js\/routes\/user-center\.js/); + assert.match(scripts, /\$user_center_deps[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\][\s\S]*?\/assets\/js\/routes\/user-center\.js/); assert.doesNotMatch(page, /REQUEST_METHOD|profile_error|['"]saved['"]|add_query_arg/); assert.doesNotMatch([profile, form, layout, lists].join('\n'), /profile-save-msg|profile-form__submit-msg/); @@ -401,7 +402,7 @@ test('front submit actions use direct fixed-id notyf flows', () => { assert.match(submit, /needContent[^;]+?'warning'/); assert.doesNotMatch(submit, /\b(?:window\.)?alert\s*\(/); - assert.match(page, /'aksrt-front-submit',[\s\S]*?'\/assets\/js\/front-submit\.js',[\s\S]*?\[[^\]]*'aksrt-notyf'[^\]]*\]/); + assert.match(page, /'aksrt-front-submit',[\s\S]*?'\/assets\/js\/front-submit\.js',[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\]/); }); test('legacy frontend toast APIs and selectors are fully removed', () => { @@ -409,7 +410,7 @@ test('legacy frontend toast APIs and selectors are fully removed', () => { 'assets/js', 'assets/css/frontend', 'assets/css/routes', - 'assets/css/build/frontend-base.min.css.disabled', + 'assets/css/build', 'page.php', 'templates/pages/page-profile.php', ]; diff --git a/tools/build-public-assets.mjs b/tools/build-public-assets.mjs new file mode 100644 index 00000000..170acb85 --- /dev/null +++ b/tools/build-public-assets.mjs @@ -0,0 +1,145 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const cssBuildDir = path.join(repoRoot, 'assets/css/build'); +const jsBuildDir = path.join(repoRoot, 'assets/js/build'); + +const cssBundles = { + 'core.css': [ + 'assets/css/core/tokens.css', + 'assets/css/core/reset.css', + 'assets/css/core/typography.css', + 'assets/css/core/buttons.css', + 'assets/css/core/forms.css', + 'assets/css/core/alerts.css', + 'assets/css/core/animations.css', + 'assets/css/core/components-ext.css', + 'assets/css/core/color-utilities.css', + ], + 'public.css': [ + 'assets/css/frontend/base.css', + 'assets/css/frontend/header-userbox.css', + 'assets/css/frontend/card-primitives.css', + 'assets/css/frontend/overlay-primitives.css', + 'assets/css/frontend/layout.css', + 'assets/css/frontend/footer.css', + 'assets/css/frontend/floating-buttons.css', + 'assets/css/frontend/enhancements.css', + 'assets/css/frontend/skeleton.css', + 'assets/css/frontend/empty-state.css', + 'assets/css/frontend/pagination.css', + 'assets/css/frontend/ajax-spinner.css', + 'assets/css/frontend/modal-runtime.css', + 'assets/css/frontend/notyf.css', + ], + 'homepage.css': [ + 'assets/css/frontend/homepage-fixed/base.css', + 'assets/css/frontend/homepage-fixed/ajax-tab-skeleton-placeholder.css', + 'assets/css/frontend/homepage-fixed/ajax-tab-error-state.css', + ], +}; + +const publicScripts = [ + 'assets/js/lazy-image-attr.js', + 'assets/js/rehydrate.js', + 'assets/js/notyf.js', + 'assets/js/aksrt-runtime.js', + 'assets/js/grade.min.js', + 'assets/js/core/modal-telemetry.js', + 'assets/js/core/request.js', + 'assets/js/core/modal-cache.js', + 'assets/js/core/modal-state.js', + 'assets/js/core/modal-runtime.js', + 'assets/js/core/modal-triggers.js', + 'assets/js/core/article.js', + 'assets/js/core/comments.js', + 'assets/js/core/comment-fixed-input.js', + 'assets/js/core/comment-move-form.js', + 'assets/js/core/ajax-list.js', + 'assets/js/core/pagination.js', + 'assets/js/core/archive.js', + 'assets/js/core/footer-tabbar.js', + 'assets/js/core/misc.js', + 'assets/js/follow.js', + 'assets/js/enhancements.js', + 'assets/js/enhancements2.js', + 'assets/js/color-extract.js', + 'assets/js/effects.js', +]; + +function normalizePath(filePath) { + return filePath.split(path.sep).join('/'); +} + +function rewriteCssUrls(css, sourcePath, outputPath) { + const outputDir = path.dirname(outputPath); + return css.replace(/url\(\s*(['"]?)([^)'"\s]+)\1\s*\)/gi, (match, quote, rawUrl) => { + if (/^(?:data:|https?:|\/\/|\/|#|var\()/i.test(rawUrl)) { + return match; + } + + const suffixIndex = rawUrl.search(/[?#]/); + const pathname = suffixIndex === -1 ? rawUrl : rawUrl.slice(0, suffixIndex); + const suffix = suffixIndex === -1 ? '' : rawUrl.slice(suffixIndex); + const absoluteAsset = path.resolve(path.dirname(sourcePath), pathname); + let relativeAsset = normalizePath(path.relative(outputDir, absoluteAsset)); + if (!relativeAsset.startsWith('.')) { + relativeAsset = `./${relativeAsset}`; + } + const wrapper = quote || '"'; + return `url(${wrapper}${relativeAsset}${suffix}${wrapper})`; + }); +} + +async function expandCss(sourcePath, outputPath, stack = []) { + const absoluteSource = path.resolve(sourcePath); + if (stack.includes(absoluteSource)) { + throw new Error(`Circular CSS import: ${normalizePath(path.relative(repoRoot, absoluteSource))}`); + } + + const source = await readFile(absoluteSource, 'utf8'); + const importPattern = /@import\s+(?:url\(\s*)?['"]([^'"]+\.css)['"]\s*\)?\s*;/gi; + let expanded = ''; + let cursor = 0; + let match; + + while ((match = importPattern.exec(source)) !== null) { + expanded += rewriteCssUrls(source.slice(cursor, match.index), absoluteSource, outputPath); + const importedPath = path.resolve(path.dirname(absoluteSource), match[1]); + expanded += await expandCss(importedPath, outputPath, [...stack, absoluteSource]); + cursor = importPattern.lastIndex; + } + + expanded += rewriteCssUrls(source.slice(cursor), absoluteSource, outputPath); + return expanded.trimEnd(); +} + +async function buildCssBundle(outputName, sources) { + const outputPath = path.join(cssBuildDir, outputName); + const sections = []; + for (const source of sources) { + const sourcePath = path.join(repoRoot, source); + sections.push(`/* source: ${source} */\n${await expandCss(sourcePath, outputPath)}`); + } + await writeFile(outputPath, `${sections.join('\n\n')}\n`, 'utf8'); +} + +async function buildScriptBundle() { + const sections = []; + for (const source of publicScripts) { + const content = (await readFile(path.join(repoRoot, source), 'utf8')).trimEnd(); + sections.push(`/* source: ${source} */\n${content}\n;`); + } + await writeFile(path.join(jsBuildDir, 'public.js'), `${sections.join('\n\n')}\n`, 'utf8'); +} + +await mkdir(cssBuildDir, { recursive: true }); +await mkdir(jsBuildDir, { recursive: true }); +for (const [outputName, sources] of Object.entries(cssBundles)) { + await buildCssBundle(outputName, sources); +} +await buildScriptBundle(); + +console.log('Built public CSS and JS bundles.');