882 lines
20 KiB
Vue
882 lines
20 KiB
Vue
<template>
|
||
<div class="home-plaza" id="home-plaza-section">
|
||
<!-- Header -->
|
||
<div class="plaza-header">
|
||
<div class="plaza-header-left">
|
||
<h2 class="plaza-title">首页广场</h2>
|
||
<p class="plaza-subtitle">精选 AI 应用与最新玩法,每次来都有新发现</p>
|
||
</div>
|
||
<div class="plaza-header-right">
|
||
<button
|
||
class="plaza-btn primary"
|
||
type="button"
|
||
@click="handleProtectedAction"
|
||
>
|
||
我要上架
|
||
</button>
|
||
<button
|
||
class="plaza-btn outline"
|
||
type="button"
|
||
@click="openBenefit"
|
||
>
|
||
领取福利
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Magazine Grid -->
|
||
<div class="plaza-grid">
|
||
<!-- Left: Feature Card (Magazine Cover Style) -->
|
||
<div
|
||
class="plaza-feature"
|
||
:data-transition-slug="featureArticle?.slug"
|
||
:data-hero-title="featureArticle?.slug"
|
||
:ref="(el) => registerCardRef(featureArticle?.slug || '', el)"
|
||
@click="handleArticleClick(featureArticle)"
|
||
:style="{
|
||
backgroundImage: `url(${featureArticle?.cover || '/cover.jpg'})`
|
||
}"
|
||
>
|
||
<div class="feature-overlay"></div>
|
||
<div class="feature-content">
|
||
<div class="feature-badge">本周主推</div>
|
||
<h3 class="feature-title" :data-hero-title="featureArticle?.slug">
|
||
{{ featureArticle?.title || "探索 AI 的无限可能" }}
|
||
</h3>
|
||
<p class="feature-desc">
|
||
{{
|
||
featureArticle?.description ||
|
||
"深入了解最新的 AI 技术趋势和应用场景,发现更多精彩内容..."
|
||
}}
|
||
</p>
|
||
|
||
<div class="feature-footer">
|
||
<div class="feature-author">
|
||
<img
|
||
:src="featureArticle?.author?.image || '/avatar-placeholder.png'"
|
||
alt="Author"
|
||
class="author-avatar"
|
||
/>
|
||
<div class="author-info">
|
||
<span class="author-name">{{ featureArticle?.author?.username || "官方编辑" }}</span>
|
||
<span class="publish-date">{{ formatDateFull(featureArticle?.createdAt) }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="feature-actions">
|
||
<button
|
||
class="plaza-like-btn"
|
||
:class="{ active: featureArticle?.favorited }"
|
||
@click.stop="toggleLike(featureArticle, $event)"
|
||
:title="featureArticle?.favorited ? '取消喜欢' : '喜欢'"
|
||
>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M12 21s-6.7-4.5-9.4-7.2A6 6 0 1 1 12 6a6 6 0 1 1 9.4 7.8C18.7 16.5 12 21 12 21Z" />
|
||
</svg>
|
||
点赞 {{ formatCount(featureArticle?.favoritesCount || featureArticle?.likes) }}
|
||
</button>
|
||
<button class="feature-cta">立即体验</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: List (Clean Info Stream) -->
|
||
<div class="plaza-list">
|
||
<div
|
||
v-for="(article, index) in listArticles"
|
||
:key="article.slug"
|
||
class="plaza-item-card"
|
||
:data-transition-slug="article.slug"
|
||
:data-hero-title="article.slug"
|
||
:ref="(el) => registerCardRef(article.slug, el)"
|
||
@click="handleArticleClick(article)"
|
||
:style="{ '--delay': `${index * 0.1}s` }"
|
||
>
|
||
<!-- Visual Anchor Bar -->
|
||
<div class="item-anchor-bar" :style="{ background: getAnchorColor(index) }"></div>
|
||
|
||
<div class="item-content">
|
||
<h4 class="item-title" :data-hero-title="article.slug">
|
||
{{ article.title }}
|
||
</h4>
|
||
<p class="item-desc">{{ article.description }}</p>
|
||
<div class="item-meta">
|
||
<span class="meta-tag" v-if="article.tagList?.[0]">{{ article.tagList[0] }}</span>
|
||
<span class="meta-divider">·</span>
|
||
<span>{{ formatCount(article.views) }} 浏览</span>
|
||
<span class="meta-divider">·</span>
|
||
|
||
<!-- Like Button for List Item -->
|
||
<button
|
||
class="item-like-btn"
|
||
:class="{ active: article.favorited }"
|
||
@click.stop="toggleLike(article, $event)"
|
||
:title="article.favorited ? '取消喜欢' : '喜欢'"
|
||
>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M12 21s-6.7-4.5-9.4-7.2A6 6 0 1 1 12 6a6 6 0 1 1 9.4 7.8C18.7 16.5 12 21 12 21Z" />
|
||
</svg>
|
||
点赞 {{ formatCount(article.favoritesCount || article.likes) }}
|
||
</button>
|
||
|
||
<span class="meta-divider">·</span>
|
||
<span>{{ formatDateFull(article.createdAt) }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="item-thumbnail"
|
||
v-if="article.cover"
|
||
:style="{ backgroundImage: `url(${article.cover})` }"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Login Modal -->
|
||
<div v-if="showLoginModal" class="plaza-login-mask" @click.self="closeLoginModal">
|
||
<div class="plaza-login-modal">
|
||
<div class="plaza-login-title">请先登录后再操作</div>
|
||
<div class="plaza-login-actions">
|
||
<button
|
||
type="button"
|
||
class="modal-btn ghost"
|
||
@click="closeLoginModal"
|
||
>
|
||
取消
|
||
</button>
|
||
<button type="button" class="modal-btn primary" @click="goLogin">
|
||
前往登录
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {
|
||
computed,
|
||
nextTick,
|
||
onBeforeUnmount,
|
||
onMounted,
|
||
ref,
|
||
watch,
|
||
type ComponentPublicInstance,
|
||
} from "vue";
|
||
import { navigateTo } from "#app";
|
||
import { useApi } from "@/composables/useApi";
|
||
import { useArticleNavContext } from "@/composables/useArticleNavContext";
|
||
import { useSharedTransition } from "@/composables/useSharedTransition";
|
||
|
||
interface Article {
|
||
slug: string;
|
||
title: string;
|
||
description?: string | null;
|
||
cover?: string | null;
|
||
tagList?: string[];
|
||
createdAt?: string;
|
||
views?: number;
|
||
likes?: number;
|
||
favoritesCount?: number;
|
||
favorites_count?: number;
|
||
favorited?: boolean;
|
||
author?: {
|
||
username?: string;
|
||
image?: string | null;
|
||
};
|
||
}
|
||
|
||
const props = defineProps<{
|
||
articles: Article[];
|
||
isLoggedIn?: boolean;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'like-changed', payload: { slug: string; favorited: boolean; favoritesCount: number }): void
|
||
}>();
|
||
|
||
const localArticles = ref<Article[]>([]);
|
||
|
||
watch(
|
||
() => props.articles,
|
||
(val) => {
|
||
const arr = Array.isArray(val) ? val : [];
|
||
localArticles.value = arr.map((article) => ({
|
||
...article,
|
||
tagList: Array.isArray(article.tagList) ? article.tagList : [],
|
||
}));
|
||
},
|
||
{ immediate: true, deep: true },
|
||
);
|
||
|
||
const featureArticle = computed(() => localArticles.value[0]);
|
||
const listArticles = computed(() => localArticles.value.slice(1, 6)); // 右侧列表最多 5 条,不补齐,避免重复展示同一篇
|
||
|
||
const formatCount = (n?: number | null) =>
|
||
Number.isFinite(n as number) ? Number(n).toLocaleString("zh-CN") : "0";
|
||
|
||
const formatDateFull = (dateStr?: string) => {
|
||
if (!dateStr) return "";
|
||
const d = new Date(dateStr);
|
||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||
};
|
||
|
||
const getAnchorColor = (index: number) => {
|
||
const colors = [
|
||
'#ec4899', // Pink
|
||
'#8b5cf6', // Purple
|
||
'#3b82f6', // Blue
|
||
'#06b6d4', // Cyan
|
||
'#10b981' // Emerald
|
||
];
|
||
return colors[index % colors.length];
|
||
};
|
||
|
||
const showLoginModal = ref(false);
|
||
const navCtx = useArticleNavContext();
|
||
const transition = useSharedTransition();
|
||
const cardRefs = ref<Partial<Record<string, HTMLElement>>>({});
|
||
const api = useApi();
|
||
|
||
const isAuthed = computed(() => Boolean(props.isLoggedIn));
|
||
|
||
function toHTMLElement(
|
||
el: Element | ComponentPublicInstance | null
|
||
): HTMLElement | null {
|
||
if (!el) return null;
|
||
if (el instanceof HTMLElement) return el;
|
||
const maybeEl = (el as any)?.$el;
|
||
return maybeEl instanceof HTMLElement ? maybeEl : null;
|
||
}
|
||
|
||
function registerCardRef(
|
||
slug: string,
|
||
el: Element | ComponentPublicInstance | null
|
||
) {
|
||
if (!slug) return;
|
||
const htmlEl = toHTMLElement(el);
|
||
if (htmlEl) {
|
||
cardRefs.value[slug] = htmlEl;
|
||
markCardPosition(slug, htmlEl);
|
||
} else {
|
||
delete cardRefs.value[slug];
|
||
}
|
||
}
|
||
|
||
function markCardPosition(slug: string, el?: HTMLElement) {
|
||
const targetEl = el || cardRefs.value[slug];
|
||
if (!slug || !targetEl) return;
|
||
transition.markSource(slug, targetEl);
|
||
}
|
||
|
||
function refreshCardPositions() {
|
||
Object.entries(cardRefs.value).forEach(([slug, el]) => {
|
||
if (el) markCardPosition(slug, el);
|
||
});
|
||
transition.cleanupOldPositions();
|
||
}
|
||
|
||
onMounted(() => {
|
||
nextTick(refreshCardPositions);
|
||
window.addEventListener("resize", refreshCardPositions);
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
window.removeEventListener("resize", refreshCardPositions);
|
||
refreshCardPositions();
|
||
});
|
||
|
||
watch(
|
||
() => props.articles,
|
||
() => nextTick(refreshCardPositions),
|
||
{ deep: true }
|
||
);
|
||
|
||
function handleProtectedAction() {
|
||
if (!isAuthed.value) {
|
||
showLoginModal.value = true;
|
||
return;
|
||
}
|
||
navigateTo("/articles/new");
|
||
}
|
||
|
||
function closeLoginModal() {
|
||
showLoginModal.value = false;
|
||
}
|
||
|
||
function goLogin() {
|
||
showLoginModal.value = false;
|
||
navigateTo("/login");
|
||
}
|
||
|
||
function openBenefit() {
|
||
window.open('https://api.wgetai.com/', '_blank');
|
||
}
|
||
|
||
function handleArticleClick(article?: Article) {
|
||
if (!article?.slug) return;
|
||
navCtx.setCurrent(article.slug);
|
||
markCardPosition(article.slug);
|
||
navigateTo(`/articles/${article.slug}`);
|
||
}
|
||
|
||
async function toggleLike(article: Article, event?: Event) {
|
||
if (event) event.stopPropagation();
|
||
if (!article?.slug) return;
|
||
|
||
if (!isAuthed.value) {
|
||
showLoginModal.value = true;
|
||
return;
|
||
}
|
||
|
||
const isFavorited = Boolean(article.favorited);
|
||
article.favorited = !isFavorited;
|
||
|
||
// Update count (handle both likes and favoritesCount for compatibility)
|
||
const count = article.favoritesCount || article.likes || 0;
|
||
const newCount = count + (isFavorited ? -1 : 1);
|
||
const applyState = (favorited: boolean, c: number) => {
|
||
localArticles.value.forEach((item) => {
|
||
if (item.slug === article.slug) {
|
||
item.favorited = favorited;
|
||
item.favoritesCount = c;
|
||
item.likes = c;
|
||
}
|
||
});
|
||
emit('like-changed', { slug: article.slug, favorited, favoritesCount: c });
|
||
};
|
||
applyState(!isFavorited, newCount);
|
||
|
||
try {
|
||
const method = isFavorited ? 'DELETE' : 'POST';
|
||
if (method === 'POST') {
|
||
await api.post(`/articles/${article.slug}/favorite`);
|
||
} else {
|
||
await api.del(`/articles/${article.slug}/favorite`);
|
||
}
|
||
} catch (e) {
|
||
// Revert
|
||
applyState(isFavorited, count);
|
||
console.error('[HomePlaza] toggle like failed', e);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.home-plaza {
|
||
width: 100%;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 32px;
|
||
background: #fff; /* Changed to pure white */
|
||
border-radius: var(--card-radius-lg);
|
||
box-shadow: 0 4px 24px rgba(0,0,0,0.02);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* Header */
|
||
.plaza-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-end;
|
||
padding-bottom: 24px;
|
||
border-bottom: 1px solid var(--color-border-light);
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.plaza-title {
|
||
font-size: 28px;
|
||
font-weight: 800;
|
||
color: var(--color-text-main);
|
||
margin: 0 0 6px 0;
|
||
letter-spacing: -0.5px;
|
||
}
|
||
|
||
.plaza-subtitle {
|
||
font-size: 14px;
|
||
color: var(--color-text-sub);
|
||
margin: 0;
|
||
}
|
||
|
||
.plaza-header-right {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.plaza-btn {
|
||
padding: 8px 20px;
|
||
border-radius: var(--btn-radius);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.plaza-btn.primary {
|
||
background: var(--grad-primary-btn);
|
||
color: #fff;
|
||
border: none;
|
||
box-shadow: var(--shadow-btn);
|
||
}
|
||
|
||
.plaza-btn.primary:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);
|
||
}
|
||
|
||
.plaza-btn.outline {
|
||
background: #fff;
|
||
border: 1px solid #e2e8f0;
|
||
color: var(--color-text-main);
|
||
}
|
||
|
||
.plaza-btn.outline:hover {
|
||
border-color: var(--color-primary-end);
|
||
color: var(--color-primary-end);
|
||
background: #f8fafc;
|
||
}
|
||
|
||
/* Grid Layout */
|
||
.plaza-grid {
|
||
display: grid;
|
||
grid-template-columns: 1.4fr 1fr;
|
||
gap: 32px;
|
||
}
|
||
|
||
/* Feature Card */
|
||
.plaza-feature {
|
||
position: relative;
|
||
height: 480px;
|
||
border-radius: var(--card-radius-lg);
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
padding: 40px;
|
||
color: #fff;
|
||
background-repeat: no-repeat;
|
||
background-size: 180%;
|
||
background-position: center center;
|
||
cursor: pointer;
|
||
transition:
|
||
transform 0.35s var(--ease-spring),
|
||
background-size 0.5s ease;
|
||
will-change: transform, background-size;
|
||
}
|
||
|
||
.plaza-feature:hover {
|
||
transform: translateY(-4px) scale(1.02);
|
||
box-shadow: var(--shadow-card-hover);
|
||
background-size: 190%;
|
||
}
|
||
|
||
.plaza-feature::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(
|
||
to top,
|
||
rgba(0, 0, 0, 0.45),
|
||
rgba(0, 0, 0, 0.18) 50%,
|
||
rgba(0, 0, 0, 0.02)
|
||
);
|
||
opacity: 1;
|
||
mix-blend-mode: normal;
|
||
z-index: 1;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.plaza-feature:hover::after {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.feature-overlay {
|
||
/* Additional gradient for text readability if needed */
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(to top, rgba(0,0,0,0.42), transparent 62%);
|
||
z-index: 2;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.feature-content {
|
||
position: relative;
|
||
z-index: 3;
|
||
}
|
||
|
||
.feature-badge {
|
||
display: inline-block;
|
||
padding: 6px 14px;
|
||
background: rgba(255, 255, 255, 0.25);
|
||
backdrop-filter: blur(8px);
|
||
border-radius: 99px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.feature-title {
|
||
font-size: 36px;
|
||
font-weight: 800;
|
||
margin: 0 0 16px 0;
|
||
line-height: 1.1;
|
||
text-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.feature-desc {
|
||
font-size: 16px;
|
||
opacity: 0.95;
|
||
margin: 0 0 32px 0;
|
||
max-width: 90%;
|
||
line-height: 1.6;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.feature-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.feature-author {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.author-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.author-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.author-name {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.publish-date {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.feature-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.feature-cta {
|
||
padding: 12px 28px;
|
||
background: #fff;
|
||
color: #000;
|
||
border: none;
|
||
border-radius: 99px;
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.feature-cta:hover {
|
||
transform: scale(1.05);
|
||
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
/* Like Button in Feature Card */
|
||
.plaza-like-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
background: rgba(255,255,255,0.2);
|
||
border: 1px solid rgba(255,255,255,0.4);
|
||
color: #fff;
|
||
padding: 8px 16px;
|
||
border-radius: 99px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
backdrop-filter: blur(4px);
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.plaza-like-btn:hover {
|
||
background: rgba(255,255,255,0.3);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.plaza-like-btn.active {
|
||
background: #fff;
|
||
color: #e11d48;
|
||
border-color: #fff;
|
||
}
|
||
|
||
.plaza-like-btn svg {
|
||
width: 20px;
|
||
height: 20px;
|
||
fill: currentColor;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
.plaza-like-btn:hover svg {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
/* List Section */
|
||
.plaza-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.plaza-item-card {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px 20px;
|
||
background: var(--color-bg-list-card);
|
||
border-radius: var(--card-radius-md);
|
||
transition: all var(--duration-hover) ease;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
animation: fade-in-up 0.5s ease backwards;
|
||
animation-delay: var(--delay, 0s);
|
||
}
|
||
|
||
@keyframes fade-in-up {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.plaza-item-card:hover {
|
||
transform: translateY(-2px);
|
||
background: #fff;
|
||
box-shadow: var(--shadow-card-hover);
|
||
}
|
||
|
||
.item-anchor-bar {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 12px;
|
||
bottom: 12px;
|
||
width: 4px;
|
||
border-radius: 0 4px 4px 0;
|
||
opacity: 0.6;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.plaza-item-card:hover .item-anchor-bar {
|
||
opacity: 1;
|
||
width: 6px;
|
||
}
|
||
|
||
.item-content {
|
||
flex: 1;
|
||
padding-left: 16px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.item-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: var(--color-text-main);
|
||
margin: 0 0 6px 0;
|
||
line-height: 1.4;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.item-desc {
|
||
font-size: 13px;
|
||
color: var(--color-text-sub);
|
||
margin: 0 0 10px 0;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 1;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.item-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
color: #94a3b8;
|
||
gap: 6px;
|
||
}
|
||
|
||
.meta-tag {
|
||
color: var(--color-primary-end);
|
||
background: rgba(59, 130, 246, 0.1);
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.meta-divider {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* Like Button in List Item */
|
||
.item-like-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
background: transparent;
|
||
border: none;
|
||
color: #94a3b8;
|
||
padding: 0;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.item-like-btn svg {
|
||
width: 14px;
|
||
height: 14px;
|
||
fill: currentColor;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
.item-like-btn:hover {
|
||
color: #64748b;
|
||
}
|
||
|
||
.item-like-btn:hover svg {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.item-like-btn.active {
|
||
color: #e11d48;
|
||
}
|
||
|
||
.item-thumbnail {
|
||
width: 80px;
|
||
height: 60px;
|
||
border-radius: 10px;
|
||
background-size: cover;
|
||
background-position: center;
|
||
margin-left: 16px;
|
||
flex-shrink: 0;
|
||
border: 1px solid rgba(0,0,0,0.05);
|
||
overflow: hidden;
|
||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||
}
|
||
|
||
.plaza-item-card:hover .item-thumbnail {
|
||
transform: scale(1.08);
|
||
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.12);
|
||
}
|
||
|
||
/* Mobile */
|
||
@media (max-width: 1024px) {
|
||
.plaza-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.plaza-feature {
|
||
height: 400px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.home-plaza {
|
||
padding: 20px;
|
||
}
|
||
.plaza-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
}
|
||
.plaza-header-right {
|
||
width: 100%;
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
.plaza-btn {
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
.plaza-feature {
|
||
height: 360px;
|
||
padding: 24px;
|
||
}
|
||
.feature-title {
|
||
font-size: 28px;
|
||
}
|
||
.feature-desc {
|
||
font-size: 14px;
|
||
-webkit-line-clamp: 3;
|
||
}
|
||
}
|
||
|
||
/* Login Modal */
|
||
.plaza-login-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
backdrop-filter: blur(4px);
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.plaza-login-modal {
|
||
background: #fff;
|
||
padding: 32px;
|
||
border-radius: 24px;
|
||
width: 90%;
|
||
max-width: 360px;
|
||
text-align: center;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.plaza-login-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
margin-bottom: 24px;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.plaza-login-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-btn {
|
||
padding: 10px 24px;
|
||
border-radius: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.modal-btn.primary {
|
||
background: var(--color-primary-end);
|
||
color: #fff;
|
||
}
|
||
|
||
.modal-btn.ghost {
|
||
background: #f1f5f9;
|
||
color: #64748b;
|
||
}
|
||
</style>
|