windows/components/NewsHub.vue

876 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>