's'
This commit is contained in:
@@ -27,6 +27,7 @@ async function registerOrLoginUser(
|
|||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
passwordConfirm: password,
|
passwordConfirm: password,
|
||||||
|
live_allowed: true,
|
||||||
}),
|
}),
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ async function ensurePbUserExists(pbUrl: string, email: string): Promise<void> {
|
|||||||
email,
|
email,
|
||||||
password: DEFAULT_PASSWORD,
|
password: DEFAULT_PASSWORD,
|
||||||
passwordConfirm: DEFAULT_PASSWORD,
|
passwordConfirm: DEFAULT_PASSWORD,
|
||||||
|
live_allowed: true,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (registerRes.ok) {
|
if (registerRes.ok) {
|
||||||
|
|||||||
52
app/api/promo/recognize-discount/route.ts
Normal file
52
app/api/promo/recognize-discount/route.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getApiUrlFromRequest, getPaymentConfig } from "@/config/services.config";
|
||||||
|
|
||||||
|
function resolvePayApiUrl(request: NextRequest): string {
|
||||||
|
const hostHeader =
|
||||||
|
request.headers.get("host") || request.headers.get("x-forwarded-host");
|
||||||
|
return (
|
||||||
|
(hostHeader ? getApiUrlFromRequest(hostHeader) : null) ||
|
||||||
|
getPaymentConfig().apiUrl
|
||||||
|
).replace(/\/$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const image = formData.get("image");
|
||||||
|
if (!(image instanceof File)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ ok: false, error: "缺少图片文件 image" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = new FormData();
|
||||||
|
payload.append("image", image, image.name || "proof.jpg");
|
||||||
|
|
||||||
|
const apiUrl = resolvePayApiUrl(request);
|
||||||
|
const res = await fetch(`${apiUrl}/nomadvip/recognize_discount_proof`, {
|
||||||
|
method: "POST",
|
||||||
|
body: payload,
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ ok: false, error: data?.error || "识别服务异常" },
|
||||||
|
{ status: res.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return NextResponse.json(data);
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
ok: false,
|
||||||
|
error: error instanceof Error ? error.message : "识别请求失败",
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,12 @@ export async function pbRegister(
|
|||||||
const res = await fetch(`${url}/api/collections/users/records`, {
|
const res = await fetch(`${url}/api/collections/users/records`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ email, password, passwordConfirm }),
|
body: JSON.stringify({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
passwordConfirm,
|
||||||
|
live_allowed: true,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.json().catch(() => ({}));
|
const err = await res.json().catch(() => ({}));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { CommunityPreview } from "./landing/CommunityPreview";
|
|||||||
import { FAQSection } from "./landing/FAQSection";
|
import { FAQSection } from "./landing/FAQSection";
|
||||||
import { LandingCTA } from "./landing/LandingCTA";
|
import { LandingCTA } from "./landing/LandingCTA";
|
||||||
import { LoginModal } from "./landing/LoginModal";
|
import { LoginModal } from "./landing/LoginModal";
|
||||||
|
import { PromoProofModal } from "./landing/PromoProofModal";
|
||||||
import styles from "./vip.module.css";
|
import styles from "./vip.module.css";
|
||||||
|
|
||||||
type LandingPageProps = {
|
type LandingPageProps = {
|
||||||
@@ -29,6 +30,7 @@ export function LandingPage({
|
|||||||
onVerifySuccess,
|
onVerifySuccess,
|
||||||
}: LandingPageProps) {
|
}: LandingPageProps) {
|
||||||
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
||||||
|
const [promoModalOpen, setPromoModalOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -49,7 +51,10 @@ export function LandingPage({
|
|||||||
<CommunityPreview />
|
<CommunityPreview />
|
||||||
</div>
|
</div>
|
||||||
<FAQSection />
|
<FAQSection />
|
||||||
<LandingCTA onJoin={onJoin} onLogin={() => setLoginModalOpen(true)} />
|
<LandingCTA
|
||||||
|
onJoin={onJoin}
|
||||||
|
onLogin={() => setLoginModalOpen(true)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LoginModal
|
<LoginModal
|
||||||
@@ -59,6 +64,7 @@ export function LandingPage({
|
|||||||
onVerifyByEmail={onVerifyByEmail}
|
onVerifyByEmail={onVerifyByEmail}
|
||||||
onSuccess={onVerifySuccess}
|
onSuccess={onVerifySuccess}
|
||||||
/>
|
/>
|
||||||
|
<PromoProofModal open={promoModalOpen} onClose={() => setPromoModalOpen(false)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function LandingCTA({ onJoin, onLogin }: LandingCTAProps) {
|
|||||||
<div className={styles.ctaGlow} aria-hidden />
|
<div className={styles.ctaGlow} aria-hidden />
|
||||||
<div className={styles.ctaCard}>
|
<div className={styles.ctaCard}>
|
||||||
<h2 id="cta-heading" className={styles.ctaPromoLine}>
|
<h2 id="cta-heading" className={styles.ctaPromoLine}>
|
||||||
<span className={styles.ctaBadgeInline}>
|
<span className={styles.ctaPromoClickable} aria-disabled="true">
|
||||||
<span className={styles.ctaBadgePulse} aria-hidden />
|
<span className={styles.ctaBadgePulse} aria-hidden />
|
||||||
限时特惠
|
限时特惠
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
134
app/vip/components/landing/PromoProofModal.tsx
Normal file
134
app/vip/components/landing/PromoProofModal.tsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { type ChangeEvent, useRef, useState } from "react";
|
||||||
|
import styles from "../vip.module.css";
|
||||||
|
|
||||||
|
type PromoProofModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RecognizeResult = {
|
||||||
|
ok: boolean;
|
||||||
|
nickname?: string;
|
||||||
|
avatar_image_base64?: string;
|
||||||
|
avatar_mime?: string;
|
||||||
|
confidence?: {
|
||||||
|
nickname?: number;
|
||||||
|
avatar?: number;
|
||||||
|
overall?: number;
|
||||||
|
};
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PromoProofModal({ open, onClose }: PromoProofModalProps) {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
const [confirmed, setConfirmed] = useState(false);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [result, setResult] = useState<RecognizeResult | null>(null);
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
const onPickFile = () => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
setError("");
|
||||||
|
setResult(null);
|
||||||
|
setUploading(true);
|
||||||
|
try {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("image", file);
|
||||||
|
const res = await fetch("/api/promo/recognize-discount", {
|
||||||
|
method: "POST",
|
||||||
|
body: form,
|
||||||
|
});
|
||||||
|
const data = (await res.json().catch(() => ({}))) as RecognizeResult;
|
||||||
|
if (!res.ok || !data?.ok) {
|
||||||
|
setError(data?.error || data?.message || "识别失败,请换一张清晰截图再试");
|
||||||
|
} else {
|
||||||
|
setResult(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setError(e instanceof Error ? e.message : "上传失败,请稍后重试");
|
||||||
|
} finally {
|
||||||
|
setUploading(false);
|
||||||
|
event.target.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const avatarSrc =
|
||||||
|
result?.avatar_image_base64 && result?.avatar_mime
|
||||||
|
? `data:${result.avatar_mime};base64,${result.avatar_image_base64}`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.communityModalBackdrop} role="dialog" aria-modal="true" aria-labelledby="promo-proof-title">
|
||||||
|
<div className={styles.promoModalPanel}>
|
||||||
|
<button type="button" onClick={onClose} className={styles.communityModalClose} aria-label="关闭">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
<div className={styles.promoModalTop}>
|
||||||
|
<div className={styles.promoModalTag}>限时优惠任务</div>
|
||||||
|
<h3 id="promo-proof-title" className={styles.promoModalTitle}>推荐任意公众号文章,立减 50 元</h3>
|
||||||
|
<p className={styles.promoModalDesc}>
|
||||||
|
在手机里打开任意一篇公众号文章,点击文末推荐(心形 icon),然后上传截图。系统将识别
|
||||||
|
<strong> 头像 + 昵称 + 心形推荐痕迹 </strong>
|
||||||
|
作为优惠凭证。
|
||||||
|
</p>
|
||||||
|
<p className={styles.promoModalHint}>示例参考:页面左下角「异地世界」上方那一行(头像 / Eric / 爱心)。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!confirmed ? (
|
||||||
|
<div className={styles.promoModalActions}>
|
||||||
|
<button type="button" className={`${styles.btnPrimary} ${styles.promoPrimaryBtn}`} onClick={() => setConfirmed(true)}>
|
||||||
|
我已理解,去上传截图
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.promoUploadArea}>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/png,image/jpeg,image/webp"
|
||||||
|
className={styles.promoFileInput}
|
||||||
|
onChange={onFileChange}
|
||||||
|
/>
|
||||||
|
<button type="button" className={styles.promoUploadBtn} onClick={onPickFile} disabled={uploading}>
|
||||||
|
{uploading ? "识别中..." : "选择手机截图并上传"}
|
||||||
|
</button>
|
||||||
|
<p className={styles.promoUploadTip}>支持 jpg/png/webp,建议宽度大于 720px 且文字清晰</p>
|
||||||
|
|
||||||
|
{error ? <p className={styles.promoError}>{error}</p> : null}
|
||||||
|
|
||||||
|
{result?.ok ? (
|
||||||
|
<div className={styles.promoResultCard}>
|
||||||
|
<p className={styles.promoResultTitle}>识别结果</p>
|
||||||
|
<div className={styles.promoResultRow}>
|
||||||
|
<div className={styles.promoAvatarWrap}>
|
||||||
|
{avatarSrc ? (
|
||||||
|
<img src={avatarSrc} alt="识别头像" className={styles.promoAvatarImg} />
|
||||||
|
) : (
|
||||||
|
<div className={styles.promoAvatarEmpty}>无头像</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.promoResultMeta}>
|
||||||
|
<p className={styles.promoNicknameLabel}>昵称</p>
|
||||||
|
<p className={styles.promoNicknameValue}>{result.nickname || "未识别"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1048,10 +1048,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ctaCard {
|
.dark .ctaCard {
|
||||||
background: rgba(20, 20, 26, 0.78);
|
background:
|
||||||
|
radial-gradient(90% 70% at 50% -10%, rgba(251, 191, 36, 0.12) 0%, rgba(251, 191, 36, 0) 58%),
|
||||||
|
linear-gradient(180deg, #0b1220 0%, #0a0f1a 100%);
|
||||||
|
border-color: rgba(251, 191, 36, 0.18);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 24px 56px rgba(0, 0, 0, 0.45),
|
0 24px 60px rgba(0, 0, 0, 0.52),
|
||||||
0 0 0 1px rgba(255, 255, 255, 0.06) inset;
|
0 0 0 1px rgba(251, 191, 36, 0.08) inset,
|
||||||
|
0 1px 0 rgba(255, 255, 255, 0.04) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaGlow {
|
||||||
|
opacity: 0.55;
|
||||||
|
filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaPromoLine {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaHighlight {
|
||||||
|
background: linear-gradient(120deg, #f59e0b 0%, #fcd34d 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
@@ -1381,3 +1401,232 @@
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
background: var(--muted);
|
background: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ctaPromoClickable {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.28rem 0.65rem;
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.35);
|
||||||
|
border-radius: 9999px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: transform 0.15s, box-shadow 0.15s, background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctaPromoClickable:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
background: rgba(245, 158, 11, 0.18);
|
||||||
|
box-shadow: 0 8px 20px rgba(245, 158, 11, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctaPromoClickable:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaPromoClickable {
|
||||||
|
color: #fbbf24;
|
||||||
|
background: rgba(245, 158, 11, 0.16);
|
||||||
|
border-color: rgba(251, 191, 36, 0.42);
|
||||||
|
box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.1) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaPromoClickable:hover {
|
||||||
|
background: rgba(245, 158, 11, 0.24);
|
||||||
|
box-shadow:
|
||||||
|
0 12px 28px rgba(0, 0, 0, 0.42),
|
||||||
|
0 0 0 1px rgba(251, 191, 36, 0.18) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaPromoDivider {
|
||||||
|
color: rgba(251, 191, 36, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaPromoSuffix {
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaChip {
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
border-color: rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaBtnSecondary {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
border-color: rgba(251, 191, 36, 0.26);
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ctaBtnSecondary:hover {
|
||||||
|
background: rgba(251, 191, 36, 0.1);
|
||||||
|
border-color: rgba(251, 191, 36, 0.42);
|
||||||
|
color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 促销识别弹窗 */
|
||||||
|
.promoModalPanel {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 520px;
|
||||||
|
padding: 1.4rem 1.2rem 1.2rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
||||||
|
background: var(--card);
|
||||||
|
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoModalTop {
|
||||||
|
padding-right: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoModalTag {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.18rem 0.58rem;
|
||||||
|
margin-bottom: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoModalTitle {
|
||||||
|
margin: 0 0 0.35rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoModalDesc {
|
||||||
|
margin: 0 0 0.55rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoModalHint {
|
||||||
|
margin: 0 0 0.9rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoModalActions {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoPrimaryBtn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoUploadArea {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
border: 1px dashed rgba(245, 158, 11, 0.34);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.9rem;
|
||||||
|
background: rgba(245, 158, 11, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoFileInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoUploadBtn {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.42);
|
||||||
|
background: linear-gradient(135deg, rgba(245, 158, 11, 0.18), rgba(245, 158, 11, 0.08));
|
||||||
|
color: var(--foreground);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.72rem 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoUploadBtn:disabled {
|
||||||
|
opacity: 0.65;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoUploadTip {
|
||||||
|
margin: 0.45rem 0 0;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
font-size: 0.76rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoError {
|
||||||
|
margin: 0.75rem 0 0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoResultCard {
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--muted);
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoResultTitle {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 0.83rem;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoResultRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoAvatarWrap {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--card);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoAvatarImg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoAvatarEmpty {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoResultMeta {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoNicknameLabel {
|
||||||
|
margin: 0 0 0.15rem;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoNicknameValue {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user