前端页面更新
This commit is contained in:
@@ -31,7 +31,11 @@
|
|||||||
"Bash(npx playwright *)",
|
"Bash(npx playwright *)",
|
||||||
"Read(//c/Users/Chenwu/.cache/**)",
|
"Read(//c/Users/Chenwu/.cache/**)",
|
||||||
"Bash(npx vue-tsc *)",
|
"Bash(npx vue-tsc *)",
|
||||||
"Bash(npx vite *)"
|
"Bash(npx vite *)",
|
||||||
|
"Bash(gh api *)",
|
||||||
|
"WebFetch(domain:raw.githubusercontent.com)",
|
||||||
|
"Bash(node -e ' *)",
|
||||||
|
"Read(//c/tmp/**)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,14 @@
|
|||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<span class="source-meta">{{ news.source_name }} · {{ timeAgo }}</span>
|
<span class="source-meta">{{ news.source_name }} · {{ timeAgo }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="card-title">{{ news.title_zh }}</h3>
|
<h3 class="card-title">{{ news.title_zh }}</h3>
|
||||||
<p class="card-summary">{{ news.summary }}</p>
|
<p class="card-summary">{{ news.summary }}</p>
|
||||||
|
|
||||||
<div v-if="news.opinion" class="opinion-block">
|
<div v-if="news.opinion" class="opinion-block">
|
||||||
<b>核心观点</b>{{ news.opinion }}
|
<b>核心观点</b>{{ news.opinion }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="kw-row">
|
<div class="kw-row">
|
||||||
<span v-for="kw in (news.keywords || []).slice(0, 5)" :key="kw" class="kw-tag">{{ kw }}</span>
|
<span v-for="kw in (news.keywords || []).slice(0, 5)" :key="kw" class="kw-chip">{{ kw }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a :href="news.source_url" target="_blank" @click.stop class="orig-link">查看原文 →</a>
|
<a :href="news.source_url" target="_blank" @click.stop class="orig-link">查看原文 →</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,42 +46,45 @@ const timeAgo = computed(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.news-card {
|
.news-card {
|
||||||
background: var(--bg-card);
|
background: #fff;
|
||||||
border: 1px solid var(--rule);
|
border: 1px solid var(--rule2);
|
||||||
border-radius: var(--r);
|
border-radius: var(--r);
|
||||||
padding: 16px;
|
padding: 18px;
|
||||||
margin-bottom: 10px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color .15s, transform .15s;
|
transition: all 0.15s;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
.news-card:hover { border-color: var(--rule2); transform: translateY(-1px); }
|
.news-card:hover {
|
||||||
.news-card.featured { border-left: 2px solid var(--blue-2); }
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border-color: var(--blue-bd);
|
||||||
|
}
|
||||||
|
.news-card.featured { border-left: 3px solid var(--blue); }
|
||||||
|
|
||||||
/* Top row */
|
|
||||||
.card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
.card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||||
|
|
||||||
.score-badge {
|
.score-badge {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 2px 7px;
|
padding: 2px 7px;
|
||||||
border-radius: 6px;
|
border-radius: 5px;
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.badge-red { background: rgba(255,77,106,.2); color: var(--red); }
|
.badge-red { background: #fef2f2; color: #dc2626; }
|
||||||
.badge-amber { background: rgba(255,163,54,.2); color: var(--amber); }
|
.badge-amber { background: #fffbeb; color: #d97706; }
|
||||||
.badge-blue { background: rgba(46,85,245,.2); color: var(--blue-2); }
|
.badge-blue { background: #eff6ff; color: #2563eb; }
|
||||||
.badge-gray { background: rgba(122,128,160,.15); color: var(--t3); }
|
.badge-gray { background: #f9fafb; color: #6b7280; }
|
||||||
|
|
||||||
.cat-label { font-size: 11px; color: var(--t3); }
|
.cat-label { font-size: 12px; color: var(--t3); }
|
||||||
.spacer { flex: 1; }
|
.spacer { flex: 1; }
|
||||||
.source-meta { font-size: 11px; color: var(--t4); font-family: var(--mono); }
|
.source-meta { font-size: 11px; color: var(--t4); font-family: var(--mono); }
|
||||||
|
|
||||||
/* Body */
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--t1);
|
color: var(--t1);
|
||||||
line-height: 1.4;
|
line-height: 1.45;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
.card-summary {
|
.card-summary {
|
||||||
@@ -98,15 +98,14 @@ const timeAgo = computed(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opinion */
|
|
||||||
.opinion-block {
|
.opinion-block {
|
||||||
background: var(--blue-gl);
|
background: #eff6ff;
|
||||||
border-left: 2px solid var(--blue-bd);
|
border-left: 2px solid rgba(37, 99, 235, 0.35);
|
||||||
border-radius: 0 6px 6px 0;
|
border-radius: 0 6px 6px 0;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 12.5px;
|
font-size: 12.5px;
|
||||||
color: #8AAAFF;
|
color: var(--t2);
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
.opinion-block b {
|
.opinion-block b {
|
||||||
@@ -115,27 +114,26 @@ const timeAgo = computed(() => {
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
opacity: .7;
|
color: var(--blue);
|
||||||
letter-spacing: .04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
.card-footer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
.card-footer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||||
.kw-row { display: flex; gap: 5px; flex-wrap: wrap; flex: 1; }
|
.kw-row { display: flex; gap: 5px; flex-wrap: wrap; flex: 1; }
|
||||||
.kw-tag {
|
.kw-chip {
|
||||||
font-size: 10px;
|
font-size: 11px;
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
padding: 2px 7px;
|
padding: 2px 7px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: rgba(46,85,245,.15);
|
background: #eff6ff;
|
||||||
color: #7B9BFF;
|
color: #3b82f6;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.orig-link {
|
.orig-link {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--blue-2);
|
color: var(--blue);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: color .15s;
|
transition: color 0.15s;
|
||||||
}
|
}
|
||||||
.orig-link:hover { color: var(--t1); }
|
.orig-link:hover { color: #1d4ed8; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,37 +1,41 @@
|
|||||||
/* 设计令牌——与 HTML 参考文件保持一致 */
|
/* Light theme design tokens */
|
||||||
:root {
|
:root {
|
||||||
--bg: #07091A;
|
--bg: #f0f2f5;
|
||||||
--bg-1: #0B0E24;
|
--bg-card: #ffffff;
|
||||||
--bg-2: #10142E;
|
--bg-hover: #f0f4ff;
|
||||||
--bg-card: #0D1028;
|
--bg-1: #ffffff;
|
||||||
--bg-hi: #141838;
|
--bg-2: #f8f9fc;
|
||||||
|
--bg-hi: #f3f4f6;
|
||||||
|
|
||||||
--blue: #2E55F5;
|
--blue: #2563eb;
|
||||||
--blue-2: #5578FF;
|
--blue-2: #3b82f6;
|
||||||
--blue-gl: rgba(46, 85, 245, 0.15);
|
--blue-gl: rgba(37, 99, 235, 0.06);
|
||||||
--blue-bd: rgba(46, 85, 245, 0.3);
|
--blue-bd: rgba(37, 99, 235, 0.2);
|
||||||
|
|
||||||
--violet: #7B3FE4;
|
--violet: #7c3aed;
|
||||||
--violet-2: #9A68EE;
|
--cyan: #0ea5e9;
|
||||||
--cyan: #19C3E6;
|
--mint: #10b981;
|
||||||
--mint: #25D6A3;
|
--amber: #f59e0b;
|
||||||
--amber: #FFA336;
|
--red: #ef4444;
|
||||||
--red: #FF4D6A;
|
|
||||||
|
|
||||||
--t1: #F0F2FF;
|
--t1: #111827;
|
||||||
--t2: #B8BEDD;
|
--t2: #374151;
|
||||||
--t3: #7A80A0;
|
--t3: #6b7280;
|
||||||
--t4: #4A506A;
|
--t4: #9ca3af;
|
||||||
|
|
||||||
--rule: rgba(255, 255, 255, 0.07);
|
--rule: #f3f4f6;
|
||||||
--rule2: rgba(255, 255, 255, 0.13);
|
--rule2: #e5e7eb;
|
||||||
|
|
||||||
--sans: 'Noto Sans SC', system-ui, sans-serif;
|
--sans: 'Noto Sans SC', system-ui, sans-serif;
|
||||||
--mono: 'JetBrains Mono', 'Courier New', monospace;
|
--mono: 'JetBrains Mono', 'Courier New', monospace;
|
||||||
|
|
||||||
--r: 12px;
|
--r: 12px;
|
||||||
--r-sm: 8px;
|
--r-sm: 8px;
|
||||||
--r-pill: 20px;
|
--r-pill: 9999px;
|
||||||
|
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
|
||||||
|
--shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
|
--shadow-lg: 0 8px 24px rgba(37,99,235,0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
@@ -47,13 +51,14 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a { color: inherit; text-decoration: none; }
|
a { color: inherit; text-decoration: none; }
|
||||||
button { cursor: pointer; font-family: inherit; }
|
button { cursor: pointer; font-family: inherit; border: none; background: none; }
|
||||||
input, select, textarea { font-family: inherit; }
|
input, select, textarea { font-family: inherit; }
|
||||||
|
|
||||||
/* Element Plus 暗色覆盖 */
|
/* Element Plus light theme */
|
||||||
.el-table { --el-table-bg-color: var(--bg-card); --el-table-header-bg-color: var(--bg-2); }
|
.el-input__wrapper { background: #fff !important; box-shadow: 0 0 0 1px var(--rule2) !important; }
|
||||||
.el-input__wrapper { background: var(--bg-2) !important; box-shadow: 0 0 0 1px var(--rule2) !important; }
|
|
||||||
.el-input__inner { color: var(--t1) !important; }
|
.el-input__inner { color: var(--t1) !important; }
|
||||||
.el-select-dropdown { background: var(--bg-2); border-color: var(--rule2); }
|
.el-select-dropdown { background: #fff; border-color: var(--rule2); }
|
||||||
.el-select-dropdown__item { color: var(--t2); }
|
.el-select-dropdown__item { color: var(--t2); }
|
||||||
.el-select-dropdown__item.hover, .el-select-dropdown__item:hover { background: var(--bg-hi); }
|
.el-select-dropdown__item.hover,
|
||||||
|
.el-select-dropdown__item:hover { background: var(--bg-hover); }
|
||||||
|
.el-table { --el-table-bg-color: #fff; --el-table-header-bg-color: var(--bg-2); }
|
||||||
|
|||||||
@@ -1,94 +1,167 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="reader-layout">
|
<div class="app">
|
||||||
<!-- ── 顶部导航 ─────────────────────────────────────────────────── -->
|
|
||||||
<header class="topbar">
|
<!-- ── Header ──────────────────────────────────────────────────── -->
|
||||||
<div class="topbar-left">
|
<header class="header">
|
||||||
<div class="logo-chip">PI</div>
|
<div class="header-inner">
|
||||||
<span class="app-title">医药情报</span>
|
<div class="hd-left">
|
||||||
</div>
|
<div class="logo">
|
||||||
<div class="live-bar">
|
<div class="logo-icon">PI</div>
|
||||||
<span class="live-dot"></span>
|
<span class="logo-text">医药情报</span>
|
||||||
<span>{{ selectedDate }} · 今日 {{ totalCount }} 条</span>
|
</div>
|
||||||
</div>
|
<div class="hd-sep"></div>
|
||||||
<div class="topbar-right">
|
<nav class="tab-nav">
|
||||||
<router-link to="/admin" class="admin-link">管理后台 →</router-link>
|
<button
|
||||||
|
v-for="tab in TABS"
|
||||||
|
:key="tab.id"
|
||||||
|
:class="['tab-btn', { active: activeTab === tab.id }]"
|
||||||
|
@click="activeTab = tab.id"
|
||||||
|
>{{ tab.label }}</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hd-right">
|
||||||
|
<div class="live-chip">
|
||||||
|
<span class="live-dot"></span>
|
||||||
|
<span class="live-text">{{ totalCount }} 条</span>
|
||||||
|
</div>
|
||||||
|
<select class="date-select" v-model="selectedDate" @change="loadDate(selectedDate)">
|
||||||
|
<option v-for="d in archiveDates" :key="d.date" :value="d.date">
|
||||||
|
{{ d.date }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<router-link to="/admin" class="admin-link">管理后台</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- ── 分类筛选 ─────────────────────────────────────────────────── -->
|
<!-- ── Main ────────────────────────────────────────────────────── -->
|
||||||
<nav class="cat-nav">
|
<main class="main">
|
||||||
<button
|
|
||||||
v-for="c in CATEGORIES"
|
|
||||||
:key="c.value"
|
|
||||||
:class="['cat-pill', { active: activeCategory === c.value }]"
|
|
||||||
@click="setCategory(c.value)"
|
|
||||||
>{{ c.label }}</button>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- ── 主内容区 ─────────────────────────────────────────────────── -->
|
<!-- 精选 -->
|
||||||
<div class="main-content">
|
<div v-show="activeTab === 'featured'" class="tab-content">
|
||||||
<TopTenPanel
|
<div v-if="loading" class="state-msg"><div class="spinner"></div>加载中...</div>
|
||||||
:items="featuredNews"
|
<div v-else-if="featuredNews.length === 0" class="state-msg">
|
||||||
:dates="archiveDates"
|
今日暂无精选,请在管理后台触发抓取
|
||||||
:active-id="selectedNews?.id"
|
|
||||||
:selected-date="selectedDate"
|
|
||||||
@select="openDetail"
|
|
||||||
@date-change="loadDate"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="news-feed">
|
|
||||||
<div v-if="loading" class="feed-state">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
</div>
|
||||||
<template v-else-if="newsList.length">
|
<div v-else class="featured-grid">
|
||||||
<NewsCard
|
<article
|
||||||
v-for="n in newsList"
|
v-for="(n, i) in featuredNews"
|
||||||
:key="n.id"
|
:key="n.id"
|
||||||
:news="n"
|
:class="['fc-card', { 'fc-hero': i === 0 }]"
|
||||||
@open="openDetail"
|
@click="openDetail(n)"
|
||||||
/>
|
>
|
||||||
<div v-if="hasMore" class="load-more" @click="loadMore">加载更多</div>
|
<div class="fc-top">
|
||||||
</template>
|
<span class="fc-rank">{{ String(i + 1).padStart(2, '0') }}</span>
|
||||||
<div v-else class="feed-state">今日暂无数据,请先在管理后台触发抓取</div>
|
<span class="score-badge" :class="badgeClass(n.importance_score)">
|
||||||
|
{{ n.importance_score?.toFixed(1) }}
|
||||||
|
</span>
|
||||||
|
<span class="fc-cat">{{ n.category }}</span>
|
||||||
|
<span class="fc-source">{{ n.source_name }}</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="fc-title">{{ n.title_zh }}</h3>
|
||||||
|
<p class="fc-summary">{{ n.summary }}</p>
|
||||||
|
<div class="fc-footer">
|
||||||
|
<div class="fc-kws">
|
||||||
|
<span v-for="kw in (n.keywords || []).slice(0, 3)" :key="kw" class="kw-chip">{{ kw }}</span>
|
||||||
|
</div>
|
||||||
|
<a :href="n.source_url" target="_blank" @click.stop class="fc-link">原文 →</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ── 详情面板 ─────────────────────────────────────────────────── -->
|
<!-- 全部动态 -->
|
||||||
|
<div v-show="activeTab === 'all'" class="tab-content">
|
||||||
|
<nav class="cat-bar">
|
||||||
|
<button
|
||||||
|
v-for="c in CATEGORIES"
|
||||||
|
:key="c.value"
|
||||||
|
:class="['cat-pill', { active: activeCategory === c.value }]"
|
||||||
|
@click="setCategory(c.value)"
|
||||||
|
>{{ c.label }}</button>
|
||||||
|
</nav>
|
||||||
|
<div v-if="loading" class="state-msg"><div class="spinner"></div>加载中...</div>
|
||||||
|
<div v-else-if="newsList.length === 0" class="state-msg">
|
||||||
|
今日暂无数据,请在管理后台触发抓取
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="news-list">
|
||||||
|
<NewsCard v-for="n in newsList" :key="n.id" :news="n" @open="openDetail" />
|
||||||
|
</div>
|
||||||
|
<button v-if="hasMore" class="load-more-btn" @click="loadMore">加载更多</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AI日报 -->
|
||||||
|
<div v-show="activeTab === 'digest'" class="tab-content">
|
||||||
|
<div class="digest-hd">
|
||||||
|
<div class="digest-date">{{ selectedDate }}</div>
|
||||||
|
<h1 class="digest-title">医药情报 · AI日报</h1>
|
||||||
|
<p class="digest-sub">
|
||||||
|
今日处理 <strong>{{ totalCount }}</strong> 条资讯,
|
||||||
|
精选 <strong>{{ featuredNews.length }}</strong> 条要闻
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="featuredNews.length === 0" class="state-msg">
|
||||||
|
今日暂无精选,请在管理后台触发抓取
|
||||||
|
</div>
|
||||||
|
<div v-else class="digest-list">
|
||||||
|
<article
|
||||||
|
v-for="(n, i) in featuredNews"
|
||||||
|
:key="n.id"
|
||||||
|
class="digest-entry"
|
||||||
|
@click="openDetail(n)"
|
||||||
|
>
|
||||||
|
<div class="de-meta">
|
||||||
|
<span class="de-num">{{ String(i + 1).padStart(2, '0') }}</span>
|
||||||
|
<span class="score-badge" :class="badgeClass(n.importance_score)">
|
||||||
|
{{ n.importance_score?.toFixed(1) }}
|
||||||
|
</span>
|
||||||
|
<span class="de-cat">{{ n.category }}</span>
|
||||||
|
<span class="de-source">{{ n.source_name }} · {{ n.published_at?.slice(0, 10) }}</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="de-title">{{ n.title_zh }}</h3>
|
||||||
|
<p class="de-summary">{{ n.summary }}</p>
|
||||||
|
<div v-if="n.opinion" class="de-opinion">
|
||||||
|
<span class="de-op-label">◆ 核心观点</span>
|
||||||
|
<span>{{ n.opinion }}</span>
|
||||||
|
</div>
|
||||||
|
<a :href="n.source_url" target="_blank" @click.stop class="de-link">查看原文 →</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- ── Detail sheet ─────────────────────────────────────────────── -->
|
||||||
<Transition name="sheet">
|
<Transition name="sheet">
|
||||||
<div v-if="selectedNews" class="sheet-bg" @click.self="selectedNews = null">
|
<div v-if="selectedNews" class="sheet-bg" @click.self="selectedNews = null">
|
||||||
<div class="sheet">
|
<div class="sheet">
|
||||||
<div class="sheet-handle" @click="selectedNews = null"></div>
|
<div class="sheet-handle" @click="selectedNews = null"></div>
|
||||||
<div class="sheet-inner">
|
<div class="sheet-inner">
|
||||||
<div class="sheet-head">
|
<div class="sheet-hd">
|
||||||
<div class="sh-tags">
|
<div class="sh-tags">
|
||||||
<span class="kw-tag cat-tag">{{ selectedNews.category }}</span>
|
<span class="cat-chip">{{ selectedNews.category }}</span>
|
||||||
<span class="score-badge" :class="badgeClass(selectedNews.importance_score)">
|
<span class="score-badge" :class="badgeClass(selectedNews.importance_score)">
|
||||||
{{ selectedNews.importance_score?.toFixed(1) }}
|
{{ selectedNews.importance_score?.toFixed(1) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="sh-title">{{ selectedNews.title_zh }}</h2>
|
<h2 class="sh-title">{{ selectedNews.title_zh }}</h2>
|
||||||
<div class="sh-meta">
|
<p class="sh-meta">{{ selectedNews.source_name }} · {{ selectedNews.published_at?.slice(0, 10) }}</p>
|
||||||
{{ selectedNews.source_name }} · {{ selectedNews.published_at?.slice(0, 10) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sheet-bd">
|
||||||
<div class="sheet-body">
|
|
||||||
<p class="sh-summary">{{ selectedNews.summary }}</p>
|
<p class="sh-summary">{{ selectedNews.summary }}</p>
|
||||||
|
|
||||||
<div v-if="selectedNews.opinion" class="sh-block sh-blue">
|
<div v-if="selectedNews.opinion" class="sh-block sh-blue">
|
||||||
<b>核心观点</b>{{ selectedNews.opinion }}
|
<b>核心观点</b>{{ selectedNews.opinion }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="selectedNews.importance_reason" class="sh-block sh-green">
|
||||||
<div v-if="selectedNews.importance_reason" class="sh-block sh-mint">
|
|
||||||
<b>评分依据 · {{ selectedNews.importance_score?.toFixed(1) }} 分</b>
|
<b>评分依据 · {{ selectedNews.importance_score?.toFixed(1) }} 分</b>
|
||||||
{{ selectedNews.importance_reason }}
|
{{ selectedNews.importance_reason }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="selectedNews.keywords?.length" class="sh-kws">
|
||||||
<div v-if="selectedNews.keywords?.length" class="sh-kw">
|
<span v-for="kw in selectedNews.keywords" :key="kw" class="kw-chip">{{ kw }}</span>
|
||||||
<span v-for="kw in selectedNews.keywords" :key="kw" class="kw-tag">{{ kw }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sh-actions">
|
<div class="sh-actions">
|
||||||
<a :href="selectedNews.source_url" target="_blank" class="btn-primary">查看原文 →</a>
|
<a :href="selectedNews.source_url" target="_blank" class="btn-primary">查看原文 →</a>
|
||||||
<button class="btn-secondary" @click="selectedNews = null">关闭</button>
|
<button class="btn-secondary" @click="selectedNews = null">关闭</button>
|
||||||
@@ -98,33 +171,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import NewsCard from '../components/NewsCard.vue'
|
import NewsCard from '../components/NewsCard.vue'
|
||||||
import TopTenPanel from '../components/TopTenPanel.vue'
|
|
||||||
import { fetchFeatured, fetchNews, fetchDates } from '../api/index.js'
|
import { fetchFeatured, fetchNews, fetchDates } from '../api/index.js'
|
||||||
|
|
||||||
|
const TABS = [
|
||||||
|
{ id: 'featured', label: '精选' },
|
||||||
|
{ id: 'all', label: '全部动态' },
|
||||||
|
{ id: 'digest', label: 'AI日报' },
|
||||||
|
]
|
||||||
|
|
||||||
const CATEGORIES = [
|
const CATEGORIES = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '药品监管', value: '药品监管' },
|
{ label: '药品监管', value: '药品监管' },
|
||||||
{ label: '临床研究', value: '临床研究' },
|
{ label: '临床研究', value: '临床研究' },
|
||||||
{ label: '行业动态', value: '行业动态' },
|
{ label: '行业动态', value: '行业动态' },
|
||||||
{ label: '政策法规', value: '政策法规' },
|
{ label: '政策法规', value: '政策法规' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const selectedDate = ref(new Date().toISOString().slice(0, 10))
|
const activeTab = ref('featured')
|
||||||
|
const selectedDate = ref(new Date().toISOString().slice(0, 10))
|
||||||
const activeCategory = ref('')
|
const activeCategory = ref('')
|
||||||
const featuredNews = ref([])
|
const featuredNews = ref([])
|
||||||
const newsList = ref([])
|
const newsList = ref([])
|
||||||
const archiveDates = ref([])
|
const archiveDates = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const totalCount = ref(0)
|
const totalCount = ref(0)
|
||||||
const hasMore = ref(false)
|
const hasMore = ref(false)
|
||||||
const selectedNews = ref(null)
|
const selectedNews = ref(null)
|
||||||
|
|
||||||
function badgeClass(score) {
|
function badgeClass(score) {
|
||||||
if (score >= 9) return 'badge-red'
|
if (score >= 9) return 'badge-red'
|
||||||
@@ -142,11 +222,14 @@ async function loadAll(date) {
|
|||||||
fetchDates(),
|
fetchDates(),
|
||||||
])
|
])
|
||||||
featuredNews.value = feat.items || []
|
featuredNews.value = feat.items || []
|
||||||
newsList.value = list.items || []
|
newsList.value = list.items || []
|
||||||
totalCount.value = list.total || 0
|
totalCount.value = list.total || 0
|
||||||
hasMore.value = list.items?.length < list.total
|
hasMore.value = (list.items?.length || 0) < (list.total || 0)
|
||||||
|
page.value = 1
|
||||||
archiveDates.value = dates || []
|
archiveDates.value = dates || []
|
||||||
page.value = 1
|
if (!archiveDates.value.find(d => d.date === date)) {
|
||||||
|
archiveDates.value = [{ date, count: list.total || 0 }, ...archiveDates.value]
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -162,10 +245,10 @@ async function setCategory(cat) {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const list = await fetchNews({ date: selectedDate.value, category: cat, page: 1 })
|
const list = await fetchNews({ date: selectedDate.value, category: cat, page: 1 })
|
||||||
newsList.value = list.items || []
|
newsList.value = list.items || []
|
||||||
totalCount.value = list.total || 0
|
totalCount.value = list.total || 0
|
||||||
hasMore.value = list.items?.length < list.total
|
hasMore.value = (list.items?.length || 0) < (list.total || 0)
|
||||||
page.value = 1
|
page.value = 1
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -178,13 +261,12 @@ async function loadMore() {
|
|||||||
hasMore.value = newsList.value.length < list.total
|
hasMore.value = newsList.value.length < list.total
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDetail(news) {
|
function openDetail(news) { selectedNews.value = news }
|
||||||
selectedNews.value = news
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyDown(e) {
|
function onKeyDown(e) {
|
||||||
if (e.key === 'Escape' && selectedNews.value) selectedNews.value = null
|
if (e.key === 'Escape' && selectedNews.value) selectedNews.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadAll(selectedDate.value)
|
loadAll(selectedDate.value)
|
||||||
window.addEventListener('keydown', onKeyDown)
|
window.addEventListener('keydown', onKeyDown)
|
||||||
@@ -193,213 +275,306 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.reader-layout {
|
.app {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Topbar ─────────────────────────────────────────────────────────────────── */
|
/* ── Header ─────────────────────────────────────────────────────── */
|
||||||
.topbar {
|
.header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
height: 56px;
|
background: #fff;
|
||||||
background: rgba(7,9,26,.95);
|
border-bottom: 1px solid var(--rule2);
|
||||||
backdrop-filter: blur(12px);
|
box-shadow: 0 1px 0 var(--rule);
|
||||||
border-bottom: 1px solid var(--rule);
|
}
|
||||||
|
.header-inner {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 24px;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
|
||||||
}
|
}
|
||||||
.topbar-left { display: flex; align-items: center; gap: 10px; }
|
.hd-left { display: flex; align-items: center; }
|
||||||
.logo-chip {
|
.logo { display: flex; align-items: center; gap: 8px; margin-right: 20px; }
|
||||||
width: 30px; height: 30px;
|
.logo-icon {
|
||||||
background: linear-gradient(135deg, var(--blue), var(--violet));
|
width: 32px; height: 32px;
|
||||||
|
background: linear-gradient(135deg, #2563eb, #7c3aed);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
font-family: var(--mono); font-size: 11px; font-weight: 700; color: #fff;
|
font-size: 11px; font-weight: 700; color: #fff;
|
||||||
|
font-family: var(--mono);
|
||||||
}
|
}
|
||||||
.app-title { font-size: 16px; font-weight: 700; letter-spacing: -.02em; }
|
.logo-text { font-size: 16px; font-weight: 700; color: var(--t1); letter-spacing: -0.02em; }
|
||||||
.live-bar {
|
.hd-sep { width: 1px; height: 20px; background: var(--rule2); margin: 0 16px; }
|
||||||
flex: 1;
|
|
||||||
display: flex; align-items: center; gap: 6px; justify-content: center;
|
.tab-nav { display: flex; gap: 0; }
|
||||||
font-size: 12px; color: var(--t4); font-family: var(--mono);
|
.tab-btn {
|
||||||
|
padding: 8px 18px;
|
||||||
|
font-size: 14px; font-weight: 500;
|
||||||
|
color: var(--t3);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
transition: all 0.15s;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
.tab-btn:hover { color: var(--t1); }
|
||||||
|
.tab-btn.active { color: var(--blue); border-bottom-color: var(--blue); font-weight: 600; }
|
||||||
|
|
||||||
|
.hd-right { display: flex; align-items: center; gap: 12px; }
|
||||||
|
.live-chip {
|
||||||
|
display: flex; align-items: center; gap: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: var(--r-pill);
|
||||||
}
|
}
|
||||||
.live-dot {
|
.live-dot {
|
||||||
width: 6px; height: 6px; border-radius: 50%; background: var(--mint);
|
width: 6px; height: 6px; border-radius: 50%; background: #10b981;
|
||||||
box-shadow: 0 0 0 0 rgba(37,214,163,.4);
|
|
||||||
animation: pulse 2s infinite;
|
animation: pulse 2s infinite;
|
||||||
}
|
}
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% { box-shadow: 0 0 0 0 rgba(37,214,163,.4); }
|
0%, 100% { box-shadow: 0 0 0 0 rgba(16,185,129,0.4); }
|
||||||
70% { box-shadow: 0 0 0 6px rgba(37,214,163,0); }
|
50% { box-shadow: 0 0 0 5px rgba(16,185,129,0); }
|
||||||
100% { box-shadow: 0 0 0 0 rgba(37,214,163,0); }
|
}
|
||||||
|
.live-text { font-size: 12px; color: #059669; font-weight: 500; }
|
||||||
|
.date-select {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--rule2); border-radius: var(--r-sm);
|
||||||
|
font-size: 13px; color: var(--t2); background: #fff; cursor: pointer;
|
||||||
}
|
}
|
||||||
.topbar-right { display: flex; align-items: center; }
|
|
||||||
.admin-link {
|
.admin-link {
|
||||||
font-size: 12px; color: var(--blue-2); font-family: var(--mono);
|
font-size: 13px; color: var(--t3);
|
||||||
padding: 5px 12px; border: 1px solid var(--blue-bd); border-radius: var(--r-pill);
|
padding: 6px 14px;
|
||||||
transition: background .15s;
|
border: 1px solid var(--rule2); border-radius: var(--r-sm);
|
||||||
|
transition: all 0.15s;
|
||||||
}
|
}
|
||||||
.admin-link:hover { background: var(--blue-gl); }
|
.admin-link:hover { color: var(--t1); border-color: var(--t4); }
|
||||||
|
|
||||||
/* ── Category nav ────────────────────────────────────────────────────────────── */
|
/* ── Main ────────────────────────────────────────────────────────── */
|
||||||
.cat-nav {
|
.main {
|
||||||
position: sticky;
|
|
||||||
top: 56px;
|
|
||||||
z-index: 99;
|
|
||||||
background: rgba(7,9,26,.92);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
border-bottom: 1px solid var(--rule);
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 10px 24px;
|
|
||||||
overflow-x: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
.cat-nav::-webkit-scrollbar { display: none; }
|
|
||||||
.cat-pill {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 5px 14px;
|
|
||||||
border-radius: var(--r-pill);
|
|
||||||
border: 1px solid var(--rule2);
|
|
||||||
background: var(--bg-2);
|
|
||||||
color: var(--t3);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all .15s;
|
|
||||||
}
|
|
||||||
.cat-pill:hover { color: var(--t2); border-color: var(--rule2); }
|
|
||||||
.cat-pill.active { background: var(--blue-gl); border-color: var(--blue-bd); color: var(--blue-2); }
|
|
||||||
|
|
||||||
/* ── Main layout ─────────────────────────────────────────────────────────────── */
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
max-width: 1200px;
|
||||||
gap: 20px;
|
|
||||||
padding: 20px 24px;
|
|
||||||
max-width: 1400px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
.tab-content { width: 100%; }
|
||||||
.news-feed { flex: 1; min-width: 0; }
|
.state-msg {
|
||||||
|
|
||||||
.feed-state {
|
|
||||||
display: flex; align-items: center; gap: 10px;
|
display: flex; align-items: center; gap: 10px;
|
||||||
padding: 40px 0; color: var(--t4); font-size: 13px;
|
padding: 80px 0; color: var(--t4); font-size: 14px; justify-content: center;
|
||||||
}
|
}
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 16px; height: 16px; border-radius: 50%;
|
width: 16px; height: 16px;
|
||||||
border: 2px solid var(--rule2); border-top-color: var(--blue-2);
|
border: 2px solid var(--rule2); border-top-color: var(--blue);
|
||||||
animation: spin .8s linear infinite;
|
border-radius: 50%; animation: spin 0.8s linear infinite;
|
||||||
}
|
}
|
||||||
@keyframes spin { to { transform: rotate(360deg); } }
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
.load-more {
|
/* ── Score badges ──────────────────────────────────────────────── */
|
||||||
text-align: center; padding: 16px;
|
.score-badge {
|
||||||
font-size: 13px; color: var(--blue-2);
|
padding: 2px 7px; border-radius: 5px;
|
||||||
cursor: pointer; border: 1px dashed var(--blue-bd);
|
font-family: var(--mono); font-size: 11px; font-weight: 700; flex-shrink: 0;
|
||||||
border-radius: var(--r); transition: background .15s;
|
|
||||||
}
|
}
|
||||||
.load-more:hover { background: var(--blue-gl); }
|
.badge-red { background: #fef2f2; color: #dc2626; }
|
||||||
|
.badge-amber { background: #fffbeb; color: #d97706; }
|
||||||
|
.badge-blue { background: #eff6ff; color: #2563eb; }
|
||||||
|
.badge-gray { background: #f9fafb; color: #6b7280; }
|
||||||
|
|
||||||
/* ── Detail sheet ────────────────────────────────────────────────────────────── */
|
/* ── KW chips ──────────────────────────────────────────────────── */
|
||||||
|
.kw-chip {
|
||||||
|
font-size: 11px; font-family: var(--mono);
|
||||||
|
padding: 2px 8px; border-radius: 4px;
|
||||||
|
background: #eff6ff; color: #3b82f6; white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 精选 ────────────────────────────────────────────────────────── */
|
||||||
|
.featured-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.fc-card {
|
||||||
|
background: #fff; border: 1px solid var(--rule2); border-radius: var(--r);
|
||||||
|
padding: 20px; cursor: pointer;
|
||||||
|
transition: all 0.15s; box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.fc-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow); border-color: var(--blue-bd);
|
||||||
|
}
|
||||||
|
.fc-hero { grid-column: 1 / -1; border-left: 3px solid var(--blue); }
|
||||||
|
.fc-hero .fc-title { font-size: 20px; }
|
||||||
|
.fc-hero .fc-summary { -webkit-line-clamp: 4; }
|
||||||
|
|
||||||
|
.fc-top { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
|
||||||
|
.fc-rank { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--blue); min-width: 22px; }
|
||||||
|
.fc-cat { font-size: 12px; color: var(--t3); }
|
||||||
|
.fc-source { font-size: 11px; color: var(--t4); font-family: var(--mono); margin-left: auto; }
|
||||||
|
|
||||||
|
.fc-title {
|
||||||
|
font-size: 16px; font-weight: 600; color: var(--t1);
|
||||||
|
line-height: 1.45; margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.fc-summary {
|
||||||
|
font-size: 13px; color: var(--t3); line-height: 1.65; margin-bottom: 14px;
|
||||||
|
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
|
||||||
|
}
|
||||||
|
.fc-footer { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.fc-kws { display: flex; gap: 5px; flex-wrap: wrap; flex: 1; }
|
||||||
|
.fc-link { font-size: 12px; color: var(--blue); white-space: nowrap; transition: color 0.15s; }
|
||||||
|
.fc-link:hover { color: #1d4ed8; }
|
||||||
|
|
||||||
|
/* ── 全部动态 ────────────────────────────────────────────────────── */
|
||||||
|
.cat-bar { display: flex; gap: 8px; padding: 0 0 20px; flex-wrap: wrap; }
|
||||||
|
.cat-pill {
|
||||||
|
padding: 6px 16px; border-radius: var(--r-pill);
|
||||||
|
border: 1px solid var(--rule2); background: #fff;
|
||||||
|
color: var(--t3); font-size: 13px; font-weight: 500; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.cat-pill:hover { border-color: var(--blue-bd); color: var(--blue); }
|
||||||
|
.cat-pill.active { background: var(--blue); border-color: var(--blue); color: #fff; }
|
||||||
|
|
||||||
|
.news-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.load-more-btn {
|
||||||
|
width: 100%; margin-top: 12px; padding: 14px;
|
||||||
|
border: 1px dashed var(--rule2); border-radius: var(--r);
|
||||||
|
background: transparent; color: var(--blue);
|
||||||
|
font-size: 13px; font-weight: 500; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.load-more-btn:hover { background: var(--blue-gl); border-color: var(--blue-bd); }
|
||||||
|
|
||||||
|
/* ── AI日报 ──────────────────────────────────────────────────────── */
|
||||||
|
.digest-hd {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 0 36px;
|
||||||
|
border-bottom: 2px solid var(--rule2);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.digest-date {
|
||||||
|
font-family: var(--mono); font-size: 12px; color: var(--t4);
|
||||||
|
margin-bottom: 10px; letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
.digest-title {
|
||||||
|
font-size: 28px; font-weight: 700; color: var(--t1);
|
||||||
|
margin-bottom: 10px; letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
.digest-sub { font-size: 14px; color: var(--t3); line-height: 1.5; }
|
||||||
|
.digest-sub strong { color: var(--blue); }
|
||||||
|
|
||||||
|
.digest-list { display: flex; flex-direction: column; }
|
||||||
|
.digest-entry {
|
||||||
|
padding: 28px 0;
|
||||||
|
border-bottom: 1px solid var(--rule2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.digest-entry:last-child { border-bottom: none; }
|
||||||
|
.digest-entry:hover .de-title { color: var(--blue); }
|
||||||
|
|
||||||
|
.de-meta { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||||
|
.de-num { font-family: var(--mono); font-size: 22px; font-weight: 700; color: var(--rule2); min-width: 32px; }
|
||||||
|
.de-cat { font-size: 12px; color: var(--t3); }
|
||||||
|
.de-source { font-size: 12px; color: var(--t4); font-family: var(--mono); margin-left: auto; }
|
||||||
|
|
||||||
|
.de-title {
|
||||||
|
font-size: 18px; font-weight: 700; color: var(--t1);
|
||||||
|
line-height: 1.4; margin-bottom: 10px; transition: color 0.15s;
|
||||||
|
}
|
||||||
|
.de-summary {
|
||||||
|
font-size: 14px; color: var(--t2); line-height: 1.7; margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.de-opinion {
|
||||||
|
display: flex; gap: 10px;
|
||||||
|
background: #eff6ff; border-left: 3px solid var(--blue);
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
padding: 10px 14px; margin-bottom: 14px;
|
||||||
|
font-size: 13px; color: var(--t2); line-height: 1.6;
|
||||||
|
}
|
||||||
|
.de-op-label { color: var(--blue); font-weight: 600; white-space: nowrap; flex-shrink: 0; }
|
||||||
|
.de-link {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 13px; color: var(--blue); font-weight: 500; transition: color 0.15s;
|
||||||
|
}
|
||||||
|
.de-link:hover { color: #1d4ed8; }
|
||||||
|
|
||||||
|
/* ── Detail sheet ───────────────────────────────────────────────── */
|
||||||
.sheet-bg {
|
.sheet-bg {
|
||||||
position: fixed; inset: 0;
|
position: fixed; inset: 0;
|
||||||
background: rgba(0,0,0,.6);
|
background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(4px);
|
||||||
backdrop-filter: blur(4px);
|
z-index: 200; display: flex; align-items: flex-end; justify-content: center;
|
||||||
z-index: 200;
|
|
||||||
display: flex; align-items: flex-end; justify-content: center;
|
|
||||||
}
|
}
|
||||||
.sheet {
|
.sheet {
|
||||||
width: 100%; max-width: 680px;
|
width: 100%; max-width: 680px;
|
||||||
background: var(--bg-1);
|
background: #fff; border-radius: 20px 20px 0 0;
|
||||||
border-radius: 20px 20px 0 0;
|
|
||||||
border-top: 1px solid var(--rule2);
|
border-top: 1px solid var(--rule2);
|
||||||
max-height: 85vh;
|
max-height: 88vh; overflow-y: auto; scrollbar-width: none;
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
}
|
||||||
.sheet::-webkit-scrollbar { display: none; }
|
.sheet::-webkit-scrollbar { display: none; }
|
||||||
.sheet-handle {
|
.sheet-handle {
|
||||||
width: 36px; height: 4px; background: var(--rule2); border-radius: 2px;
|
width: 36px; height: 4px; background: var(--rule2);
|
||||||
margin: 10px auto 0; cursor: pointer;
|
border-radius: 2px; margin: 12px auto 0; cursor: pointer;
|
||||||
}
|
}
|
||||||
.sheet-inner { padding: 8px 0 40px; }
|
.sheet-inner { padding: 8px 0 48px; }
|
||||||
|
.sheet-hd { padding: 14px 24px 16px; }
|
||||||
.sheet-head { padding: 12px 24px 16px; }
|
.sh-tags { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||||
.sh-tags { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
.cat-chip {
|
||||||
.cat-tag { background: rgba(122,128,160,.15); color: var(--t3); }
|
font-size: 12px; padding: 3px 10px;
|
||||||
.score-badge {
|
border-radius: var(--r-pill); background: #f3f4f6; color: var(--t3);
|
||||||
padding: 2px 8px; border-radius: 5px;
|
|
||||||
font-family: var(--mono); font-size: 11px; font-weight: 700;
|
|
||||||
}
|
}
|
||||||
.badge-red { background: rgba(255,77,106,.2); color: var(--red); }
|
.sh-title {
|
||||||
.badge-amber { background: rgba(255,163,54,.2); color: var(--amber); }
|
font-size: 20px; font-weight: 700; color: var(--t1);
|
||||||
.badge-blue { background: rgba(46,85,245,.2); color: var(--blue-2); }
|
line-height: 1.4; margin-bottom: 6px;
|
||||||
.badge-gray { background: rgba(122,128,160,.15); color: var(--t3); }
|
}
|
||||||
|
.sh-meta { font-size: 12px; color: var(--t4); font-family: var(--mono); }
|
||||||
.sh-title { font-size: 18px; font-weight: 700; color: var(--t1); line-height: 1.4; margin-bottom: 6px; }
|
|
||||||
.sh-meta { font-size: 11px; color: var(--t4); font-family: var(--mono); }
|
|
||||||
|
|
||||||
.sheet-body { padding: 0 24px; }
|
|
||||||
.sh-summary { font-size: 14px; color: var(--t2); line-height: 1.7; margin-bottom: 14px; }
|
|
||||||
|
|
||||||
|
.sheet-bd { padding: 0 24px; }
|
||||||
|
.sh-summary { font-size: 14px; color: var(--t2); line-height: 1.7; margin-bottom: 16px; }
|
||||||
.sh-block {
|
.sh-block {
|
||||||
border-radius: 0 var(--r-sm) var(--r-sm) 0;
|
border-radius: 0 8px 8px 0; padding: 10px 14px;
|
||||||
padding: 10px 14px;
|
margin-bottom: 12px; font-size: 13px; line-height: 1.6;
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
}
|
||||||
.sh-blue { background: var(--blue-gl); border-left: 2px solid var(--blue-bd); color: #8AAAFF; }
|
.sh-blue { background: #eff6ff; border-left: 2px solid var(--blue); color: var(--t2); }
|
||||||
.sh-mint { background: rgba(37,214,163,.06); border-left: 2px solid rgba(37,214,163,.4); color: rgba(37,214,163,.85); }
|
.sh-green { background: #f0fdf4; border-left: 2px solid #10b981; color: var(--t2); }
|
||||||
.sh-block b {
|
.sh-block b {
|
||||||
display: block; font-family: var(--mono); font-size: 10px;
|
display: block; font-family: var(--mono); font-size: 11px; font-weight: 700;
|
||||||
font-weight: 700; margin-bottom: 4px; opacity: .7; letter-spacing: .04em;
|
margin-bottom: 4px; letter-spacing: 0.04em; color: var(--t4);
|
||||||
}
|
}
|
||||||
|
.sh-kws { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 18px; }
|
||||||
.sh-kw { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
|
|
||||||
|
|
||||||
.kw-tag {
|
|
||||||
font-size: 10px; font-family: var(--mono);
|
|
||||||
padding: 2px 7px; border-radius: 4px;
|
|
||||||
background: rgba(46,85,245,.15); color: #7B9BFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sh-actions { display: flex; gap: 10px; }
|
.sh-actions { display: flex; gap: 10px; }
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
flex: 1; height: 42px; border-radius: 10px;
|
flex: 1; height: 44px; border-radius: var(--r);
|
||||||
background: var(--blue); color: #fff;
|
background: var(--blue); color: #fff;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
font-size: 13px; font-weight: 600;
|
font-size: 14px; font-weight: 600; transition: background 0.15s;
|
||||||
transition: background .15s;
|
|
||||||
}
|
}
|
||||||
.btn-primary:hover { background: var(--blue-2); }
|
.btn-primary:hover { background: #1d4ed8; }
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
flex: 1; height: 42px; border-radius: 10px;
|
flex: 1; height: 44px; border-radius: var(--r);
|
||||||
background: var(--bg-2); border: 1px solid var(--rule2); color: var(--t2);
|
background: #f9fafb; border: 1px solid var(--rule2); color: var(--t2);
|
||||||
font-size: 13px; font-weight: 600;
|
font-size: 14px; font-weight: 600; transition: background 0.15s;
|
||||||
transition: background .15s;
|
|
||||||
}
|
}
|
||||||
.btn-secondary:hover { background: var(--bg-hi); }
|
.btn-secondary:hover { background: #f3f4f6; }
|
||||||
|
|
||||||
/* ── Transitions ─────────────────────────────────────────────────────────────── */
|
/* ── Transitions ─────────────────────────────────────────────────── */
|
||||||
.sheet-enter-active, .sheet-leave-active { transition: opacity .25s; }
|
.sheet-enter-active, .sheet-leave-active { transition: opacity 0.25s; }
|
||||||
.sheet-enter-active .sheet, .sheet-leave-active .sheet { transition: transform .3s cubic-bezier(.32,.72,0,1); }
|
.sheet-enter-active .sheet,
|
||||||
|
.sheet-leave-active .sheet { transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1); }
|
||||||
.sheet-enter-from, .sheet-leave-to { opacity: 0; }
|
.sheet-enter-from, .sheet-leave-to { opacity: 0; }
|
||||||
.sheet-enter-from .sheet, .sheet-leave-to .sheet { transform: translateY(100%); }
|
.sheet-enter-from .sheet, .sheet-leave-to .sheet { transform: translateY(100%); }
|
||||||
|
|
||||||
/* ── Mobile ──────────────────────────────────────────────────────────────────── */
|
/* ── Mobile ──────────────────────────────────────────────────────── */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.main-content { padding: 12px 16px; gap: 0; }
|
.header-inner { padding: 0 16px; }
|
||||||
/* TopTenPanel becomes horizontally scrollable on mobile — handled in component */
|
.hd-sep, .logo-text { display: none; }
|
||||||
.topbar { padding: 0 16px; }
|
.live-chip { display: none; }
|
||||||
.cat-nav { padding: 8px 16px; }
|
.main { padding: 16px; }
|
||||||
.live-bar { display: none; }
|
.featured-grid { grid-template-columns: 1fr; }
|
||||||
|
.fc-hero { grid-column: auto; }
|
||||||
|
.digest-title { font-size: 22px; }
|
||||||
|
.de-source { display: none; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
257
refrence/frontend_DESIGN.md
Normal file
257
refrence/frontend_DESIGN.md
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# Design System: MiniMax
|
||||||
|
|
||||||
|
## 1. Visual Theme & Atmosphere
|
||||||
|
|
||||||
|
MiniMax's website is a clean, product-showcase platform for a Chinese AI technology company that bridges consumer-friendly appeal with technical credibility. The design language is predominantly white-space-driven with a light, airy feel — pure white backgrounds (`#ffffff`) dominate, letting colorful product cards and AI model illustrations serve as the visual anchors. The overall aesthetic sits at the intersection of Apple's product marketing clarity and a playful, rounded design language that makes AI technology feel approachable.
|
||||||
|
|
||||||
|
The typography system is notably multi-font: DM Sans serves as the primary UI workhorse, Outfit handles display headings with geometric elegance, Poppins appears for mid-tier headings, and Roboto handles data-heavy contexts. This variety reflects a brand in rapid growth — each font serves a distinct communicative purpose rather than competing for attention. The hero heading at 80px weight 500 in both DM Sans and Outfit with a tight 1.10 line-height creates a bold but not aggressive opening statement.
|
||||||
|
|
||||||
|
What makes MiniMax distinctive is its pill-button geometry (9999px radius) for navigation and primary actions, combined with softer 8px–24px radiused cards for product showcases. The product cards themselves are richly colorful — vibrant gradients in pink, purple, orange, and blue — creating a "gallery of AI capabilities" feel. Against the white canvas, these colorful cards pop like app icons on a phone home screen, making each AI model/product feel like a self-contained creative tool.
|
||||||
|
|
||||||
|
**Key Characteristics:**
|
||||||
|
- White-dominant layout with colorful product card accents
|
||||||
|
- Multi-font system: DM Sans (UI), Outfit (display), Poppins (mid-tier), Roboto (data)
|
||||||
|
- Pill buttons (9999px radius) for primary navigation and CTAs
|
||||||
|
- Generous rounded cards (20px–24px radius) for product showcases
|
||||||
|
- Brand blue spectrum: from `#1456f0` (brand-6) through `#3b82f6` (primary-500) to `#60a5fa` (light)
|
||||||
|
- Brand pink (`#ea5ec1`) as secondary accent
|
||||||
|
- Near-black text (`#222222`, `#18181b`) on white backgrounds
|
||||||
|
- Purple-tinted shadows (`rgba(44, 30, 116, 0.16)`) creating subtle brand-colored depth
|
||||||
|
- Dark footer section (`#181e25`) with product/company links
|
||||||
|
|
||||||
|
## 2. Color Palette & Roles
|
||||||
|
|
||||||
|
### Brand Primary
|
||||||
|
- **Brand Blue** (`#1456f0`): `--brand-6`, primary brand identity color
|
||||||
|
- **Sky Blue** (`#3daeff`): `--col-brand00`, lighter brand variant for accents
|
||||||
|
- **Brand Pink** (`#ea5ec1`): `--col-brand02`, secondary brand accent
|
||||||
|
|
||||||
|
### Blue Scale (Primary)
|
||||||
|
- **Primary 200** (`#bfdbfe`): `--color-primary-200`, light blue backgrounds
|
||||||
|
- **Primary Light** (`#60a5fa`): `--color-primary-light`, active states, highlights
|
||||||
|
- **Primary 500** (`#3b82f6`): `--color-primary-500`, standard blue actions
|
||||||
|
- **Primary 600** (`#2563eb`): `--color-primary-600`, hover states
|
||||||
|
- **Primary 700** (`#1d4ed8`): `--color-primary-700`, pressed/active states
|
||||||
|
- **Brand Deep** (`#17437d`): `--brand-3`, deep blue for emphasis
|
||||||
|
|
||||||
|
### Text Colors
|
||||||
|
- **Near Black** (`#222222`): `--col-text00`, primary text
|
||||||
|
- **Dark** (`#18181b`): Button text, headings
|
||||||
|
- **Charcoal** (`#181e25`): Dark surface text, footer background
|
||||||
|
- **Dark Gray** (`#45515e`): `--col-text04`, secondary text
|
||||||
|
- **Mid Gray** (`#8e8e93`): Tertiary text, muted labels
|
||||||
|
- **Light Gray** (`#5f5f5f`): `--brand-2`, helper text
|
||||||
|
|
||||||
|
### Surface & Background
|
||||||
|
- **Pure White** (`#ffffff`): `--col-bg13`, primary background
|
||||||
|
- **Light Gray** (`#f0f0f0`): Secondary button backgrounds
|
||||||
|
- **Glass White** (`hsla(0, 0%, 100%, 0.4)`): `--fill-bg-white`, frosted glass overlay
|
||||||
|
- **Border Light** (`#f2f3f5`): Subtle section dividers
|
||||||
|
- **Border Gray** (`#e5e7eb`): Component borders
|
||||||
|
|
||||||
|
### Semantic
|
||||||
|
- **Success Background** (`#e8ffea`): `--success-bg`, positive state backgrounds
|
||||||
|
|
||||||
|
### Shadows
|
||||||
|
- **Standard** (`rgba(0, 0, 0, 0.08) 0px 4px 6px`): Default card shadow
|
||||||
|
- **Soft Glow** (`rgba(0, 0, 0, 0.08) 0px 0px 22.576px`): Ambient soft shadow
|
||||||
|
- **Brand Purple** (`rgba(44, 30, 116, 0.16) 0px 0px 15px`): Brand-tinted glow
|
||||||
|
- **Brand Purple Offset** (`rgba(44, 30, 116, 0.11) 6.5px 2px 17.5px`): Directional brand glow
|
||||||
|
- **Card Elevation** (`rgba(36, 36, 36, 0.08) 0px 12px 16px -4px`): Lifted card shadow
|
||||||
|
|
||||||
|
## 3. Typography Rules
|
||||||
|
|
||||||
|
### Font Families
|
||||||
|
- **Primary UI**: `DM Sans`, with fallbacks: `Helvetica Neue, Helvetica, Arial`
|
||||||
|
- **Display**: `Outfit`, with fallbacks: `Helvetica Neue, Helvetica, Arial`
|
||||||
|
- **Mid-tier**: `Poppins`
|
||||||
|
- **Data/Technical**: `Roboto`, with fallbacks: `Helvetica Neue, Helvetica, Arial`
|
||||||
|
|
||||||
|
### Hierarchy
|
||||||
|
|
||||||
|
| Role | Font | Size | Weight | Line Height | Notes |
|
||||||
|
|------|------|------|--------|-------------|-------|
|
||||||
|
| Display Hero | DM Sans / Outfit | 80px (5.00rem) | 500 | 1.10 (tight) | Hero headlines |
|
||||||
|
| Section Heading | Outfit | 31px (1.94rem) | 600 | 1.50 | Feature section titles |
|
||||||
|
| Section Heading Alt | Roboto / DM Sans | 32px (2.00rem) | 600 | 0.88 (tight) | Compact headers |
|
||||||
|
| Card Title | Outfit | 28px (1.75rem) | 500–600 | 1.71 (relaxed) | Product card headings |
|
||||||
|
| Sub-heading | Poppins | 24px (1.50rem) | 500 | 1.50 | Mid-tier headings |
|
||||||
|
| Feature Label | Poppins | 18px (1.13rem) | 500 | 1.50 | Feature names |
|
||||||
|
| Body Large | DM Sans | 20px (1.25rem) | 500 | 1.50 | Emphasized body |
|
||||||
|
| Body | DM Sans | 16px (1.00rem) | 400–500 | 1.50 | Standard body text |
|
||||||
|
| Body Bold | DM Sans | 16px (1.00rem) | 700 | 1.50 | Strong emphasis |
|
||||||
|
| Nav/Link | DM Sans | 14px (0.88rem) | 400–500 | 1.50 | Navigation, links |
|
||||||
|
| Button Small | DM Sans | 13px (0.81rem) | 600 | 1.50 | Compact buttons |
|
||||||
|
| Caption | DM Sans / Poppins | 13px (0.81rem) | 400 | 1.70 (relaxed) | Metadata |
|
||||||
|
| Small Label | DM Sans | 12px (0.75rem) | 500–600 | 1.25–1.50 | Tags, badges |
|
||||||
|
| Micro | DM Sans / Outfit | 10px (0.63rem) | 400–500 | 1.50–1.80 | Tiny annotations |
|
||||||
|
|
||||||
|
### Principles
|
||||||
|
- **Multi-font purpose**: DM Sans = UI workhorse (body, nav, buttons); Outfit = geometric display (headings, product names); Poppins = friendly mid-tier (sub-headings, features); Roboto = technical/data contexts.
|
||||||
|
- **Universal 1.50 line-height**: The overwhelming majority of text uses 1.50 line-height, creating a consistent reading rhythm regardless of font or size. Exceptions: display (1.10 tight) and some captions (1.70 relaxed).
|
||||||
|
- **Weight 500 as default emphasis**: Most headings use 500 (medium) rather than bold, creating a modern, approachable tone. 600 for section titles, 700 reserved for strong emphasis.
|
||||||
|
- **Compact hierarchy**: The size scale jumps from 80px display straight to 28–32px section, then 16–20px body — a deliberate compression that keeps the visual hierarchy feeling efficient.
|
||||||
|
|
||||||
|
## 4. Component Stylings
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
|
||||||
|
**Pill Primary Dark**
|
||||||
|
- Background: `#181e25`
|
||||||
|
- Text: `#ffffff`
|
||||||
|
- Padding: 11px 20px
|
||||||
|
- Radius: 8px
|
||||||
|
- Use: Primary CTA ("Get Started", "Learn More")
|
||||||
|
|
||||||
|
**Pill Nav**
|
||||||
|
- Background: `rgba(0, 0, 0, 0.05)` (subtle tint)
|
||||||
|
- Text: `#18181b`
|
||||||
|
- Radius: 9999px (full pill)
|
||||||
|
- Use: Navigation tabs, filter toggles
|
||||||
|
|
||||||
|
**Pill White**
|
||||||
|
- Background: `#ffffff`
|
||||||
|
- Text: `rgba(24, 30, 37, 0.8)`
|
||||||
|
- Radius: 9999px
|
||||||
|
- Opacity: 0.5 (default state)
|
||||||
|
- Use: Secondary nav, inactive tabs
|
||||||
|
|
||||||
|
**Secondary Light**
|
||||||
|
- Background: `#f0f0f0`
|
||||||
|
- Text: `#333333`
|
||||||
|
- Padding: 11px 20px
|
||||||
|
- Radius: 8px
|
||||||
|
- Use: Secondary actions
|
||||||
|
|
||||||
|
### Product Cards
|
||||||
|
- Background: Vibrant gradients (pink/purple/orange/blue)
|
||||||
|
- Radius: 20px–24px (generous rounding)
|
||||||
|
- Shadow: `rgba(44, 30, 116, 0.16) 0px 0px 15px` (brand purple glow)
|
||||||
|
- Content: Product name, model version, descriptive text
|
||||||
|
- Each card has its own color palette matching the product identity
|
||||||
|
|
||||||
|
### AI Product Cards (Matrix)
|
||||||
|
- Background: white with subtle shadow
|
||||||
|
- Radius: 13px–16px
|
||||||
|
- Shadow: `rgba(0, 0, 0, 0.08) 0px 4px 6px`
|
||||||
|
- Icon/illustration centered above product name
|
||||||
|
- Product name in DM Sans 14–16px weight 500
|
||||||
|
|
||||||
|
### Links
|
||||||
|
- **Primary**: `#18181b` or `#181e25`, underline on dark text
|
||||||
|
- **Secondary**: `#8e8e93`, muted for less emphasis
|
||||||
|
- **On Dark**: `rgba(255, 255, 255, 0.8)` for footer and dark sections
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
- Clean horizontal nav on white background
|
||||||
|
- MiniMax logo left-aligned (red accent in logo)
|
||||||
|
- DM Sans 14px weight 500 for nav items
|
||||||
|
- Pill-shaped active indicators (9999px radius)
|
||||||
|
- "Login" text link, minimal right-side actions
|
||||||
|
- Sticky header behavior
|
||||||
|
|
||||||
|
## 5. Layout Principles
|
||||||
|
|
||||||
|
### Spacing System
|
||||||
|
- Base unit: 8px
|
||||||
|
- Scale: 1px, 2px, 4px, 6px, 8px, 10px, 11px, 14px, 16px, 24px, 32px, 40px, 50px, 64px, 80px
|
||||||
|
|
||||||
|
### Grid & Container
|
||||||
|
- Max content width centered on page
|
||||||
|
- Product card grids: horizontal scroll or 3–4 column layout
|
||||||
|
- Full-width white sections with contained content
|
||||||
|
- Dark footer at full-width
|
||||||
|
|
||||||
|
### Breakpoints
|
||||||
|
| Name | Width | Key Changes |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| Mobile | <768px | Single column, stacked cards |
|
||||||
|
| Tablet | 768–1024px | 2-column grids |
|
||||||
|
| Desktop | >1024px | Full layout, horizontal card scrolls |
|
||||||
|
|
||||||
|
### Whitespace Philosophy
|
||||||
|
- **Gallery spacing**: Products are presented like gallery items with generous white space between cards, letting each AI model breathe as its own showcase.
|
||||||
|
- **Section rhythm**: Large vertical gaps (64px–80px) between major sections create distinct "chapters" of content.
|
||||||
|
- **Card breathing**: Product cards use internal padding of 16px–24px with ample whitespace around text.
|
||||||
|
|
||||||
|
### Border Radius Scale
|
||||||
|
- Minimal (4px): Small tags, micro badges
|
||||||
|
- Standard (8px): Buttons, small cards
|
||||||
|
- Comfortable (11px–13px): Medium cards, panels
|
||||||
|
- Generous (16px–20px): Large product cards
|
||||||
|
- Large (22px–24px): Hero product cards, major containers
|
||||||
|
- Pill (30px–32px): Badge pills, rounded panels
|
||||||
|
- Full (9999px): Buttons, nav tabs
|
||||||
|
|
||||||
|
## 6. Depth & Elevation
|
||||||
|
|
||||||
|
| Level | Treatment | Use |
|
||||||
|
|-------|-----------|-----|
|
||||||
|
| Flat (Level 0) | No shadow | White background, text blocks |
|
||||||
|
| Subtle (Level 1) | `rgba(0, 0, 0, 0.08) 0px 4px 6px` | Standard cards, containers |
|
||||||
|
| Ambient (Level 2) | `rgba(0, 0, 0, 0.08) 0px 0px 22.576px` | Soft glow around elements |
|
||||||
|
| Brand Glow (Level 3) | `rgba(44, 30, 116, 0.16) 0px 0px 15px` | Featured product cards |
|
||||||
|
| Elevated (Level 4) | `rgba(36, 36, 36, 0.08) 0px 12px 16px -4px` | Lifted cards, hover states |
|
||||||
|
|
||||||
|
**Shadow Philosophy**: MiniMax uses a distinctive purple-tinted shadow (`rgba(44, 30, 116, ...)`) for featured elements, creating a subtle brand-color glow that connects the shadow system to the blue brand identity. Standard shadows use neutral black but at low opacity (0.08), keeping everything feeling light and airy. The directional shadow variant (6.5px offset) adds dimensional interest to hero product cards.
|
||||||
|
|
||||||
|
## 7. Do's and Don'ts
|
||||||
|
|
||||||
|
### Do
|
||||||
|
- Use white as the dominant background — let product cards provide the color
|
||||||
|
- Apply pill radius (9999px) for navigation tabs and toggle buttons
|
||||||
|
- Use generous border radius (20px–24px) for product showcase cards
|
||||||
|
- Employ the purple-tinted shadow for featured/hero product cards
|
||||||
|
- Keep body text at DM Sans weight 400–500 — heavier weights for buttons only
|
||||||
|
- Use Outfit for display headings, DM Sans for everything functional
|
||||||
|
- Maintain the universal 1.50 line-height across body text
|
||||||
|
- Let colorful product illustrations/gradients serve as the primary visual interest
|
||||||
|
|
||||||
|
### Don't
|
||||||
|
- Don't add colored backgrounds to main content sections — white is structural
|
||||||
|
- Don't use sharp corners (0–4px radius) on product cards — the rounded aesthetic is core
|
||||||
|
- Don't apply the brand pink (`#ea5ec1`) to text or buttons — it's for logo and decorative accents only
|
||||||
|
- Don't mix more than one display font per section (Outfit OR Poppins, not both)
|
||||||
|
- Don't use weight 700 for headings — 500–600 is the range, 700 is reserved for strong emphasis in body text
|
||||||
|
- Don't darken shadows beyond 0.16 opacity — the light, airy feel requires restraint
|
||||||
|
- Don't use Roboto for headings — it's the data/technical context font only
|
||||||
|
|
||||||
|
## 8. Responsive Behavior
|
||||||
|
|
||||||
|
### Breakpoints
|
||||||
|
| Name | Width | Key Changes |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| Mobile | <768px | Single column, stacked product cards, hamburger nav |
|
||||||
|
| Tablet | 768–1024px | 2-column product grids, condensed spacing |
|
||||||
|
| Desktop | >1024px | Full horizontal card layouts, expanded spacing |
|
||||||
|
|
||||||
|
### Collapsing Strategy
|
||||||
|
- Hero: 80px → responsive scaling to ~40px on mobile
|
||||||
|
- Product card grid: horizontal scroll → 2-column → single column stacked
|
||||||
|
- Navigation: horizontal → hamburger menu
|
||||||
|
- Footer: multi-column → stacked sections
|
||||||
|
- Spacing: 64–80px gaps → 32–40px on mobile
|
||||||
|
|
||||||
|
## 9. Agent Prompt Guide
|
||||||
|
|
||||||
|
### Quick Color Reference
|
||||||
|
- Background: `#ffffff` (primary), `#181e25` (dark/footer)
|
||||||
|
- Text: `#222222` (primary), `#45515e` (secondary), `#8e8e93` (muted)
|
||||||
|
- Brand Blue: `#1456f0` (brand), `#3b82f6` (primary-500), `#2563eb` (hover)
|
||||||
|
- Brand Pink: `#ea5ec1` (accent only)
|
||||||
|
- Borders: `#e5e7eb`, `#f2f3f5`
|
||||||
|
|
||||||
|
### Example Component Prompts
|
||||||
|
- "Create a hero section on white background. Headline at 80px Outfit weight 500, line-height 1.10, near-black (#222222) text. Sub-text at 16px DM Sans weight 400, line-height 1.50, #45515e. Dark CTA button (#181e25, 8px radius, 11px 20px padding, white text)."
|
||||||
|
- "Design a product card grid: white cards with 20px border-radius, shadow rgba(44,30,116,0.16) 0px 0px 15px. Product name at 28px Outfit weight 600. Internal gradient background for the product illustration area."
|
||||||
|
- "Build navigation bar: white background, DM Sans 14px weight 500 for links, #18181b text. Pill-shaped active tab (9999px radius, rgba(0,0,0,0.05) background). MiniMax logo left-aligned."
|
||||||
|
- "Create an AI product matrix: 4-column grid of cards with 13px radius, subtle shadow rgba(0,0,0,0.08) 0px 4px 6px. Centered icon above product name in DM Sans 16px weight 500."
|
||||||
|
- "Design footer on dark (#181e25) background. Product links in DM Sans 14px, rgba(255,255,255,0.8). Multi-column layout."
|
||||||
|
|
||||||
|
### Iteration Guide
|
||||||
|
1. Start with white — color comes from product cards and illustrations only
|
||||||
|
2. Pill buttons (9999px) for nav/tabs, standard radius (8px) for CTA buttons
|
||||||
|
3. Purple-tinted shadows for featured cards, neutral shadows for everything else
|
||||||
|
4. DM Sans handles 70% of text — Outfit is display-only, Poppins is mid-tier only
|
||||||
|
5. Keep weights moderate (500–600 for headings) — the brand tone is confident but approachable
|
||||||
|
6. Large radius cards (20–24px) for products, smaller radius (8–13px) for UI elements
|
||||||
1267
refrence/preview-dark.html
Normal file
1267
refrence/preview-dark.html
Normal file
File diff suppressed because it is too large
Load Diff
1253
refrence/preview.html
Normal file
1253
refrence/preview.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user