style: optimize admin layout, mobile sticky glass nav, collapsible table cards, homepage search spotlight capsule, and mobile navigation cards

This commit is contained in:
nianzhibai
2026-05-21 00:08:00 +08:00
parent d4acc028e5
commit 014b993227
10 changed files with 436 additions and 123 deletions
+4
View File
@@ -6,6 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="视频聚合站首页 Demo" />
<title>视频聚合站</title>
<!-- Premium Fonts Preconnect & Links -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
+4 -4
View File
@@ -101,12 +101,12 @@ export function TagsPage() {
<tbody>
{tags.map((tag) => (
<tr key={tag.id}>
<td>
<td data-label="标签">
<span className="admin-pill">{tag.label}</span>
</td>
<td>{tag.count}</td>
<td>{sourceLabel(tag.source)}</td>
<td>
<td data-label="视频数">{tag.count}</td>
<td data-label="来源">{sourceLabel(tag.source)}</td>
<td data-label="别名">
{(tag.aliases ?? []).length > 0 ? (
<div className="admin-pills">
{(tag.aliases ?? []).map((alias) => (
+7 -7
View File
@@ -166,7 +166,7 @@ export function VideosPage() {
<tbody>
{filtered.map((v) => (
<tr key={v.id}>
<td>
<td data-label="标题">
<div className="admin-video-title">{v.title}</div>
{fileMeta(v) && (
<div className="admin-video-filemeta">
@@ -174,8 +174,8 @@ export function VideosPage() {
</div>
)}
</td>
<td>{v.author || <span className="admin-text-faint"></span>}</td>
<td>
<td data-label="作者">{v.author || <span className="admin-text-faint"></span>}</td>
<td data-label="标签">
<div className="admin-pills">
{(v.tags ?? []).map((t) => (
<span key={t} className="admin-pill">
@@ -184,14 +184,14 @@ export function VideosPage() {
))}
</div>
</td>
<td>{formatDur(v.durationSeconds)}</td>
<td>
<td data-label="时长">{formatDur(v.durationSeconds)}</td>
<td data-label="Teaser">
<PreviewStatus s={v.previewStatus} />
</td>
<td className="admin-mono-cell">
<td data-label="来源" className="admin-mono-cell">
{driveNameMap.get(v.driveId) ?? v.driveId}
</td>
<td className="is-actions">
<td className="is-actions" data-label="操作">
<button className="admin-btn" onClick={() => setEditing(v)}>
<Edit size={13} />
</button>{" "}
+13 -10
View File
@@ -18,17 +18,20 @@ export function SearchPanel() {
return (
<form className="search-panel" onSubmit={handleSubmit} role="search">
<div className="search-panel__form">
<input
className="search-panel__input"
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="搜索视频标题或作者"
aria-label="搜索关键词"
/>
<div className="search-panel__input-wrapper">
<Search size={16} className="search-panel__search-icon" />
<input
className="search-panel__input"
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="搜索视频标题或作者..."
aria-label="搜索关键词"
/>
</div>
<button className="search-panel__submit" type="submit">
<Search size={16} />
<Search size={16} className="search-panel__submit-icon" />
<span className="search-panel__submit-text"></span>
</button>
</div>
</form>
+29 -17
View File
@@ -19,24 +19,36 @@ export function TagCloud() {
if (tags.length === 0) return null;
// 将标签分为奇偶两行,使其横向自由流式排布,不发生强制的列对齐
const row1 = tags.filter((_, idx) => idx % 2 === 0);
const row2 = tags.filter((_, idx) => idx % 2 !== 0);
const renderTag = (tag: TagItem) => (
<Link
key={tag.id}
to={`/list?tag=${encodeURIComponent(tag.label)}`}
className={`tag-chip ${activeTag === tag.label ? "is-active" : ""}`}
title={
typeof tag.count === "number" ? `${tag.count} 个视频` : undefined
}
>
{tag.label}
{typeof tag.count === "number" && tag.count > 0 && (
<span style={{ marginLeft: 4, opacity: 0.7 }}>({tag.count})</span>
)}
</Link>
);
return (
<div className="tag-cloud" aria-label="热门分类">
<span className="tag-cloud__label"></span>
{tags.map((tag) => (
<Link
key={tag.id}
to={`/list?tag=${encodeURIComponent(tag.label)}`}
className={`tag-chip ${activeTag === tag.label ? "is-active" : ""}`}
title={
typeof tag.count === "number" ? `${tag.count} 个视频` : undefined
}
>
{tag.label}
{typeof tag.count === "number" && tag.count > 0 && (
<span style={{ marginLeft: 4, opacity: 0.7 }}>({tag.count})</span>
)}
</Link>
))}
<div className="tag-cloud-container" aria-label="热门分类">
<div className="tag-cloud__grid">
<div className="tag-cloud__row">
{row1.map(renderTag)}
</div>
<div className="tag-cloud__row">
{row2.map(renderTag)}
</div>
</div>
</div>
);
}
+110 -17
View File
@@ -48,6 +48,18 @@
background: var(--accent-gradient);
color: var(--text-on-accent);
box-shadow: 0 4px 14px var(--accent-glow), var(--shadow-inset);
animation: admin-brand-pulse 3s infinite ease-in-out;
}
@keyframes admin-brand-pulse {
0%, 100% {
box-shadow: 0 4px 12px var(--accent-glow), var(--shadow-inset);
transform: scale(1);
}
50% {
box-shadow: 0 6px 20px rgba(255, 138, 60, 0.45), var(--shadow-inset);
transform: scale(1.04);
}
}
/* ----- Nav ----- */
@@ -68,12 +80,13 @@
color: var(--text-muted);
font-size: var(--font-md);
font-weight: var(--weight-medium);
transition: color var(--transition-fast), background var(--transition-fast);
transition: color var(--transition-fast), background var(--transition-fast), transform var(--transition-fast);
}
.admin-nav__link:hover {
color: var(--text-strong);
background: rgba(255, 255, 255, 0.04);
transform: translateX(4px);
}
.admin-nav__link.is-active {
@@ -241,7 +254,7 @@
.admin-form__row textarea:focus {
outline: none;
border-color: var(--border-accent);
box-shadow: 0 0 0 3px var(--accent-soft);
box-shadow: 0 0 0 3px var(--accent-soft), 0 0 12px rgba(255, 138, 60, 0.15);
}
.admin-form__row textarea {
@@ -298,10 +311,11 @@
background: var(--bg-surface);
border-color: var(--border-strong);
color: var(--text-strong);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.admin-btn:active:not(:disabled) {
transform: translateY(1px);
transform: scale(0.97);
}
.admin-btn.is-primary {
@@ -935,16 +949,22 @@
}
.admin-sidebar {
position: static;
position: sticky;
top: 0;
height: auto;
flex-direction: row;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
padding: var(--space-2);
padding: var(--space-2) var(--space-3);
background: var(--glass-nav);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-right: 0;
border-bottom: 1px solid var(--border-subtle);
z-index: var(--z-nav);
scrollbar-width: none;
box-shadow: var(--shadow-sm);
}
.admin-sidebar::-webkit-scrollbar { display: none; }
@@ -957,24 +977,38 @@
width: max-content;
margin: 0;
padding: 0;
gap: 4px;
gap: var(--space-2);
}
.admin-nav__link {
height: 36px;
height: 34px;
padding: 0 var(--space-3);
border-radius: var(--radius-pill);
white-space: nowrap;
font-size: var(--font-sm);
border: 1px solid transparent;
}
.admin-nav__link:hover {
background: rgba(255, 255, 255, 0.05);
transform: none;
}
.admin-nav__link.is-active {
color: var(--text-strong);
background: var(--accent-soft);
border-color: var(--border-accent);
box-shadow: 0 2px 8px var(--accent-glow);
}
.admin-nav__link.is-active::before { display: none; }
.admin-main {
padding: var(--space-4);
padding: var(--space-4) var(--space-3);
}
.admin-page__title {
font-size: var(--font-2xl);
font-size: var(--font-xl);
}
.admin-page__header > div {
@@ -1007,25 +1041,84 @@
width: 100%;
}
/* Table card-grid conversion for videos & tags tables */
.admin-table:not(.admin-drives-table) {
display: block;
max-width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
overflow: visible;
background: transparent;
border: 0;
}
.admin-table:not(.admin-drives-table) thead {
display: none;
}
.admin-table:not(.admin-drives-table) tbody {
display: grid;
gap: var(--space-3);
}
.admin-table:not(.admin-drives-table) tr {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-3) var(--space-4);
padding: var(--space-4);
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
transition: border-color var(--transition-fast), transform var(--transition-fast);
}
.admin-table:not(.admin-drives-table) tr:hover {
border-color: var(--border-strong);
}
.admin-table:not(.admin-drives-table) tr:hover td {
background: transparent;
}
.admin-table:not(.admin-drives-table) th,
.admin-table:not(.admin-drives-table) td {
white-space: nowrap;
display: grid;
align-content: start;
gap: 4px;
min-width: 0;
padding: 0;
border-bottom: 0;
white-space: normal;
overflow-wrap: anywhere;
}
.admin-table:not(.admin-drives-table) td::before {
content: attr(data-label);
color: var(--text-faint);
font-size: var(--font-xs);
font-weight: var(--weight-semibold);
letter-spacing: 0.05em;
text-transform: uppercase;
}
/* Span main fields to full row width */
.admin-table:not(.admin-drives-table) td[data-label="标题"],
.admin-table:not(.admin-drives-table) td[data-label="标签"] {
grid-column: 1 / -1;
}
.admin-table:not(.admin-drives-table) td.is-actions {
min-width: 180px;
grid-column: 1 / -1;
flex-wrap: wrap;
display: flex;
gap: 6px;
text-align: left;
white-space: normal;
}
.admin-table td.is-actions .admin-btn {
margin-bottom: 4px;
.admin-table:not(.admin-drives-table) td.is-actions::before {
flex-basis: 100%;
}
.admin-table:not(.admin-drives-table) td.is-actions .admin-btn + .admin-btn {
margin-left: 0;
}
.admin-modal-backdrop {
+1 -1
View File
@@ -33,7 +33,7 @@ html {
body {
font-family:
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Plus Jakarta Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
font-size: var(--font-base);
font-weight: var(--weight-regular);
+45 -15
View File
@@ -32,10 +32,17 @@
font-size: var(--font-lg);
letter-spacing: 0.01em;
color: var(--text-strong);
transition: transform var(--transition-fast);
}
.main-nav__logo:hover {
color: var(--text-strong);
transform: scale(1.02);
}
.main-nav__logo:hover .main-nav__logo-mark {
transform: rotate(6deg) scale(1.05);
box-shadow: 0 6px 20px rgba(255, 138, 60, 0.45), var(--shadow-inset);
}
.main-nav__logo-mark {
@@ -52,6 +59,7 @@
position: relative;
isolation: isolate;
overflow: hidden;
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.main-nav__logo-mark::after {
@@ -110,19 +118,25 @@
/* ----- 汉堡按钮 ----- */
.main-nav__toggle {
display: none;
width: 40px;
height: 40px;
border-radius: var(--radius-sm);
width: 38px;
height: 38px;
border-radius: 50%;
color: var(--text-strong);
background: rgba(255, 255, 255, 0.04);
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-subtle);
align-items: center;
justify-content: center;
transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
}
.main-nav__toggle:hover {
background: rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.09);
border-color: var(--border-default);
transform: scale(1.04);
}
.main-nav__toggle:active {
transform: scale(0.96);
}
/* ----- 子导航(一般为空,留作扩展) ----- */
@@ -180,28 +194,44 @@
margin-left: auto;
}
/* Mobile floating glassmorphism card menu */
.main-nav.is-open .main-nav__list {
display: flex;
position: absolute;
top: 56px;
left: 0;
right: 0;
top: 64px;
left: var(--space-3);
right: var(--space-3);
flex-direction: column;
align-items: stretch;
gap: 2px;
padding: var(--space-2);
background: var(--bg-surface);
border-top: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--border-subtle);
box-shadow: var(--shadow-lg);
gap: 4px;
padding: var(--space-3);
background: var(--glass-nav);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--border-strong);
border-radius: var(--radius-md);
box-shadow: var(--shadow-xl);
animation: mobile-nav-slide-down var(--transition-fast) var(--ease-out);
}
@keyframes mobile-nav-slide-down {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.main-nav.is-open .main-nav__link {
width: 100%;
height: 44px;
height: 42px;
padding: 0 var(--space-4);
border-radius: var(--radius-sm);
justify-content: flex-start;
font-size: var(--font-base);
}
.main-nav.is-open .main-nav__link.is-active::after {
+103 -33
View File
@@ -2,7 +2,7 @@
* Search / Tag cloud / Sort toolbar
* ========================================================= */
.search-panel {
margin-top: var(--space-4);
margin-top: var(--space-5);
}
.search-panel__form {
@@ -10,25 +10,47 @@
gap: var(--space-2);
flex-wrap: nowrap;
padding: 6px;
background: var(--bg-surface);
background: var(--glass-card);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
border-radius: var(--radius-xl);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4), var(--shadow-inset);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast);
}
.search-panel__form:focus-within {
border-color: var(--border-accent);
box-shadow: var(--shadow-glow);
box-shadow: 0 0 0 1px var(--border-accent), 0 8px 24px var(--accent-glow);
transform: translateY(-1px);
}
.search-panel__input-wrapper {
position: relative;
flex: 1 1 auto;
display: flex;
align-items: center;
min-width: 0;
}
.search-panel__search-icon {
position: absolute;
left: 14px;
color: var(--text-faint);
pointer-events: none;
transition: color var(--transition-fast);
}
.search-panel__form:focus-within .search-panel__search-icon {
color: var(--accent);
}
.search-panel__input {
flex: 1 1 auto;
min-width: 0;
height: 42px;
padding: 0 var(--space-4);
padding: 0 var(--space-4) 0 38px;
border: 0;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-strong);
font-size: var(--font-md);
@@ -48,40 +70,61 @@
padding: 0 var(--space-5);
background: var(--accent-gradient);
color: var(--text-on-accent);
border-radius: var(--radius-sm);
border-radius: var(--radius-lg);
font-weight: var(--weight-semibold);
font-size: var(--font-md);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
box-shadow: 0 6px 16px var(--accent-glow), var(--shadow-inset);
box-shadow: 0 4px 12px var(--accent-glow), var(--shadow-inset);
transition: background var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
}
.search-panel__submit:hover {
filter: brightness(1.05);
transform: translateY(-1px);
box-shadow: 0 10px 22px var(--accent-glow), var(--shadow-inset);
box-shadow: 0 8px 20px var(--accent-glow), var(--shadow-inset);
}
.search-panel__submit:active {
transform: translateY(0);
transform: scale(0.97);
}
.search-panel__submit-icon {
display: none;
}
/* ----- Tag cloud ----- */
.tag-cloud {
.tag-cloud-container {
margin-top: var(--space-4);
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
width: 100%;
position: relative;
/* Premium horizontal fade mask */
mask-image: linear-gradient(to right, black 0%, black 93%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, black 0%, black 93%, transparent 100%);
}
.tag-cloud__label {
font-size: var(--font-sm);
color: var(--text-faint);
margin-right: 2px;
flex: 0 0 auto;
.tag-cloud__grid {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
overflow-x: auto;
scrollbar-width: none; /* Hide scrollbar for Firefox */
-webkit-overflow-scrolling: touch;
padding-bottom: 4px;
}
.tag-cloud__grid::-webkit-scrollbar {
display: none; /* Hide scrollbar for Chrome/Safari */
}
.tag-cloud__row {
display: flex;
gap: var(--space-2);
width: max-content;
}
.tag-chip {
@@ -179,33 +222,60 @@
.search-panel__form {
flex-wrap: nowrap;
gap: 6px;
padding: 6px;
padding: 4px;
border-radius: var(--radius-xl);
}
.search-panel__input-wrapper {
height: 40px;
}
.search-panel__search-icon {
left: 12px;
}
.search-panel__input {
flex: 1 1 auto;
min-width: 0;
height: 40px;
padding: 0 var(--space-3) 0 34px;
font-size: var(--font-base);
}
.search-panel__submit {
flex: 0 0 auto;
flex: 0 0 40px;
width: 40px;
height: 40px;
padding: 0 var(--space-4);
padding: 0;
border-radius: 50%;
}
.tag-cloud {
overflow-x: auto;
flex-wrap: nowrap;
.search-panel__submit-text {
display: none;
}
.search-panel__submit-icon {
display: block;
}
.tag-cloud-container {
margin-top: var(--space-3);
-webkit-overflow-scrolling: touch;
padding-bottom: 4px;
/* Mobile-friendly fade mask */
mask-image: linear-gradient(to right, black 0%, black 90%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, black 0%, black 90%, transparent 100%);
}
.tag-cloud::-webkit-scrollbar { display: none; }
.tag-cloud__grid {
gap: 6px;
}
.tag-cloud__row {
gap: 6px;
}
.tag-chip {
flex-shrink: 0;
height: 28px;
font-size: var(--font-xs);
padding: 0 10px;
}
.sort-toolbar {
+120 -19
View File
@@ -111,6 +111,34 @@
isolation: isolate;
}
/* Hover Shimmer Sweep Animation */
.thumb-frame::before {
content: "";
position: absolute;
top: 0;
left: -150%;
width: 50%;
height: 100%;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.16) 50%,
transparent 100%
);
transform: skewX(-25deg);
z-index: 3;
pointer-events: none;
}
.video-card:hover .thumb-frame::before {
animation: shine-sweep var(--duration-slow) var(--ease-out) forwards;
}
@keyframes shine-sweep {
0% { left: -150%; }
100% { left: 150%; }
}
.thumb-image,
.preview-video {
position: absolute;
@@ -166,6 +194,7 @@
border-radius: 6px;
background: rgba(0, 0, 0, 0.78);
color: #fff;
font-family: "Outfit", monospace;
font-size: var(--font-xs);
font-weight: var(--weight-semibold);
letter-spacing: 0.02em;
@@ -212,12 +241,20 @@
height: 22px;
padding: 0 8px;
border-radius: var(--radius-pill);
background: rgba(0, 0, 0, 0.6);
color: #fff;
background: var(--drive-bg, rgba(20, 22, 28, 0.8));
color: var(--drive-text, #fff);
font-size: var(--font-xs);
font-weight: var(--weight-medium);
backdrop-filter: blur(6px);
border: 1px solid rgba(255, 255, 255, 0.08);
font-weight: var(--weight-semibold);
backdrop-filter: blur(8px);
border: 1px solid var(--drive-border, rgba(255, 255, 255, 0.09));
box-shadow: 0 4px 12px var(--drive-shadow, rgba(0, 0, 0, 0.3)), inset 0 1px 0 rgba(255, 255, 255, 0.05);
transition: transform var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.source-badge:hover {
transform: translateY(-1px) scale(1.04);
border-color: var(--drive-color);
box-shadow: 0 0 12px var(--drive-shadow-strong, var(--accent-glow));
}
.source-badge::before {
@@ -225,15 +262,50 @@
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--source-color, var(--accent));
box-shadow: 0 0 6px var(--source-color, var(--accent));
background: var(--drive-color, var(--accent));
box-shadow: 0 0 6px var(--drive-color, var(--accent));
}
.source-badge[data-kind="quark"] { --source-color: var(--drive-quark); }
.source-badge[data-kind="p115"] { --source-color: var(--drive-p115); }
.source-badge[data-kind="pikpak"] { --source-color: var(--drive-pikpak); }
.source-badge[data-kind="wopan"] { --source-color: var(--drive-wopan); }
.source-badge[data-kind="onedrive"] { --source-color: var(--drive-onedrive); }
.source-badge[data-kind="quark"] {
--drive-color: var(--drive-quark);
--drive-bg: rgba(91, 141, 239, 0.12);
--drive-text: #a4c5ff;
--drive-border: rgba(91, 141, 239, 0.35);
--drive-shadow: rgba(91, 141, 239, 0.15);
--drive-shadow-strong: rgba(91, 141, 239, 0.4);
}
.source-badge[data-kind="p115"] {
--drive-color: var(--drive-p115);
--drive-bg: rgba(245, 107, 118, 0.12);
--drive-text: #ffb1b7;
--drive-border: rgba(245, 107, 118, 0.35);
--drive-shadow: rgba(245, 107, 118, 0.15);
--drive-shadow-strong: rgba(245, 107, 118, 0.4);
}
.source-badge[data-kind="pikpak"] {
--drive-color: var(--drive-pikpak);
--drive-bg: rgba(138, 109, 255, 0.12);
--drive-text: #cabaff;
--drive-border: rgba(138, 109, 255, 0.35);
--drive-shadow: rgba(138, 109, 255, 0.15);
--drive-shadow-strong: rgba(138, 109, 255, 0.4);
}
.source-badge[data-kind="wopan"] {
--drive-color: var(--drive-wopan);
--drive-bg: rgba(255, 138, 60, 0.12);
--drive-text: #ffc49e;
--drive-border: rgba(255, 138, 60, 0.35);
--drive-shadow: rgba(255, 138, 60, 0.15);
--drive-shadow-strong: rgba(255, 138, 60, 0.4);
}
.source-badge[data-kind="onedrive"] {
--drive-color: var(--drive-onedrive);
--drive-bg: rgba(76, 171, 234, 0.12);
--drive-text: #abd9ff;
--drive-border: rgba(76, 171, 234, 0.35);
--drive-shadow: rgba(76, 171, 234, 0.15);
--drive-shadow-strong: rgba(76, 171, 234, 0.4);
}
/* ----- Preview overlay ----- */
.preview-loader {
@@ -391,22 +463,51 @@
.skeleton-card {
position: relative;
aspect-ratio: 16 / 12.5;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 8px;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Skeleton image thumbnail area */
.skeleton-card::before {
content: "";
display: block;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: var(--radius-sm);
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.03) 0%,
rgba(255, 255, 255, 0.07) 50%,
rgba(255, 255, 255, 0.03) 100%
rgba(255, 255, 255, 0.03) 25%,
rgba(255, 255, 255, 0.08) 50%,
rgba(255, 255, 255, 0.03) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.6s ease-in-out infinite;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
}
/* Skeleton text title and meta placeholders */
.skeleton-card::after {
content: "";
display: block;
margin-top: 10px;
width: 100%;
height: 28px;
background:
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 25%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.03) 75%) 0 0 / 70% 12px no-repeat,
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 25%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.03) 75%) 0 20px / 42% 8px no-repeat;
background-size: 200% 100%;
animation: skeleton-shimmer 1.6s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
from { background-position: 150% 0; }
to { background-position: -150% 0; }
}
@media (max-width: 1024px) {