style(ui): 移除吉祥物插画,前台 hero 改为 Memphis 装饰元素
- 首页 hero 去掉 Mascot,换成漂浮的蜡笔色墨边方块(float/wiggle/bounce 动画) - KPI 卡片轮流使用黄/粉/蓝/薄荷平涂色,能力卡图标、模型卡光晕统一 Memphis 调色 - 画廊跑马灯加粗黑上下边;About 页 hero 改为色块拼贴,文案不再以"薇薇"自称 - 空状态统一用旋转的 .empty-badge 黄色星标替代吉祥物 - 邀请页顶部卡片改为 ink-card + 蜡笔色统计块 + 橙色复制按钮 - 删除 Mascot.vue Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
<script setup>
|
||||
// Vivi — the platform's original chibi mascot. Pure inline SVG (no raster
|
||||
// asset, no external dependency), so it scales crisply and inherits the
|
||||
// pink/violet/sky brand palette. `size` sets the square px box; the halo of
|
||||
// twin-tails + a camera-lens hair ring ties her to the B-ring logo.
|
||||
import { computed } from 'vue'
|
||||
const props = defineProps({
|
||||
size: { type: [Number, String], default: 160 },
|
||||
float: { type: Boolean, default: true },
|
||||
uid: { type: String, default: () => 'm' + Math.random().toString(36).slice(2, 8) },
|
||||
})
|
||||
const px = computed(() => (typeof props.size === 'number' ? props.size + 'px' : props.size))
|
||||
const g = (k) => `${props.uid}-${k}`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="px" :height="px" viewBox="0 0 200 200" fill="none"
|
||||
:class="['mascot', { 'mascot-float': float }]" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient :id="g('hair')" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#f0abfc" />
|
||||
<stop offset="0.55" stop-color="#c084fc" />
|
||||
<stop offset="1" stop-color="#818cf8" />
|
||||
</linearGradient>
|
||||
<linearGradient :id="g('eye')" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#7dd3fc" />
|
||||
<stop offset="1" stop-color="#a855f7" />
|
||||
</linearGradient>
|
||||
<linearGradient :id="g('ring')" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#f472b6" />
|
||||
<stop offset="0.5" stop-color="#a78bfa" />
|
||||
<stop offset="1" stop-color="#38bdf8" />
|
||||
</linearGradient>
|
||||
<radialGradient :id="g('glow')" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#e879f9" stop-opacity="0.55" />
|
||||
<stop offset="1" stop-color="#e879f9" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- soft aura -->
|
||||
<circle cx="100" cy="104" r="86" :fill="`url(#${g('glow')})`" />
|
||||
|
||||
<!-- back twin-tail -->
|
||||
<path d="M56 78 C22 84 20 140 44 168 C58 150 60 120 74 104 Z" :fill="`url(#${g('hair')})`" opacity="0.92" />
|
||||
<path d="M144 78 C178 84 180 140 156 168 C142 150 140 120 126 104 Z" :fill="`url(#${g('hair')})`" opacity="0.92" />
|
||||
|
||||
<!-- hair back mass -->
|
||||
<path d="M100 34 C60 34 44 66 46 100 C47 116 56 128 62 132 L138 132 C144 128 153 116 154 100 C156 66 140 34 100 34 Z"
|
||||
:fill="`url(#${g('hair')})`" />
|
||||
|
||||
<!-- face -->
|
||||
<path d="M64 92 C64 66 80 52 100 52 C120 52 136 66 136 92 C136 118 120 134 100 134 C80 134 64 118 64 92 Z"
|
||||
fill="#fff5fb" />
|
||||
<!-- fringe -->
|
||||
<path d="M62 92 C60 66 78 50 100 50 C122 50 140 66 138 92 C132 78 120 74 118 90 C112 74 100 72 100 72 C100 72 88 74 82 90 C80 74 68 78 62 92 Z"
|
||||
:fill="`url(#${g('hair')})`" />
|
||||
|
||||
<!-- ears blush -->
|
||||
<circle cx="76" cy="106" r="7" fill="#fbb6ce" opacity="0.75" />
|
||||
<circle cx="124" cy="106" r="7" fill="#fbb6ce" opacity="0.75" />
|
||||
|
||||
<!-- eyes -->
|
||||
<g>
|
||||
<ellipse cx="83" cy="98" rx="9" ry="12" :fill="`url(#${g('eye')})`" />
|
||||
<ellipse cx="117" cy="98" rx="9" ry="12" :fill="`url(#${g('eye')})`" />
|
||||
<circle cx="85.5" cy="93.5" r="3.2" fill="#fff" />
|
||||
<circle cx="119.5" cy="93.5" r="3.2" fill="#fff" />
|
||||
<circle cx="81" cy="101" r="1.6" fill="#fff" opacity="0.85" />
|
||||
<circle cx="115" cy="101" r="1.6" fill="#fff" opacity="0.85" />
|
||||
</g>
|
||||
<!-- brows / smile -->
|
||||
<path d="M74 84 q9 -5 18 -1" stroke="#c084fc" stroke-width="2.4" stroke-linecap="round" />
|
||||
<path d="M108 83 q9 -4 18 1" stroke="#c084fc" stroke-width="2.4" stroke-linecap="round" />
|
||||
<path d="M92 114 q8 7 16 0" stroke="#f472b6" stroke-width="2.6" stroke-linecap="round" fill="none" />
|
||||
|
||||
<!-- camera-lens hair ring (echoes the logo) -->
|
||||
<g transform="translate(140 58)">
|
||||
<circle r="15" fill="none" :stroke="`url(#${g('ring')})`" stroke-width="4"
|
||||
stroke-linecap="round" stroke-dasharray="70 24" transform="rotate(-55)" />
|
||||
<circle r="5.5" fill="#38bdf8" />
|
||||
<circle r="2.2" fill="#fff" opacity="0.9" />
|
||||
</g>
|
||||
|
||||
<!-- sparkles -->
|
||||
<g fill="#fef08a">
|
||||
<path d="M46 60 l2.2 5 5 2.2 -5 2.2 -2.2 5 -2.2 -5 -5 -2.2 5 -2.2 Z" />
|
||||
<path d="M158 128 l1.6 3.6 3.6 1.6 -3.6 1.6 -1.6 3.6 -1.6 -3.6 -3.6 -1.6 3.6 -1.6 Z" />
|
||||
</g>
|
||||
<circle cx="52" cy="150" r="2.4" fill="#7dd3fc" />
|
||||
<circle cx="150" cy="44" r="2" fill="#f0abfc" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mascot { display: block; }
|
||||
.mascot-float { animation: mascotFloat 5s ease-in-out infinite; transform-origin: center; }
|
||||
@keyframes mascotFloat {
|
||||
0%, 100% { transform: translateY(0) rotate(-1deg); }
|
||||
50% { transform: translateY(-8px) rotate(1.5deg); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { .mascot-float { animation: none; } }
|
||||
</style>
|
||||
@@ -473,6 +473,25 @@ html.dark .memphis-dots {
|
||||
.memphis-press { transition: transform 0.15s ease; }
|
||||
.memphis-press:active { transform: scale(0.97) translateY(2px); }
|
||||
|
||||
/* Empty-state badge — a rotated crayon tile with a star, replaces the old
|
||||
mascot illustration in empty lists. */
|
||||
.empty-badge {
|
||||
width: 4.5rem;
|
||||
height: 4.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 900;
|
||||
color: #111;
|
||||
background: var(--m-yellow);
|
||||
border: 3px solid var(--ink);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 5px 5px 0 var(--shadow-ink);
|
||||
transform: rotate(-6deg);
|
||||
animation: memphis-float 6s ease-in-out infinite;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { .empty-badge { animation: none; } }
|
||||
|
||||
@keyframes memphis-wiggle { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
|
||||
@keyframes memphis-float { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-10px) rotate(1.5deg); } }
|
||||
@keyframes memphis-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
|
||||
|
||||
@@ -1,59 +1,63 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { site } from '../site'
|
||||
import Mascot from '../components/Mascot.vue'
|
||||
|
||||
const contact = computed(() => site.contact || {})
|
||||
|
||||
const powers = [
|
||||
{ icon: '🎨', title: '文字生图', desc: '一句话出图,画幅与分辨率随模型可选。', tint: 'from-fuchsia-400/20 to-pink-400/10' },
|
||||
{ icon: '🖼️', title: '参考图生图', desc: '上传参考换风格、改构图、做变体。', tint: 'from-violet-400/20 to-indigo-400/10' },
|
||||
{ icon: '🎬', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', tint: 'from-sky-400/20 to-cyan-400/10' },
|
||||
{ icon: '✨', title: '图生视频', desc: '给张参考图,让静态画面动起来。', tint: 'from-amber-400/20 to-orange-400/10' },
|
||||
{ icon: '🎨', title: '文字生图', desc: '一句话出图,画幅与分辨率随模型可选。', bg: 'var(--m-mint)' },
|
||||
{ icon: '🖼️', title: '参考图生图', desc: '上传参考换风格、改构图、做变体。', bg: 'var(--m-pink)' },
|
||||
{ icon: '🎬', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', bg: 'var(--m-blue)' },
|
||||
{ icon: '✨', title: '图生视频', desc: '给张参考图,让静态画面动起来。', bg: 'var(--m-yellow)' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-16 pb-8">
|
||||
<!-- ===== hero ===== -->
|
||||
<section class="relative overflow-hidden rounded-[2rem] anime-card px-6 py-10 md:px-12 md:py-14">
|
||||
<div class="anime-stars"></div>
|
||||
<section class="relative overflow-hidden rounded-[1.4rem] anime-card px-6 py-10 md:px-12 md:py-14">
|
||||
<div class="memphis-dots absolute inset-0 opacity-40 pointer-events-none"></div>
|
||||
<div class="relative grid md:grid-cols-[1.1fr_0.9fr] gap-8 items-center">
|
||||
<div>
|
||||
<span class="sticker">✦ 关于我们</span>
|
||||
<h1 class="mt-4 text-4xl md:text-5xl font-extrabold tracking-tight text-[color:var(--fg)]">
|
||||
嗨,我是 <span class="anime-grad-text">薇薇</span>
|
||||
<h1 class="mt-4 text-4xl md:text-5xl font-black tracking-tight text-[color:var(--fg)]">
|
||||
把创作交给 <span class="anime-grad-text">{{ site.title }}</span>
|
||||
</h1>
|
||||
<p class="text-[color:var(--fg-2)] mt-5 text-base md:text-lg leading-relaxed max-w-xl">
|
||||
{{ site.title }} 是一个聚合 GPT、Gemini、Firefly、Flux 等顶级模型的 AI 生图与生视频平台。
|
||||
把脑海里的画面写成一句话,交给我替你变成图像与视频。
|
||||
把脑海里的画面写成一句话,剩下的交给我们变成图像与视频。
|
||||
</p>
|
||||
<router-link to="/user" class="bubble-btn mt-7">开始创作 →</router-link>
|
||||
</div>
|
||||
<div class="grid place-items-center">
|
||||
<Mascot :size="240" />
|
||||
<!-- memphis tile cluster standing in for the old mascot illustration -->
|
||||
<div class="ab-cluster" aria-hidden="true">
|
||||
<span class="ab-tile ab-1">🎨</span>
|
||||
<span class="ab-tile ab-2">🖼️</span>
|
||||
<span class="ab-tile ab-3">🎬</span>
|
||||
<span class="ab-tile ab-4">✨</span>
|
||||
<span class="ab-star">✦</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== powers ===== -->
|
||||
<section>
|
||||
<div class="anime-eyebrow text-fuchsia-500 dark:text-fuchsia-300">能做什么</div>
|
||||
<h2 class="mt-3 text-2xl md:text-3xl font-extrabold tracking-tight text-[color:var(--fg)]">四种方式,把想象变成画面</h2>
|
||||
<div class="anime-eyebrow text-[var(--m-orange)]">能做什么</div>
|
||||
<h2 class="mt-3 text-2xl md:text-3xl font-black tracking-tight text-[color:var(--fg)]">四种方式,把想象变成画面</h2>
|
||||
<div class="mt-7 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<div v-for="p in powers" :key="p.title"
|
||||
class="anime-card p-5 bg-gradient-to-br" :class="p.tint">
|
||||
class="power-card p-5" :style="{ background: p.bg }">
|
||||
<div class="text-3xl">{{ p.icon }}</div>
|
||||
<div class="mt-3 text-base font-bold text-[color:var(--fg)]">{{ p.title }}</div>
|
||||
<div class="mt-1.5 text-[13px] leading-relaxed text-[color:var(--fg-3)]">{{ p.desc }}</div>
|
||||
<div class="mt-3 text-base font-extrabold text-[#111]">{{ p.title }}</div>
|
||||
<div class="mt-1.5 text-[13px] font-medium leading-relaxed text-[#111]/60">{{ p.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== contact ===== -->
|
||||
<section>
|
||||
<div class="anime-eyebrow text-sky-500 dark:text-sky-300">联系我们</div>
|
||||
<h2 class="mt-3 text-2xl md:text-3xl font-extrabold tracking-tight text-[color:var(--fg)]">有问题?来找我们玩~</h2>
|
||||
<div class="anime-eyebrow text-sky-600">联系我们</div>
|
||||
<h2 class="mt-3 text-2xl md:text-3xl font-black tracking-tight text-[color:var(--fg)]">有问题?来找我们玩~</h2>
|
||||
<p class="text-[color:var(--fg-3)] mt-2 mb-6">合作、反馈,或想加入交流群,随时招呼。</p>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-3xl">
|
||||
@@ -61,7 +65,7 @@ const powers = [
|
||||
class="group anime-card flex items-center justify-between gap-3 p-5">
|
||||
<div>
|
||||
<div class="sticker">🛍️ 商店</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 group-hover:text-fuchsia-400 transition-colors">前往商店</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 group-hover:text-[var(--m-orange)] transition-colors">前往商店</div>
|
||||
</div>
|
||||
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
|
||||
</a>
|
||||
@@ -69,14 +73,14 @@ const powers = [
|
||||
<a v-if="contact.email" :href="`mailto:${contact.email}`"
|
||||
class="group anime-card p-5">
|
||||
<div class="sticker">✉️ 邮箱</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 break-all group-hover:text-sky-400 transition-colors">{{ contact.email }}</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 break-all group-hover:text-sky-600 transition-colors">{{ contact.email }}</div>
|
||||
</a>
|
||||
|
||||
<a v-if="contact.qq && contact.qq_link" :href="contact.qq_link" target="_blank" rel="noopener"
|
||||
class="group anime-card flex items-center justify-between gap-3 p-5">
|
||||
<div>
|
||||
<div class="sticker">💬 QQ</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all group-hover:text-sky-400 transition-colors">{{ contact.qq }}</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all group-hover:text-sky-600 transition-colors">{{ contact.qq }}</div>
|
||||
</div>
|
||||
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
|
||||
</a>
|
||||
@@ -89,7 +93,7 @@ const powers = [
|
||||
class="group anime-card flex items-center justify-between gap-3 p-5">
|
||||
<div>
|
||||
<div class="sticker">🎀 QQ 群</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all group-hover:text-emerald-400 transition-colors">{{ contact.qq_group }}</div>
|
||||
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all group-hover:text-emerald-600 transition-colors">{{ contact.qq_group }}</div>
|
||||
</div>
|
||||
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
|
||||
</a>
|
||||
@@ -104,3 +108,42 @@ const powers = [
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Crayon tile cluster — a loose collage of rotated ink-outlined squares. */
|
||||
.ab-cluster {
|
||||
position: relative;
|
||||
min-height: 240px;
|
||||
}
|
||||
.ab-tile {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 5.5rem;
|
||||
height: 5.5rem;
|
||||
font-size: 2.2rem;
|
||||
border: 3px solid var(--ink);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 6px 6px 0 var(--shadow-ink);
|
||||
}
|
||||
.ab-1 { left: 12%; top: 6%; background: var(--m-mint); transform: rotate(-9deg); animation: memphis-float 6s ease-in-out infinite; }
|
||||
.ab-2 { right: 14%; top: 0; background: var(--m-pink); transform: rotate(7deg); animation: memphis-float 7s ease-in-out infinite reverse; }
|
||||
.ab-3 { left: 26%; bottom: 4%; background: var(--m-blue); transform: rotate(5deg); animation: memphis-bounce 4.5s ease-in-out infinite; }
|
||||
.ab-4 { right: 8%; bottom: 14%; background: var(--m-yellow); transform: rotate(-6deg); animation: memphis-float 5.5s ease-in-out infinite; }
|
||||
.ab-star {
|
||||
position: absolute;
|
||||
left: 50%; top: 44%;
|
||||
font-size: 2.6rem;
|
||||
color: var(--m-orange);
|
||||
animation: memphis-wiggle 3.5s ease-in-out infinite;
|
||||
}
|
||||
.power-card {
|
||||
border: 2.5px solid var(--ink);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 5px 5px 0 var(--shadow-ink);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
.power-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-ink); }
|
||||
@media (max-width: 768px) { .ab-cluster { display: none; } }
|
||||
@media (prefers-reduced-motion: reduce) { .ab-tile, .ab-star { animation: none; } }
|
||||
</style>
|
||||
|
||||
+104
-47
@@ -4,8 +4,6 @@ import { useRouter } from 'vue-router'
|
||||
import { api, generatedUrl } from '../api'
|
||||
import { site } from '../site'
|
||||
import { isAuthed, openLogin } from '../auth'
|
||||
import Mascot from '../components/Mascot.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// Navigate to a signed-in page, or pop the login modal (remembering the
|
||||
@@ -123,32 +121,32 @@ const caps = computed(() => {
|
||||
{
|
||||
key: 't2i', title: '文字生图', model: imageModels.value[0]?.id,
|
||||
desc: '一句话出图,画幅与分辨率按所选模型的可用范围给出。',
|
||||
tint: 'rgb(52 211 153 / 0.14)', color: 'rgb(52 211 153)',
|
||||
tint: '#a7f3d0', color: '#065f46',
|
||||
metas: [resRange.value, `${imageModels.value.length} 个模型`].filter(Boolean),
|
||||
},
|
||||
{
|
||||
key: 'i2i', title: '参考图生图', model: i2iModels[0]?.id,
|
||||
desc: '上传参考图换风格、改构图、做变体,支持多张参考。',
|
||||
tint: 'rgb(217 70 239 / 0.14)', color: 'rgb(232 121 249)',
|
||||
tint: '#ffc1cc', color: '#9f1239',
|
||||
metas: ['多图参考', `${i2iModels.length} 个模型`],
|
||||
},
|
||||
{
|
||||
key: 't2v', title: '文字生视频', model: videoModels.value[0]?.id,
|
||||
desc: '一段描述生成短片,时长与分辨率按模型能力可选。',
|
||||
tint: 'rgb(14 165 233 / 0.14)', color: 'rgb(56 189 248)',
|
||||
tint: '#bae6fd', color: '#075985',
|
||||
metas: [durationRange.value, videoRes.value, `${videoModels.value.length} 个模型`].filter(Boolean),
|
||||
},
|
||||
{
|
||||
key: 'i2v', title: '图生视频', model: i2vModels[0]?.id,
|
||||
desc: '给参考图或首帧,让静态画面动起来。',
|
||||
tint: 'rgb(245 158 11 / 0.14)', color: 'rgb(251 191 36)',
|
||||
tint: '#fef08a', color: '#92400e',
|
||||
metas: ['参考图', `${i2vModels.length} 个模型`],
|
||||
},
|
||||
].filter((c) => c.model)
|
||||
})
|
||||
|
||||
// ---- model cards: the admin-set alias wins, so no upstream naming leaks ----
|
||||
const MODEL_TINT = ['#a78bfa', '#f0abfc', '#fb7185', '#fbbf24', '#38bdf8', '#34d399', '#94a3b8']
|
||||
const MODEL_TINT = ['#ec5b13', '#ffc1cc', '#bae6fd', '#a7f3d0', '#fef08a', '#f472b6', '#94a3b8']
|
||||
const modelCards = computed(() => managed.value.map((m, i) => {
|
||||
const bits = []
|
||||
if (m.resolutions?.length) bits.push(m.resolutions.join(' / '))
|
||||
@@ -199,38 +197,43 @@ function useExample(ex) {
|
||||
|
||||
<template>
|
||||
<div class="px-4 md:px-8">
|
||||
<!-- ============ HERO (centered, mascot-forward) ============ -->
|
||||
<section class="hero-band relative overflow-hidden pt-16 md:pt-24 pb-10">
|
||||
<!-- ============ HERO (centered, memphis deco) ============ -->
|
||||
<section class="hero-band relative overflow-hidden pt-14 md:pt-20 pb-12">
|
||||
<div class="anime-stars"></div>
|
||||
<div class="relative flex flex-col items-center text-center">
|
||||
<!-- mascot centerpiece with soft aura -->
|
||||
<div class="relative mb-6">
|
||||
<span class="mascot-aura"></span>
|
||||
<Mascot :size="184" class="relative" />
|
||||
|
||||
<!-- floating memphis shapes (desktop only) -->
|
||||
<div aria-hidden="true" class="pointer-events-none absolute inset-0 hidden lg:block">
|
||||
<span class="deco deco-a">🎨</span>
|
||||
<span class="deco deco-b">⚡</span>
|
||||
<span class="deco deco-c">4K</span>
|
||||
<span class="deco deco-d">🎬</span>
|
||||
<span class="deco deco-star">✦</span>
|
||||
<span class="deco deco-star deco-star-2">✦</span>
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col items-center text-center">
|
||||
<span class="sticker">
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 shadow-[0_0_8px_rgb(52_211_153)]"></span>
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-500 border border-[#111]"></span>
|
||||
{{ modelCount }} 个模型在线 · 图像与视频一站生成
|
||||
</span>
|
||||
|
||||
<h1 class="mt-5 font-extrabold tracking-tight leading-[1.05] text-[clamp(2.6rem,6vw,4.6rem)] text-[color:var(--fg)]">
|
||||
<h1 class="mt-5 font-black tracking-tight leading-[1.05] text-[clamp(2.6rem,6vw,4.6rem)] text-[color:var(--fg)]">
|
||||
把想象 <span class="anime-grad-text">说给 AI 听</span>
|
||||
</h1>
|
||||
|
||||
<p class="mt-5 text-[15px] md:text-base text-[color:var(--fg-2)] max-w-xl leading-relaxed">
|
||||
<p class="mt-5 text-[15px] md:text-base font-medium text-[color:var(--fg-2)] max-w-xl leading-relaxed">
|
||||
写一句话,剩下的交给我们:自动选模型、自动排队,图像与视频都在同一个工作台里出片。
|
||||
</p>
|
||||
|
||||
<!-- prompt composer — the shortest path into the workbench -->
|
||||
<form class="composer mt-8 !max-w-2xl w-full" @submit.prevent="submitPrompt">
|
||||
<form class="composer mt-9 !max-w-2xl w-full" @submit.prevent="submitPrompt">
|
||||
<input v-model="prompt" class="composer-input" type="text"
|
||||
placeholder="描述你想要的画面,比如:雨夜的东京小巷,霓虹倒映在湿滑路面…" />
|
||||
<button type="submit" class="composer-go">✦ 生成</button>
|
||||
</form>
|
||||
|
||||
<!-- quick starters come from the curated bento set -->
|
||||
<div v-if="bento.length" class="mt-4 flex flex-wrap justify-center gap-2 max-w-2xl">
|
||||
<div v-if="bento.length" class="mt-5 flex flex-wrap justify-center gap-2 max-w-2xl">
|
||||
<button v-for="ex in bento.slice(0, 5)" :key="ex.id" type="button" class="chip"
|
||||
@click="useExample(ex)">{{ ex.title }}</button>
|
||||
</div>
|
||||
@@ -238,7 +241,7 @@ function useExample(ex) {
|
||||
</section>
|
||||
|
||||
<!-- ============ GALLERY MARQUEE (hero + bento imagery) ============ -->
|
||||
<section v-if="gallery.length" class="mt-2 -mx-4 md:-mx-8 overflow-hidden marquee-mask">
|
||||
<section v-if="gallery.length" class="mt-6 -mx-4 md:-mx-8 overflow-hidden marquee-mask border-y-[3px] border-y-[color:var(--ink)] bg-[var(--surface)] py-4">
|
||||
<div class="marquee flex gap-4 px-4 md:px-8 w-max">
|
||||
<button v-for="(card, i) in [...gallery, ...gallery]" :key="card.id + '-' + i" type="button"
|
||||
class="gcard" :style="cardBg(card)" :aria-label="card.title" @click="useExample(card)">
|
||||
@@ -254,23 +257,23 @@ function useExample(ex) {
|
||||
<!-- ============ KPI STRIP ============ -->
|
||||
<section class="mt-12 grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<div v-for="k in [
|
||||
{ v: modelCount, l: '在线模型', e: '🧩' },
|
||||
{ v: caps.length, l: '创作方式', e: '🎨' },
|
||||
{ v: resRange, l: '输出分辨率', e: '🖼️' },
|
||||
{ v: avgLabel, l: '平均出片 · 近 24h', e: '⚡' },
|
||||
{ v: modelCount, l: '在线模型', e: '🧩', bg: 'var(--m-yellow)' },
|
||||
{ v: caps.length, l: '创作方式', e: '🎨', bg: 'var(--m-pink)' },
|
||||
{ v: resRange, l: '输出分辨率', e: '🖼️', bg: 'var(--m-blue)' },
|
||||
{ v: avgLabel, l: '平均出片 · 近 24h', e: '⚡', bg: 'var(--m-mint)' },
|
||||
]" :key="k.l"
|
||||
class="anime-card px-5 py-4">
|
||||
class="kpi-card px-5 py-4" :style="{ background: k.bg }">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">{{ k.e }}</span>
|
||||
<span class="text-[26px] font-bold tabular-nums text-[color:var(--fg)]">{{ k.v }}</span>
|
||||
<span class="text-[26px] font-black tabular-nums text-[#111]">{{ k.v }}</span>
|
||||
</div>
|
||||
<div class="mt-1.5 text-[10px] uppercase tracking-[0.18em] text-[color:var(--fg-3)]">{{ k.l }}</div>
|
||||
<div class="mt-1.5 text-[10px] font-bold uppercase tracking-[0.18em] text-[#111]/60">{{ k.l }}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ CAPABILITIES ============ -->
|
||||
<section class="mt-24">
|
||||
<div class="anime-eyebrow text-violet-500 dark:text-violet-300/80">能力</div>
|
||||
<div class="anime-eyebrow text-[var(--m-orange)]">能力</div>
|
||||
<h2 class="mt-3 text-[clamp(1.65rem,3vw,2.3rem)] font-extrabold tracking-tight text-[color:var(--fg)]">
|
||||
{{ caps.length }} 种方式开始创作
|
||||
</h2>
|
||||
@@ -299,7 +302,7 @@ function useExample(ex) {
|
||||
|
||||
<!-- ============ INSPIRATION (kind=bento) ============ -->
|
||||
<section v-if="bento.length" id="bento" class="mt-24">
|
||||
<div class="anime-eyebrow text-fuchsia-500 dark:text-fuchsia-300/80">灵感</div>
|
||||
<div class="anime-eyebrow text-rose-500">灵感</div>
|
||||
<h2 class="mt-3 text-[clamp(1.65rem,3vw,2.3rem)] font-extrabold tracking-tight text-[color:var(--fg)]">点一下,套用同款提示词</h2>
|
||||
<p class="mt-2.5 text-[13.5px] text-[color:var(--fg-3)] max-w-lg leading-relaxed">
|
||||
任选一张,自动进入画图工作台并预填提示词。
|
||||
@@ -320,7 +323,7 @@ function useExample(ex) {
|
||||
|
||||
<!-- ============ WORKS (kind=work) ============ -->
|
||||
<section v-if="works.length" class="mt-24">
|
||||
<div class="anime-eyebrow text-sky-500 dark:text-sky-300/80">作品</div>
|
||||
<div class="anime-eyebrow text-sky-600">作品</div>
|
||||
<h2 class="mt-3 text-[clamp(1.65rem,3vw,2.3rem)] font-extrabold tracking-tight text-[color:var(--fg)]">大家都在生成什么</h2>
|
||||
<p class="mt-2.5 text-[13.5px] text-[color:var(--fg-3)] max-w-lg leading-relaxed">
|
||||
精选公开作品,点开即用同一条提示词继续创作。
|
||||
@@ -340,7 +343,7 @@ function useExample(ex) {
|
||||
|
||||
<!-- ============ MODELS ============ -->
|
||||
<section v-if="modelCards.length" class="mt-24">
|
||||
<div class="anime-eyebrow text-emerald-500 dark:text-emerald-300/80">模型</div>
|
||||
<div class="anime-eyebrow text-emerald-600">模型</div>
|
||||
<h2 class="mt-3 text-[clamp(1.65rem,3vw,2.3rem)] font-extrabold tracking-tight text-[color:var(--fg)]">在线模型</h2>
|
||||
<p class="mt-2.5 text-[13.5px] text-[color:var(--fg-3)] max-w-lg leading-relaxed">
|
||||
每个模型的分辨率、时长与参考图能力都写在卡片上,选之前就知道能做什么。
|
||||
@@ -668,27 +671,82 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
|
||||
.model-tag.is-image { background: var(--m-mint); color: #065f46; }
|
||||
.model-tag.is-video { background: var(--m-blue); color: #075985; }
|
||||
|
||||
/* ---------- hero band + mascot aura ---------- */
|
||||
/* ---------- hero band + memphis deco ---------- */
|
||||
.hero-band {
|
||||
border-radius: 2rem;
|
||||
}
|
||||
.mascot-aura {
|
||||
/* Floating crayon chips scattered around the headline — each is a small
|
||||
ink-outlined tile with a hard shadow and its own idle animation. */
|
||||
.deco {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgb(254 240 138 / 0.9), rgb(236 91 19 / 0.2) 55%, transparent 72%);
|
||||
filter: blur(14px);
|
||||
animation: auraPulse 5s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 3px solid var(--ink);
|
||||
box-shadow: 5px 5px 0 var(--shadow-ink);
|
||||
font-weight: 900;
|
||||
color: #111;
|
||||
}
|
||||
@keyframes auraPulse {
|
||||
0%, 100% { opacity: 0.8; transform: translate(-50%, -50%) scale(1); }
|
||||
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
|
||||
.deco-a {
|
||||
left: 7%; top: 22%;
|
||||
width: 3.6rem; height: 3.6rem;
|
||||
border-radius: 0.9rem;
|
||||
background: var(--m-yellow);
|
||||
font-size: 1.5rem;
|
||||
transform: rotate(-8deg);
|
||||
animation: memphis-float 6s ease-in-out infinite;
|
||||
}
|
||||
.deco-b {
|
||||
right: 8%; top: 16%;
|
||||
width: 3rem; height: 3rem;
|
||||
border-radius: 999px;
|
||||
background: var(--m-pink);
|
||||
font-size: 1.2rem;
|
||||
transform: rotate(6deg);
|
||||
animation: memphis-wiggle 3.5s ease-in-out infinite;
|
||||
}
|
||||
.deco-c {
|
||||
left: 13%; bottom: 16%;
|
||||
width: 3.2rem; height: 2.4rem;
|
||||
border-radius: 0.6rem;
|
||||
background: var(--m-blue);
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.05em;
|
||||
transform: rotate(9deg);
|
||||
animation: memphis-float 7s ease-in-out infinite reverse;
|
||||
}
|
||||
.deco-d {
|
||||
right: 13%; bottom: 20%;
|
||||
width: 3.4rem; height: 2.6rem;
|
||||
border-radius: 0.7rem;
|
||||
background: var(--m-mint);
|
||||
font-size: 1.15rem;
|
||||
transform: rotate(-7deg);
|
||||
animation: memphis-bounce 4s ease-in-out infinite;
|
||||
}
|
||||
.deco-star {
|
||||
left: 22%; top: 10%;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
font-size: 1.7rem;
|
||||
color: var(--m-orange);
|
||||
animation: memphis-wiggle 4s ease-in-out infinite;
|
||||
}
|
||||
.deco-star-2 {
|
||||
left: auto; right: 22%; top: auto; bottom: 10%;
|
||||
font-size: 1.3rem;
|
||||
color: var(--m-pink);
|
||||
animation: memphis-wiggle 5s ease-in-out infinite reverse;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { .deco { animation: none; } }
|
||||
|
||||
/* KPI tiles — flat crayon fills, dark ink type (readable in both themes). */
|
||||
.kpi-card {
|
||||
border: 2.5px solid var(--ink);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 5px 5px 0 var(--shadow-ink);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
.kpi-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-ink); }
|
||||
|
||||
/* ---------- gallery marquee ---------- */
|
||||
.marquee-mask {
|
||||
@@ -724,6 +782,5 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.marquee { animation: none; }
|
||||
.mascot-aura { animation: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { auth, refreshMe } from '../auth'
|
||||
import { api } from '../api'
|
||||
import { fmtIso } from '../utils/format'
|
||||
import Icon from '../components/Icon.vue'
|
||||
import Mascot from '../components/Mascot.vue'
|
||||
|
||||
// Reward per completed invite — comes from the backend (credits.invite_reward),
|
||||
// falling back to 3 until the response lands.
|
||||
@@ -86,34 +85,34 @@ function toast(m) {
|
||||
<p class="text-white/45 mt-2">好友用你的链接注册,并完成首次生图后,你得 {{ INVITE_REWARD }} 积分。</p>
|
||||
</header>
|
||||
|
||||
<section class="relative rounded-3xl ring-1 ring-white/[0.08] p-7 md:p-8 overflow-hidden"
|
||||
style="background: radial-gradient(at 70% 30%,rgba(251,191,36,0.16) 0%, transparent 55%),linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))">
|
||||
<div class="inline-grid w-10 h-10 rounded-xl bg-amber-500/15 ring-1 ring-amber-400/30 grid place-items-center text-amber-300">
|
||||
<Icon name="accounts" class="w-4 h-4" />
|
||||
<section class="relative rounded-2xl p-7 md:p-8 overflow-hidden ink-card">
|
||||
<div class="memphis-dots absolute inset-0 opacity-30 pointer-events-none"></div>
|
||||
<div class="relative inline-grid w-11 h-11 rounded-xl bg-[var(--m-yellow)] border-2 border-[color:var(--ink)] shadow-[3px_3px_0_var(--shadow-ink)] grid place-items-center text-[#111]">
|
||||
<Icon name="accounts" class="w-5 h-5" />
|
||||
</div>
|
||||
|
||||
<!-- summary -->
|
||||
<div class="mt-6 grid grid-cols-2 gap-3">
|
||||
<div class="rounded-xl bg-white/[0.04] ring-1 ring-white/[0.06] px-4 py-3">
|
||||
<div class="text-2xl font-bold tabular-nums">{{ inviteCount }}</div>
|
||||
<div class="text-[10px] text-white/40 mt-1 uppercase tracking-widest">已邀请</div>
|
||||
<div class="relative mt-6 grid grid-cols-2 gap-3">
|
||||
<div class="rounded-xl px-4 py-3 border-2 border-[color:var(--ink)] bg-[var(--m-mint)] shadow-[3px_3px_0_var(--shadow-ink)]">
|
||||
<div class="text-2xl font-black tabular-nums text-[#111]">{{ inviteCount }}</div>
|
||||
<div class="text-[10px] text-[#111]/55 mt-1 uppercase tracking-widest font-bold">已邀请</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-white/[0.04] ring-1 ring-white/[0.06] px-4 py-3">
|
||||
<div class="text-2xl font-bold tabular-nums">{{ inviteEarned.toLocaleString('en-US') }}</div>
|
||||
<div class="text-[10px] text-white/40 mt-1 uppercase tracking-widest">累计积分</div>
|
||||
<div class="rounded-xl px-4 py-3 border-2 border-[color:var(--ink)] bg-[var(--m-blue)] shadow-[3px_3px_0_var(--shadow-ink)]">
|
||||
<div class="text-2xl font-black tabular-nums text-[#111]">{{ inviteEarned.toLocaleString('en-US') }}</div>
|
||||
<div class="text-[10px] text-[#111]/55 mt-1 uppercase tracking-widest font-bold">累计积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- code -->
|
||||
<div class="mt-6">
|
||||
<label class="block text-xs text-white/50 mb-2">邀请码</label>
|
||||
<div class="relative mt-6">
|
||||
<label class="block text-xs font-bold text-white/50 mb-2">邀请码</label>
|
||||
<div class="flex gap-2">
|
||||
<button @click="copyCode"
|
||||
class="flex-1 rounded-xl bg-white/[0.05] ring-1 ring-white/10 hover:ring-white/30 hover:bg-white/[0.08] px-4 py-3 text-sm font-mono text-left transition-all">
|
||||
class="flex-1 rounded-xl border-2 border-[color:var(--ink)] bg-[var(--surface)] px-4 py-3 text-sm font-mono text-left shadow-[3px_3px_0_var(--shadow-ink)] hover:-translate-y-0.5 hover:shadow-[4px_4px_0_var(--shadow-ink)] transition-all">
|
||||
{{ inviteCode || '—' }}
|
||||
</button>
|
||||
<button @click="copyInvite"
|
||||
class="rounded-xl bg-white text-black hover:bg-white/90 px-5 py-3 text-sm font-semibold transition-colors">
|
||||
class="rounded-xl border-[2.5px] border-[color:var(--ink)] bg-[var(--m-orange)] text-white px-5 py-3 text-sm font-extrabold shadow-[3px_3px_0_var(--shadow-ink)] hover:-translate-y-0.5 hover:shadow-[4px_4px_0_var(--shadow-ink)] active:translate-y-0.5 active:shadow-none transition-all">
|
||||
复制链接
|
||||
</button>
|
||||
</div>
|
||||
@@ -134,7 +133,7 @@ function toast(m) {
|
||||
<div v-if="loading && !records.length" class="text-center text-sm text-white/40 py-16">加载中…</div>
|
||||
<div v-else-if="!records.length" class="relative overflow-hidden flex flex-col items-center gap-3 text-white/40 py-14">
|
||||
<div class="anime-stars"></div>
|
||||
<Mascot :size="110" />
|
||||
<span class="empty-badge">✦</span>
|
||||
<span class="text-sm">还没有人通过你的链接注册~快去分享吧</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { api, jsonBody } from '../api'
|
||||
import { openPayment } from '../payment'
|
||||
import { refreshMe } from '../auth'
|
||||
import Icon from '../components/Icon.vue'
|
||||
import Mascot from '../components/Mascot.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const items = ref([])
|
||||
@@ -118,7 +117,7 @@ async function cont(o) {
|
||||
<div v-if="loading && !items.length" class="card text-center text-sm text-slate-400 py-24">加载中…</div>
|
||||
<div v-else-if="!total" class="anime-card relative overflow-hidden flex flex-col items-center gap-3 text-[color:var(--fg-3)] py-20">
|
||||
<div class="anime-stars"></div>
|
||||
<Mascot :size="120" />
|
||||
<span class="empty-badge">✦</span>
|
||||
<span class="text-sm">还没有充值订单~</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { copyText } from '../utils/clipboard'
|
||||
import { points } from '../credits'
|
||||
import Icon from '../components/Icon.vue'
|
||||
import MediaLightbox from '../components/MediaLightbox.vue'
|
||||
import Mascot from '../components/Mascot.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const items = ref([]) // current server page
|
||||
@@ -184,7 +183,7 @@ const params = (e) => {
|
||||
<div v-if="loading && !items.length" class="card text-center text-sm text-slate-400 py-24">加载中…</div>
|
||||
<div v-else-if="!total" class="anime-card relative overflow-hidden flex flex-col items-center gap-3 text-[color:var(--fg-3)] py-20">
|
||||
<div class="anime-stars"></div>
|
||||
<Mascot :size="120" />
|
||||
<span class="empty-badge">✦</span>
|
||||
<span class="text-sm">还没有生成日志~</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { copyText } from '../utils/clipboard'
|
||||
import { zipSync } from 'fflate'
|
||||
import Icon from '../components/Icon.vue'
|
||||
import MediaLightbox from '../components/MediaLightbox.vue'
|
||||
import Mascot from '../components/Mascot.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -277,7 +276,7 @@ onUnmounted(() => {
|
||||
<div v-else-if="!filtered.length"
|
||||
class="anime-card relative overflow-hidden flex flex-col items-center gap-3 text-[color:var(--fg-3)] py-20">
|
||||
<div class="anime-stars"></div>
|
||||
<Mascot :size="120" />
|
||||
<span class="empty-badge">✦</span>
|
||||
<span class="text-sm">还没有创作记录,画一张试试吧~</span>
|
||||
<button @click="router.push('/user')" class="bubble-btn mt-1">开始第一张 ✦</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user