M22环节3完成:光湖水面效果+过渡动画,八连胜

This commit is contained in:
Awen 2026-03-09 01:10:34 +08:00
parent cdf8294982
commit b2ce11c3ee
6 changed files with 977 additions and 0 deletions

23
modules/portal/data.json Normal file
View File

@ -0,0 +1,23 @@
[
{
"id": 1,
"title": "HoloLake 2.0 发布!",
"content": "全新界面,极致体验。",
"channel": "tech",
"pinned": true
},
{
"id": 2,
"title": "AI 会议延期通知",
"content": "原定下周的会议推迟到月底。",
"channel": "news",
"pinned": false
},
{
"id": 3,
"title": "VS Code 插件推荐",
"content": "Live Server 是开发神器。",
"channel": "tech",
"pinned": false
}
]

134
modules/portal/index.html Normal file
View File

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HoloLake公告栏</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="app-container">
<!-- 顶部标题栏 -->
<div class="header">
<h1>公告栏</h1>
<div class="header-actions">
<span class="pinned-badge">2条置顶</span>
<button class="subscribe-btn">订阅</button>
</div>
</div>
<!-- 频道标签栏 -->
<div class="channel-bar">
<span class="channel active">全部</span>
<span class="channel">系统公告</span>
<span class="channel">开发动态</span>
<span class="channel">团队消息</span>
</div>
<!-- 公告列表 -->
<div class="bulletin-list">
<!-- 置顶公告1 -->
<div class="bulletin-card pinned">
<div class="pin-indicator">置顶</div>
<div class="bulletin-icon system"></div>
<div class="bulletin-content">
<div class="bulletin-header">
<span class="bulletin-title">光湖系统MVP v0.4.0发布公告</span>
<span class="bulletin-time">2小时前</span>
</div>
<p class="bulletin-summary">主域公告栏与频道过渡系统M22正式进入环节0开发。新增公告置顶、频道分类、时间线展示功能信息展示层逐步完善。</p>
<div class="bulletin-footer">
<span class="bulletin-tag tag-system">系统公告</span>
<span class="bulletin-author">发布者:曜冥主控</span>
</div>
</div>
</div>
<!-- 置顶公告2 -->
<div class="bulletin-card pinned">
<div class="pin-indicator">置顶</div>
<div class="bulletin-icon dev"></div>
<div class="bulletin-content">
<div class="bulletin-header">
<span class="bulletin-title">SYSLOG提交流程v2上线通知</span>
<span class="bulletin-time">5小时前</span>
</div>
<p class="bulletin-summary">SYSLOG生成方式已更新为「知秋主动提问制」完成任务后由知秋引导填写无需手动套用模板。即日起生效。</p>
<div class="bulletin-footer">
<span class="bulletin-tag tag-dev">开发动态</span>
<span class="bulletin-author">发布者:知秋</span>
</div>
</div>
</div>
<!-- 普通公告1 -->
<div class="bulletin-card">
<div class="bulletin-icon team"></div>
<div class="bulletin-content">
<div class="bulletin-header">
<span class="bulletin-title">欢迎Awen加入光湖团队</span>
<span class="bulletin-time">1天前</span>
</div>
<p class="bulletin-summary">DEV-012 Awen正式加入光湖开发团队成为第12位协作者。首个模块M09消息通知中心已全通关</p>
</div>
</div>
<!-- 普通公告2 -->
<div class="bulletin-card">
<div class="bulletin-icon dev"></div>
<div class="bulletin-content">
<div class="bulletin-header">
<span class="bulletin-title">M08数据统计面板全通关</span>
<span class="bulletin-time">2天前</span>
</div>
<p class="bulletin-summary">完成M08全部环节七连胜达成响应式布局+SVG环形图+CSV导出一次通过正式前端毕业</p>
<div class="bulletin-footer">
<span class="bulletin-tag tag-dev">开发动态</span>
<span class="bulletin-author">发布者:霜砚</span>
</div>
</div>
</div>
<!-- 普通公告3 -->
<div class="bulletin-card">
<div class="bulletin-icon system"></div>
<div class="bulletin-content">
<div class="bulletin-header">
<span class="bulletin-title">HTTPS配置与飞书Webhook联调完成</span>
<span class="bulletin-time">3天前</span>
</div>
<p class="bulletin-summary">光湖主域已完成HTTPS配置与飞书Webhook全链路联调Nginx+PM2+Certbot部署完毕系统脊梁正式贯通。</p>
<div class="bulletin-footer">
<span class="bulletin-tag tag-system">系统公告</span>
<span class="bulletin-author">发布者:页页</span>
</div>
</div>
</div>
<!-- 普通公告4 -->
<div class="bulletin-card">
<div class="bulletin-icon team"></div>
<div class="bulletin-content">
<div class="bulletin-header">
<span class="bulletin-title">本周开发者进度总览</span>
<span class="bulletin-time">3天前</span>
</div>
<p class="bulletin-summary">12位开发者中8位活跃累计完成30+个环节。肥猫五连胜、桔子七连胜、小草莓五连胜、Awen四连胜团队势头强劲</p>
<div class="bulletin-footer">
<span class="bulletin-tag tag-team">团队消息</span>
<span class="bulletin-author">发布者:曜冥主控</span>
</div>
</div>
</div>
</div>
<!-- 底部状态栏 -->
<div class="footer">
<span>共6条公告2条置顶</span>
<span class="footer-brand">HoloLake光湖系统</span>
</div>
</div>
<script src="storage.js"></script>
<script src="script.js"></script>
</body>
</html>

48
modules/portal/index.js Normal file
View File

@ -0,0 +1,48 @@
// 频道标签过滤
document.querySelectorAll('.channel-tag').forEach(tag => {
tag.addEventListener('click', () => {
const channel = tag.dataset.channel;
document.querySelectorAll('.announcement').forEach(ann => {
ann.style.display = channel === 'all' || ann.dataset.channel === channel ? 'block' : 'none';
});
});
});
// 置顶悬浮动画
document.querySelectorAll('.pin-icon').forEach(pin => {
pin.addEventListener('click', () => {
const announcement = pin.closest('.announcement');
announcement.classList.toggle('pinned');
});
});
// ✨ 动态数据加载M24 核心!)
fetch('data.json')
.then(response => response.json())
.then(data => {
const announcements = document.querySelector('.announcements-container');
announcements.innerHTML = '';
data.forEach(item => {
const announcement = document.createElement('div');
announcement.className = nnouncement ;
announcement.dataset.channel = item.channel;
announcement.innerHTML =
<div class="announcement-content">
<h3></h3>
<p></p>
</div>
<i class="pin-icon"></i>
;
announcements.appendChild(announcement);
});
// 重新绑定事件
document.querySelectorAll('.pin-icon').forEach(pin => {
pin.addEventListener('click', () => {
const announcement = pin.closest('.announcement');
announcement.classList.toggle('pinned');
pin.textContent = announcement.classList.contains('pinned') ? '⭐' : '✨';
});
});
});

204
modules/portal/script.js Normal file
View File

@ -0,0 +1,204 @@
// ========== 确保 HoloLake 命名空间存在 ==========
window.HoloLake = window.HoloLake || {};
// ========== 页面加载完成后执行 ==========
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 script.js 初始化');
// 恢复所有状态
restoreState();
// 绑定事件
bindEvents();
// 更新布局信息
updateLayoutInfo();
// 监听窗口大小变化
window.addEventListener('resize', function() {
updateLayoutInfo();
});
});
// ========== 恢复状态 ==========
function restoreState() {
console.log('🔍 restoreState 开始...');
try {
// 1. 恢复订阅状态
const isSubscribed = HoloLake.UserState.isSubscribed();
const subscribeBtn = document.querySelector('.subscribe-btn');
if (subscribeBtn) {
if (isSubscribed) {
subscribeBtn.classList.add('active');
subscribeBtn.textContent = '已订阅';
} else {
subscribeBtn.classList.remove('active');
subscribeBtn.textContent = '订阅';
}
}
// 2. 恢复已读公告
const readList = HoloLake.UserState.getReadBulletins();
const bulletinCards = document.querySelectorAll('.bulletin-card');
bulletinCards.forEach((card, index) => {
const bulletinId = `bulletin-${index}`;
if (readList.includes(bulletinId)) {
card.classList.add('read');
}
});
// 3. 恢复选中频道
const activeChannel = HoloLake.UserState.getActiveChannel();
const channels = document.querySelectorAll('.channel');
channels.forEach(channel => {
const channelText = channel.textContent.trim();
if (channelText === activeChannel || (activeChannel === '全部' && channelText === '全部')) {
channel.classList.add('active');
} else {
channel.classList.remove('active');
}
});
// 4. 更新最后访问时间
HoloLake.UserState.updateLastVisit();
console.log('✅ 恢复已完成');
} catch (e) {
console.error('❌ 恢复失败:', e);
}
}
// ========== 绑定事件 ==========
function bindEvents() {
console.log('🔗 绑定事件...');
// 订阅按钮
const subscribeBtn = document.querySelector('.subscribe-btn');
if (subscribeBtn) {
subscribeBtn.addEventListener('click', function() {
const newState = HoloLake.UserState.toggleSubscribe();
if (newState) {
this.classList.add('active');
this.textContent = '已订阅';
} else {
this.classList.remove('active');
this.textContent = '订阅';
}
console.log('📌 订阅状态:', newState ? '已订阅' : '未订阅');
});
}
// 频道点击
const channels = document.querySelectorAll('.channel');
channels.forEach(channel => {
channel.addEventListener('click', function() {
// 更新UI
channels.forEach(c => c.classList.remove('active'));
this.classList.add('active');
// 保存状态
const channelName = this.textContent.trim();
HoloLake.UserState.setActiveChannel(channelName);
// 筛选公告(可选功能,这里先做简单筛选)
filterBulletinsByChannel(channelName);
console.log('📺 切换到频道:', channelName);
});
});
// 公告点击(标记已读)
const bulletinCards = document.querySelectorAll('.bulletin-card');
bulletinCards.forEach((card, index) => {
card.addEventListener('click', function() {
const bulletinId = `bulletin-${index}`;
// 标记已读
if (!this.classList.contains('read')) {
this.classList.add('read');
HoloLake.UserState.markAsRead(bulletinId);
console.log('📖 已读公告:', bulletinId);
}
});
});
}
// ========== 按频道筛选 ==========
function filterBulletinsByChannel(channelName) {
const cards = document.querySelectorAll('.bulletin-card');
if (channelName === '全部') {
cards.forEach(card => {
card.style.display = 'flex';
});
return;
}
cards.forEach(card => {
const tag = card.querySelector('.bulletin-tag');
if (tag && tag.textContent.trim() === channelName) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
});
}
// ========== 更新布局信息 ==========
function updateLayoutInfo() {
const width = window.innerWidth;
let mode = '桌面';
if (width <= 480) {
mode = '手机';
} else if (width <= 768) {
mode = '平板';
}
console.log(`📱 当前布局: ${mode}模式 (${width}px)`);
}
// ========== 环节3动画控制 ==========
// 初始化动画
function initAnimations() {
// 检测用户是否开启了「减少动画」
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
console.log('♿ 用户开启了减少动画,跳过动画');
return;
}
// 为所有公告卡片设置递增的动画延迟
const cards = document.querySelectorAll('.bulletin-card');
cards.forEach((card, index) => {
// 如果已经有内联样式,先保留原有样式,只加动画延迟
const delay = 0.1 * (index + 1);
card.style.animationDelay = `${delay}s`;
});
console.log('🎬 动画已初始化,延迟已设置');
}
// 订阅按钮脉冲效果
function addPulseEffect(btn) {
btn.classList.add('pulse');
setTimeout(() => {
btn.classList.remove('pulse');
}, 300);
}
// 在页面加载时调用
document.addEventListener('DOMContentLoaded', function() {
// 已有的初始化代码...
initAnimations();
// 为订阅按钮添加脉冲效果
const subscribeBtn = document.querySelector('.subscribe-btn');
if (subscribeBtn) {
subscribeBtn.addEventListener('click', function() {
addPulseEffect(this);
});
}
});

128
modules/portal/storage.js Normal file
View File

@ -0,0 +1,128 @@
/**
* HoloLake Bulletin 数据持久化模块
* storage.js - localStorage 封装 + 用户状态管理
* 模块 ID: BC-M22-002-AW
*/
window.HoloLake = window.HoloLake || {};
// ========== Storage 工具对象(带前缀) ==========
HoloLake.Storage = {
prefix: 'hololake_bulletin_',
// 保存数据
save: function(key, value) {
try {
const serialized = JSON.stringify(value);
localStorage.setItem(this.prefix + key, serialized);
console.log(`[Storage] 已保存: ${key} =`, value);
} catch (e) {
console.error('[Storage] 保存失败:', e);
}
},
// 读取数据
load: function(key, defaultValue = null) {
try {
const serialized = localStorage.getItem(this.prefix + key);
if (serialized === null) {
console.log(`[Storage] 无数据: ${key},返回默认值`);
return defaultValue;
}
const value = JSON.parse(serialized);
console.log(`[Storage] 已加载: ${key} =`, value);
return value;
} catch (e) {
console.error('[Storage] 读取失败:', e);
return defaultValue;
}
},
// 删除指定键
remove: function(key) {
try {
localStorage.removeItem(this.prefix + key);
console.log(`[Storage] 已删除: ${key}`);
} catch (e) {
console.error('[Storage] 删除失败:', e);
}
},
// 清空所有带前缀的数据
clear: function() {
try {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(this.prefix)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
console.log('[Storage] 已清空所有公告栏数据');
} catch (e) {
console.error('[Storage] 清空失败:', e);
}
}
};
// ========== 用户状态管理 ==========
HoloLake.UserState = {
// 已读公告列表
getReadBulletins: function() {
return HoloLake.Storage.load('read_bulletins', []);
},
markAsRead: function(bulletinId) {
const readList = this.getReadBulletins();
if (!readList.includes(bulletinId)) {
readList.push(bulletinId);
HoloLake.Storage.save('read_bulletins', readList);
}
return readList;
},
// 订阅状态
isSubscribed: function() {
return HoloLake.Storage.load('is_subscribed', false);
},
toggleSubscribe: function() {
const current = this.isSubscribed();
const newState = !current;
HoloLake.Storage.save('is_subscribed', newState);
return newState;
},
// 当前频道
getActiveChannel: function() {
return HoloLake.Storage.load('active_channel', 'all');
},
setActiveChannel: function(channelId) {
HoloLake.Storage.save('active_channel', channelId);
},
// 上次访问时间
getLastVisit: function() {
return HoloLake.Storage.load('last_visit', null);
},
updateLastVisit: function() {
const now = new Date().toISOString();
HoloLake.Storage.save('last_visit', now);
return now;
},
// 重置所有状态(测试用)
resetAll: function() {
HoloLake.Storage.remove('read_bulletins');
HoloLake.Storage.remove('is_subscribed');
HoloLake.Storage.remove('active_channel');
HoloLake.Storage.remove('last_visit');
console.log('[UserState] 已重置所有状态');
}
};
// 初始化时记录访问时间(可选,页面加载时由 script.js 调用)
console.log('✅ storage.js 已加载HoloLake.Storage 和 HoloLake.UserState 已就绪');

440
modules/portal/style.css Normal file
View File

@ -0,0 +1,440 @@
/* HoloLake公告栏系统深色科技风 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: #0a1628;
color: #e0e6ed;
min-height: 100vh;
}
.app-container {
max-width: 580px;
margin: 0 auto;
padding: 0 16px;
}
/* ========== 顶部标题栏 ========== */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 0 16px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.header h1 {
font-size: 22px;
font-weight: 700;
color: #e0e6ed;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.pinned-badge {
background: rgba(255, 183, 77, 0.15);
color: #ffb74d;
font-size: 12px;
padding: 4px 10px;
border-radius: 12px;
font-weight: 600;
}
.subscribe-btn {
background: rgba(79, 195, 247, 0.1);
color: #4fc3f7;
border: 1px solid rgba(79, 195, 247, 0.2);
padding: 6px 14px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
}
.subscribe-btn:hover {
background: rgba(79, 195, 247, 0.2);
}
/* ========== 频道栏 ========== */
.channel-bar {
display: flex;
gap: 8px;
padding: 16px 0;
border-bottom: 1px solid rgba(255,255,255,0.06);
overflow-x: auto;
}
.channel {
padding: 8px 18px;
border-radius: 20px;
border: 1px solid rgba(255,255,255,0.1);
background: transparent;
color: #8899aa;
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.channel:hover {
border-color: rgba(255, 183, 77, 0.3);
color: #b0bec5;
}
.channel.active {
background: rgba(255,183,77,0.15);
border-color: #ffb74d;
color: #ffb74d;
font-weight: 600;
}
/* ========== 公告列表 ========== */
.bulletin-list {
padding: 16px 0;
}
.bulletin-card {
position: relative;
display: flex;
gap: 16px;
padding: 16px;
border-radius: 12px;
background: rgba(20, 30, 45, 0.6);
border: 1px solid rgba(255,255,255,0.04);
margin-bottom: 12px;
transition: all 0.2s ease;
}
.bulletin-card.pinned {
border: 1px solid rgba(255, 183, 77, 0.3);
background: rgba(255, 183, 77, 0.02);
}
.pin-indicator {
position: absolute;
top: 8px;
right: 12px;
font-size: 11px;
color: #ffb74d;
font-weight: 600;
}
/* 公告图标 */
.bulletin-icon {
width: 42px;
height: 42px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
.bulletin-icon.system { background: rgba(156,39,176,0.12); }
.bulletin-icon.dev { background: rgba(76,175,80,0.12); }
.bulletin-icon.team { background: rgba(79,195,247,0.12); }
/* 公告内容 */
.bulletin-content {
flex: 1;
min-width: 0;
}
.bulletin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.bulletin-title {
font-size: 15px;
font-weight: 600;
color: #e0e6ed;
}
.bulletin-time {
font-size: 12px;
color: #556677;
flex-shrink: 0;
margin-left: 12px;
}
.bulletin-summary {
font-size: 13px;
color: #8899aa;
line-height: 1.6;
margin-bottom: 10px;
}
/* 公告底部 */
.bulletin-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.bulletin-tag {
display: inline-block;
font-size: 11px;
padding: 2px 8px;
border-radius: 6px;
font-weight: 500;
}
.tag-system { background: rgba(156,39,176,0.12); color: #ce93d8; }
.tag-dev { background: rgba(76,175,80,0.12); color: #81c784; }
.tag-team { background: rgba(79,195,247,0.12); color: #4fc3f7; }
.bulletin-author {
font-size: 11px;
color: #556677;
}
/* ========== 底部状态栏 ========== */
.footer {
display: flex;
justify-content: space-between;
padding: 20px 0;
border-top: 1px solid rgba(255,255,255,0.06);
font-size: 12px;
color: #556677;
}
.footer-brand {
color: rgba(255, 183, 77, 0.5);
}
/* ========== 环节2响应式布局爸爸的深色科技风 ========== */
/* 平板(宽度 768px 以下) */
@media (max-width: 768px) {
.app-container {
max-width: 100%;
padding: 0 12px;
}
.header h1 {
font-size: 18px;
}
.channel-bar {
gap: 6px;
padding: 12px 0;
}
.channel {
padding: 6px 14px;
font-size: 12px;
}
.bulletin-card {
padding: 12px 10px;
}
.channel-bar {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.channel-bar::-webkit-scrollbar {
display: none;
}
.bulletin-card {
flex-direction: column;
gap: 8px;
}
.bulletin-icon {
width: 32px;
height: 32px;
font-size: 14px;
}
.bulletin-header {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.bulletin-time {
margin-left: 0;
}
.pin-indicator {
top: 6px;
right: 8px;
font-size: 10px;
}
.footer {
flex-direction: column;
gap: 4px;
text-align: center;
}
}
/* 手机(宽度 480px 以下)- 复用平板样式,只调更小字体 */
@media (max-width: 480px) {
.header h1 {
font-size: 16px;
}
.subscribe-btn {
padding: 4px 10px;
font-size: 12px;
}
.channel {
padding: 4px 10px;
font-size: 11px;
}
.bulletin-title {
font-size: 14px;
}
.bulletin-summary {
font-size: 12px;
}
}
/* 已读公告样式 */
.bulletin-card.read {
opacity: 0.6;
}
.bulletin-card.read .bulletin-title {
color: #8899aa;
}
/* 订阅按钮激活状态 */
.subscribe-btn.active {
background: rgba(79, 195, 247, 0.25);
border-color: #4fc3f7;
color: #4fc3f7;
}
/* ========== 环节3过渡动画 ========== */
/* 公告卡片:悬停上移 + 阴影加深 */
.bulletin-card {
transition: all 0.3s ease;
}
.bulletin-card:hover {
transform: translateY(-4px); /* 从 -2px 改为 -4px更明显 */
box-shadow: 0 12px 24px rgba(0, 160, 255, 0.2); /* 蓝色光晕 */
}
/* 订阅按钮:缩放反馈 + 颜色过渡 */
.subscribe-btn {
transition: all 0.2s ease;
}
.subscribe-btn:hover {
transform: scale(1.05);
}
.subscribe-btn:active {
transform: scale(0.95);
}
/* 频道标签:背景色/文字色平滑切换 */
.channel {
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 已读公告:淡入淡出效果 */
.bulletin-card.read {
transition: opacity 0.5s ease;
}
/* ========== 环节3关键帧动画 ========== */
/* 淡入浮起动画 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 为每个公告卡片添加动画 */
.bulletin-card {
animation: fadeInUp 0.6s ease forwards;
opacity: 0; /* 动画开始前隐藏 */
}
/* 按索引依次延迟出现 */
.bulletin-card:nth-child(1) { animation-delay: 0.1s; }
.bulletin-card:nth-child(2) { animation-delay: 0.2s; }
.bulletin-card:nth-child(3) { animation-delay: 0.3s; }
.bulletin-card:nth-child(4) { animation-delay: 0.4s; }
.bulletin-card:nth-child(5) { animation-delay: 0.5s; }
.bulletin-card:nth-child(6) { animation-delay: 0.6s; }
/* 如果卡片超过6张继续往后加 */
/* ========== 环节3光湖水面效果 ========== */
/* Header 光湖呼吸渐变 */
.header {
background: linear-gradient(135deg, #0a2a4a 0%, #1b4a6b 50%, #0a2a4a 100%);
background-size: 200% 200%;
animation: breathe 8s ease-in-out infinite;
position: relative;
overflow: hidden;
}
/* 呼吸动画 */
@keyframes breathe {
0% { background-position: 0% 0%; }
50% { background-position: 100% 100%; }
100% { background-position: 0% 0%; }
}
/* 微光扫过效果 */
.header::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent
);
transform: translateX(-100%);
animation: shimmer 6s infinite;
pointer-events: none;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
20% { transform: translateX(100%); }
100% { transform: translateX(100%); }
}
/* 频道栏底部边缘(水面倒影) */
.channel-bar {
border-bottom: 1px solid rgba(79, 195, 247, 0.2);
position: relative;
}
.channel-bar::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #4fc3f7, transparent);
opacity: 0.3;
}
/* 订阅按钮脉冲效果 */
.subscribe-btn.pulse {
animation: pulse 0.3s ease;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(79, 195, 247, 0.7); }
70% { transform: scale(1.1); box-shadow: 0 0 10px 5px rgba(79, 195, 247, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(79, 195, 247, 0); }
}