windows/components/NewsHub.vue

876 lines
24 KiB
Vue
Raw Permalink Normal View History

2025-09-25 17:20:06 +00:00
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import StreamerFlipCard from './StreamerFlipCard.vue';
const { t } = useI18n();
// Mock data for live streams
const featuredStreams = computed(() => [
{
id: 1,
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyLzNlYTNkMzI0ZjJhMmZhNDQ5MDAyZWQyNDE0ZDNhZDhlLnBuZw==",
externalUrl: "https://twitch.tv/gamingking"
},
{
id: 2,
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyLzE5OGI4OGZhZmQ3ZDM2NjEyZmE0YTBmZDQ0NzViMmVjLnBuZw==",
externalUrl: "https://youtube.com/watch?v=music123"
},
{
id: 3,
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyLzVjMmM1NzRhNTY0ZDNhMmU4ODM5OTlhZTQ3NDk1NTQ5LnBuZw==",
externalUrl: "https://twitch.tv/cookingchef"
},
{
id: 4,
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyL2JjNGI4NmJlNzM5OWJlNGJmNTY2MTk0YjZmOWZkZDYwLnBuZw==",
externalUrl: "https://youtube.com/watch?v=coding123"
}
]);
const popularStreamers = computed(() => [
{
id: 1,
name: "音樂小天使",
photo: "https://playone-assets.goplayone.com/playone/user/play/avatar/ea249b64-a3d5-4ff1-bdc5-2eb0d0f956ea",
description: "音樂達人♫",
rank: "黃金",
fans: 1560,
orders: 112,
badges: ["rank-gold", "pro-music", "feature-verified"],
gender: "female" as const,
birthday: "1998-03-15",
greeting: "你好~我是音樂小天使~音樂達人♫",
status: "專業音樂陪陪!",
availability: "隨時可約 聲音甜美",
personality: "溫柔體貼 歌聲動人",
promise: "用音樂治癒你的心靈~快來聽我唱歌吧💕"
},
{
id: 2,
name: "電競女武神",
photo: "https://imgproxy.goplayone.com/1/auto/244/244/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci83NDk5ODA2OS04YTU4LTQ5YWQtOTc1YS1jNTczZmY0YjNjNTY=",
description: "FPS女王",
rank: "大師",
fans: 2800,
orders: 198,
badges: ["rank-master", "pro-gaming", "feature-live"],
gender: "female" as const,
birthday: "1995-07-22",
greeting: "Yo~我是電競女武神~FPS女王🔫",
status: "前職業選手 現役陪陪!",
availability: "晚上8-12點 週末全天",
personality: "冷靜狙擊 一槍一個",
promise: "帶你體驗職業級操作!從菜鳥到高手 包教包會🎯"
},
{
id: 3,
name: "二次元萌妹",
photo: "https://playone-assets.goplayone.com/playone/user/play/avatar/a4b858a7-a1f3-4543-8d18-ae94e8db573b",
description: "動漫專家",
rank: "黃金",
fans: 1200,
orders: 89,
badges: ["rank-gold", "special-anime", "feature-new"],
gender: "female" as const,
birthday: "2000-11-08",
greeting: "こんにちは~我是二次元萌妹~動漫專家🌸",
status: "動漫系大學生 兼職陪陪!",
availability: "平日晚上 週末下午",
personality: "超愛動漫 聲音超萌",
promise: "一起討論最新番劇!陪你刷副本 收集老婆💖"
},
{
id: 4,
name: "策略大師",
photo: "https://imgproxy.goplayone.com/1/auto/244/244/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci8zZjMzMTZlYS04YzRkLTQ0Y2UtOTc5Mi0zMTk2ZmUxZmJhOGU=",
description: "戰術專家",
rank: "鑽石",
fans: 4200,
orders: 312,
badges: ["rank-diamond", "achievement-expert", "feature-vip"],
gender: "male" as const,
birthday: "1992-05-18",
greeting: "你好~我是策略大師~戰術專家🧠",
status: "前職業教練 現專職陪陪!",
availability: "週一到週五 下午2-8點",
personality: "理性分析 耐心指導",
promise: "從戰術思維到操作細節 全面提升你的遊戲智商!📊"
},
{
id: 5,
name: "派對女王",
photo: "https://imgproxy.goplayone.com/1/auto/244/244/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci80YmUzOWUxZi04YzJhLTRiMmItYjk0NS1lYjAyMWEzNjc5ZGM=",
description: "社交達人",
rank: "鑽石",
fans: 2100,
orders: 167,
badges: ["rank-diamond", "special-party", "feature-popular"],
gender: "female" as const,
birthday: "1996-09-12",
greeting: "Hey~我是派對女王~社交達人🎉",
status: "全職陪陪 專攻社交遊戲!",
availability: "24小時待命 隨時開趴",
personality: "超會帶氣氛 人緣超好",
promise: "讓你的遊戲時光充滿歡笑!組隊開黑 一起嗨翻天🎊"
},
{
id: 6,
name: "生存專家",
photo: "https://imgproxy.goplayone.com/1/auto/420/420/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci9hYTFhNzdjMy05YTI2LTRmNTctOTlkMC02NTA2M2IwMDgyYTY=",
description: "荒野求生",
rank: "大師",
fans: 1800,
orders: 134,
badges: ["rank-master", "special-survival", "achievement-mentor"],
gender: "male" as const,
birthday: "1994-12-03",
greeting: "Hello~我是生存專家~荒野求生🏕️",
status: "建築系學生 兼職陪陪!",
availability: "晚上7點後 週末全天",
personality: "創意無限 耐心建造",
promise: "帶你建造夢想家園!從零開始 打造專屬世界🏗️"
}
]);
// Mock data for recommended services
const recommendedServices = computed(() => [
{
id: 1,
name: "1v1 聊天",
nameEn: "1v1 Chat",
image: "https://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvYzQ4YzUxYjE1YmUyZTFjMDcyOTk1ZGJhZGE0MmExY2EucG5n",
description: "一對一聊天服務",
category: "chat"
},
{
id: 2,
name: "唱歌",
nameEn: "Singing",
image: "http://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvYzY1ZTJhMjAzYzRlY2U2NzhkNjkxNGE3YTBhMmQ0ODMucG5n",
description: "音樂歌唱服務",
category: "music"
},
{
id: 3,
name: "STEAM",
nameEn: "STEAM",
image: "https://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvM2RmOGQ2NzdiYzZmMGIzMzIzZmQ1MGNhYWFhZDUyMjUucG5n",
description: "Steam 遊戲平台",
category: "gaming"
},
{
id: 4,
name: "英雄聯盟",
nameEn: "League of Legends",
image: "https://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvMTljZmUxYWYwMGYyN2M4YWY1ZTYzOGFkNjM0ZDNkMDYucG5n",
description: "英雄聯盟遊戲",
category: "gaming"
},
{
id: 5,
name: "王者榮耀",
nameEn: "Honor of Kings",
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImtpbmdHcmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+CjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRkQ3MDAiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjRkZBNTAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxMjAiIGZpbGw9InVybCgja2luZ0dyYWRpZW50KSIvPgo8dGV4dCB4PSIxMDAiIHk9IjYwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTgiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+546L5a2Q5rW35rKzPC90ZXh0Pgo8c3ZnIHg9IjgwIiB5PSIzMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSIjRkZGRkZGIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xMCAxMCBMMzAgMzAgTTMwIDEwIEwxMCAzMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cjwvc3ZnPgo=",
description: "王者榮耀手遊",
category: "gaming"
},
{
id: 6,
name: "原神",
nameEn: "Genshin Impact",
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImdlbnNoaW5HcmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+CjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM2NkMzRkYiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMDA2NkZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxMjAiIGZpbGw9InVybCgjZ2Vuc2hpbkdyYWRpZW50KSIvPgo8dGV4dCB4PSIxMDAiIHk9IjYwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+5Y6f5YibPC90ZXh0Pgo8c3ZnIHg9IjgwIiB5PSIzMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSIjRkZGRkZGIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xMCAxMCBMMzAgMzAgTTMwIDEwIEwxMCAzMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cjwvc3ZnPgo=",
description: "原神開放世界遊戲",
category: "gaming"
},
{
id: 7,
name: "直播",
nameEn: "Live Streaming",
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImxpdmVHcmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+CjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRjAwMDAiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjQ0MwMDAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxMjAiIGZpbGw9InVybCgjbGl2ZUdyYWRpZW50KSIvPgo8dGV4dCB4PSIxMDAiIHk9IjYwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+55m75b2VPC90ZXh0Pgo8c3ZnIHg9IjgwIiB5PSIzMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSIjRkZGRkZGIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xMCAxMCBMMzAgMzAgTTMwIDEwIEwxMCAzMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cjwvc3ZnPgo=",
description: "直播服務",
category: "streaming"
},
{
id: 8,
name: "陪玩",
nameEn: "Gaming Companion",
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImNvbXBhbm9uR3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgo8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjRkY2NkNDIi8+CjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0ZGMDA5OSIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTIwIiBmaWxsPSJ1cmwoI2NvbXBhbm9uR3JhZGllbnQpIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNjAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj7mh6Dmh6A8L3RleHQ+CjxzdmcgeD0iODAiIHk9IjMwIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIGZpbGw9IiNGRkZGRkYiPgo8Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIxOCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTEwIDEwIEwzMCAzMCBNMzAgMTAgTDEwIDMwIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4KPC9zdmc+Cg==",
description: "遊戲陪玩服務",
category: "companion"
}
]);
// Services drag state
const isDragging = ref(false);
const startX = ref(0);
const scrollLeft = ref(0);
const servicesContainer = ref<HTMLElement | null>(null);
// Carousel state
const currentSlide = ref(0);
const carouselInterval = ref<number | null>(null);
// Auto-play carousel
const startCarousel = () => {
carouselInterval.value = window.setInterval(() => {
currentSlide.value = (currentSlide.value + 1) % featuredStreams.value.length;
}, 4000);
};
const stopCarousel = () => {
if (carouselInterval.value) {
clearInterval(carouselInterval.value);
carouselInterval.value = null;
}
};
// Handle stream click
const handleStreamClick = (stream: any) => {
if (stream.externalUrl) {
window.open(stream.externalUrl, '_blank', 'noopener,noreferrer');
}
};
// Handle streamer click
const handleStreamerClick = (streamer: any) => {
console.log(`查看主播: ${streamer.name}`);
// TODO: Open streamer profile
};
// Handle more button click
const handleMoreClick = (streamer: any) => {
console.log(`查看更多: ${streamer.name}`);
// TODO: Open detailed streamer page
};
// Handle profile button click
const handleProfileClick = (streamer: any) => {
console.log(`前往主播主頁: ${streamer.name}`);
// TODO: Navigate to streamer profile page
};
// Handle service click
const handleServiceClick = (service: any) => {
console.log(`點擊服務: ${service.name}`);
// TODO: Navigate to service page or open service modal
};
// Handle service drag functionality
const handleMouseDown = (e: MouseEvent) => {
if (!servicesContainer.value) return;
isDragging.value = true;
startX.value = e.pageX - servicesContainer.value.offsetLeft;
scrollLeft.value = servicesContainer.value.scrollLeft;
// Prevent text selection while dragging
e.preventDefault();
};
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging.value || !servicesContainer.value) return;
e.preventDefault();
const x = e.pageX - servicesContainer.value.offsetLeft;
const walk = (x - startX.value) * 2; // Multiply for faster scrolling
servicesContainer.value.scrollLeft = scrollLeft.value - walk;
};
const handleMouseUp = () => {
isDragging.value = false;
};
const handleMouseLeave = () => {
isDragging.value = false;
};
// Handle more services button click
const handleMoreServicesClick = () => {
console.log('點擊更多服務');
// TODO: Navigate to services page
};
onMounted(() => {
startCarousel();
});
onUnmounted(() => {
stopCarousel();
});
</script>
<template>
<div class="news-hub">
<!-- Header -->
<div class="hub-header">
<h2 class="hub-title">📰 {{ t('livestream.title') }}</h2>
<div class="hub-subtitle">{{ t('livestream.subtitle') }}</div>
</div>
<!-- Home View -->
<div class="home-view">
<!-- Featured Streams Carousel -->
<div class="carousel-section">
<h3 class="section-title">🔥 {{ t('livestream.featuredStreams') }}</h3>
<div class="carousel-container" @mouseenter="stopCarousel" @mouseleave="startCarousel">
<div class="carousel-track" :style="{ transform: `translateX(-${currentSlide * 100}%)` }">
<div
v-for="stream in featuredStreams"
:key="stream.id"
class="carousel-slide"
@click="handleStreamClick(stream)"
>
<div class="stream-image-card">
<img :src="stream.image" :alt="`Stream ${stream.id}`" />
</div>
</div>
</div>
<!-- Carousel Controls -->
<button
class="carousel-btn prev"
@click="currentSlide = currentSlide > 0 ? currentSlide - 1 : featuredStreams.length - 1"
>
</button>
<button
class="carousel-btn next"
@click="currentSlide = (currentSlide + 1) % featuredStreams.length"
>
</button>
<!-- Carousel Indicators -->
<div class="carousel-indicators">
<button
v-for="(stream, index) in featuredStreams"
:key="index"
class="indicator"
:class="{ active: index === currentSlide }"
@click="currentSlide = index"
></button>
</div>
</div>
</div>
<!-- Recommended Services -->
<div class="services-section">
<div class="services-header">
<h3 class="section-title">🎯 {{ t('livestream.recommendedServices') }}</h3>
<button class="more-services-btn" @click="handleMoreServicesClick">
更多
</button>
</div>
<div
ref="servicesContainer"
class="services-scroll-container"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
:class="{ 'dragging': isDragging }"
>
<div class="services-grid">
<div
v-for="service in recommendedServices"
:key="service.id"
class="service-card"
@click="handleServiceClick(service)"
>
<div class="service-image">
<img :src="service.image" :alt="service.name" />
</div>
<div class="service-label">{{ service.name }}</div>
</div>
</div>
</div>
</div>
<!-- Popular Streamers -->
<div class="streamers-section">
<h3 class="section-title"> {{ t('livestream.popularStreamers') }}</h3>
<div class="streamers-grid">
<StreamerFlipCard
v-for="streamer in popularStreamers"
:key="streamer.id"
:streamer="streamer"
@click="handleStreamerClick"
@more="handleMoreClick"
@profile="handleProfileClick"
/>
</div>
</div>
</div> <!-- End home-view -->
</div>
</template>
<style scoped>
.news-hub {
width: 100%;
height: 100%;
background: var(--window-background);
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
}
.hub-header {
text-align: center;
margin-bottom: 24px;
}
.hub-title {
font-size: 24px;
font-weight: bold;
color: var(--content-text-color);
margin: 0 0 8px 0;
}
.hub-subtitle {
font-size: 14px;
color: var(--content-text-color);
opacity: 0.7;
}
.carousel-section {
margin-bottom: 32px;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: var(--content-text-color);
margin: 0 0 16px 0;
}
.carousel-container {
position: relative;
width: 100%;
aspect-ratio: 16/9;
overflow: hidden;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.carousel-track {
display: flex;
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out;
}
.carousel-slide {
min-width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.stream-image-card {
width: 100%;
height: 100%;
position: relative;
cursor: pointer;
transition: transform 0.3s ease;
overflow: hidden;
}
.stream-image-card img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
transition: transform 0.3s ease;
}
.stream-image-card:hover img {
transform: scale(1.05);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.carousel-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-size: 24px;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
z-index: 10;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
}
.carousel-btn:hover {
background: rgba(0, 0, 0, 0.7);
transform: translateY(-50%) scale(1.1);
}
.carousel-btn.prev {
left: 16px;
}
.carousel-btn.next {
right: 16px;
}
.carousel-indicators {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
z-index: 10;
}
.indicator {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
aspect-ratio: 1;
min-width: 12px;
min-height: 12px;
}
.indicator.active {
background: white;
border-color: white;
transform: scale(1.2);
}
.services-section {
margin-bottom: 32px;
}
.services-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.more-services-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.more-services-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.services-scroll-container {
overflow-x: auto;
overflow-y: hidden;
cursor: grab;
border-radius: 12px;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.services-scroll-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.services-scroll-container.dragging {
cursor: grabbing;
user-select: none;
}
.services-grid {
display: flex;
gap: 24px;
padding: 0 12px;
min-width: max-content;
}
.service-card {
background: white;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.05);
width: 200px;
flex-shrink: 0;
}
.service-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.service-image {
width: 100%;
height: 120px;
overflow: hidden;
position: relative;
}
.service-image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.service-label {
padding: 12px 16px;
text-align: center;
font-size: 14px;
font-weight: 600;
color: #333333;
background: white;
}
.streamers-section {
flex: 1;
}
.streamers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
align-items: start;
}
/* 響應式網格佈局 */
/* 響應式設計 */
@media (max-width: 1200px) {
.services-grid {
gap: 20px;
padding: 0 16px;
}
.service-card {
width: 250px;
}
.streamers-grid {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.carousel-container {
aspect-ratio: 16/9;
}
.carousel-btn {
width: 40px;
height: 40px;
font-size: 20px;
}
.carousel-btn.prev {
left: 12px;
}
.carousel-btn.next {
right: 12px;
}
}
@media (max-width: 768px) {
.news-hub {
padding: 16px;
}
.hub-title {
font-size: 24px;
}
.hub-subtitle {
font-size: 14px;
}
.services-grid {
gap: 16px;
padding: 0 12px;
}
.service-card {
width: 180px;
}
.service-image {
height: 100px;
}
.service-label {
padding: 10px 12px;
font-size: 13px;
}
.more-services-btn {
padding: 6px 12px;
font-size: 12px;
}
.streamers-grid {
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.carousel-container {
aspect-ratio: 16/9;
border-radius: 8px;
}
.carousel-btn {
width: 36px;
height: 36px;
font-size: 18px;
}
.carousel-btn.prev {
left: 8px;
}
.carousel-btn.next {
right: 8px;
}
.carousel-indicators {
bottom: 12px;
gap: 6px;
}
.indicator {
width: 10px;
height: 10px;
}
}
@media (max-width: 480px) {
.news-hub {
padding: 12px;
}
.hub-title {
font-size: 20px;
margin-bottom: 8px;
}
.hub-subtitle {
font-size: 13px;
margin-bottom: 20px;
}
.services-grid {
gap: 12px;
padding: 0 8px;
}
.service-card {
width: 150px;
}
.service-image {
height: 80px;
}
.service-label {
padding: 8px 10px;
font-size: 12px;
}
.more-services-btn {
padding: 4px 8px;
font-size: 11px;
}
.streamers-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.carousel-container {
aspect-ratio: 16/9;
border-radius: 6px;
}
.carousel-btn {
width: 32px;
height: 32px;
font-size: 16px;
}
.carousel-btn.prev {
left: 6px;
}
.carousel-btn.next {
right: 6px;
}
.carousel-indicators {
bottom: 8px;
gap: 4px;
}
.indicator {
width: 8px;
height: 8px;
}
}
/* Light theme adjustments */
.theme-light .carousel-btn {
background: rgba(255, 255, 255, 0.8);
color: #333;
}
.theme-light .carousel-btn:hover {
background: rgba(255, 255, 255, 0.9);
}
.theme-light .indicator {
border-color: rgba(0, 0, 0, 0.6);
background: rgba(0, 0, 0, 0.3);
}
.theme-light .indicator.active {
background: #333;
border-color: #333;
}
</style>