Commit 75992d7b authored by yaoke.yk's avatar yaoke.yk

企业级工业风格

parent 175f406c
import type { ReactNode } from "react";
import { Link } from "react-router";
import { ArrowLeft, ShieldCheck, Sparkles } from "lucide-react";
import { brand, brandHighlights, brandMetrics } from "../../lib/brand";
import { BrandLogo } from "./BrandLogo";
type AuthShellProps = {
title: string;
description: string;
children: ReactNode;
footer: ReactNode;
};
export function AuthShell({ title, description, children, footer }: AuthShellProps) {
return (
<div className="relative min-h-screen overflow-hidden bg-background">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(34,146,255,0.22),transparent_34%),radial-gradient(circle_at_bottom_right,_rgba(6,43,92,0.16),transparent_28%)]" />
<div className="absolute inset-0 opacity-60 [background-image:linear-gradient(rgba(15,87,157,0.06)_1px,transparent_1px),linear-gradient(90deg,rgba(15,87,157,0.06)_1px,transparent_1px)] [background-size:32px_32px]" />
<div className="relative mx-auto flex min-h-screen max-w-7xl flex-col px-4 py-6 lg:px-8">
<div className="mb-6 flex items-center justify-between">
<BrandLogo subtitle={brand.tagline} />
<Link
to="/"
className="inline-flex items-center gap-2 rounded-full border border-border/80 bg-white/75 px-4 py-2 text-sm text-muted-foreground shadow-sm backdrop-blur hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
返回工作台
</Link>
</div>
<div className="grid flex-1 items-stretch gap-6 lg:grid-cols-[1.15fr_0.85fr]">
<section className="relative overflow-hidden rounded-[32px] border border-white/70 bg-[linear-gradient(145deg,#0d294b_0%,#11477f_46%,#1f9ce8_100%)] p-8 text-white shadow-[0_32px_90px_rgba(9,42,82,0.24)] lg:p-10">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,_rgba(255,255,255,0.18),transparent_24%),radial-gradient(circle_at_bottom_left,_rgba(255,255,255,0.1),transparent_28%)]" />
<div className="absolute -right-20 top-10 h-72 w-72 rounded-full border border-white/15" />
<div className="absolute right-10 top-28 h-44 w-44 rounded-full border border-white/10" />
<div className="absolute bottom-10 left-8 h-28 w-28 rounded-full border border-white/10" />
<div className="relative flex h-full flex-col">
<div className="mb-8 inline-flex w-fit items-center gap-2 rounded-full border border-white/20 bg-white/10 px-4 py-2 text-sm text-white/86 backdrop-blur">
<ShieldCheck className="h-4 w-4" />
企业级短剧生产平台
</div>
<div className="max-w-2xl">
<div className="mb-3 text-sm uppercase tracking-[0.32em] text-white/64">
{brand.companyName}
</div>
<h1 className="max-w-xl text-4xl font-semibold leading-tight lg:text-5xl">
{brand.heroTitle}
</h1>
<p className="mt-5 max-w-xl text-base leading-7 text-white/76 lg:text-lg">
{brand.heroDescription}
</p>
</div>
<div className="mt-10 grid gap-4 sm:grid-cols-3">
{brandMetrics.map((metric) => (
<div
key={metric.label}
className="rounded-3xl border border-white/14 bg-white/10 px-5 py-4 backdrop-blur-sm"
>
<div className="text-2xl font-semibold">{metric.value}</div>
<div className="mt-2 text-sm text-white/66">{metric.label}</div>
</div>
))}
</div>
<div className="mt-auto pt-10">
<div className="mb-4 inline-flex items-center gap-2 rounded-full bg-white/10 px-3 py-1.5 text-xs uppercase tracking-[0.28em] text-white/72">
<Sparkles className="h-3.5 w-3.5" />
Platform Value
</div>
<div className="grid gap-3">
{brandHighlights.map((item) => (
<div
key={item}
className="rounded-2xl border border-white/14 bg-white/8 px-4 py-3 text-sm leading-6 text-white/82"
>
{item}
</div>
))}
</div>
</div>
</div>
</section>
<section className="flex items-center">
<div className="w-full rounded-[32px] border border-white/80 bg-white/82 p-6 shadow-[0_30px_80px_rgba(15,57,99,0.12)] backdrop-blur-xl lg:p-8">
<div className="mb-6">
<div className="mb-2 text-xs uppercase tracking-[0.28em] text-primary/70">
Secure Access
</div>
<h2 className="text-3xl font-semibold text-foreground">{title}</h2>
<p className="mt-3 text-sm leading-6 text-muted-foreground">{description}</p>
</div>
{children}
<div className="mt-6 border-t border-border/80 pt-5 text-sm text-muted-foreground">
{footer}
</div>
</div>
</section>
</div>
</div>
</div>
);
}
import { Link } from "react-router";
import { brand } from "../../lib/brand";
import { cn } from "./ui/utils";
type BrandLogoProps = {
compact?: boolean;
className?: string;
href?: string;
subtitle?: string;
};
export function BrandLogo({
compact = false,
className,
href = "/",
subtitle,
}: BrandLogoProps) {
const content = (
<div className={cn("flex items-center gap-3", className)}>
<div
className={cn(
"overflow-hidden rounded-2xl ring-1 ring-white/70 shadow-[0_12px_28px_rgba(15,77,143,0.16)]",
compact ? "h-10 w-10 rounded-xl" : "h-12 w-12",
)}
>
<img
src={brand.logo}
alt={brand.companyName}
className="h-full w-full object-cover"
/>
</div>
<div className="min-w-0">
<div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-primary/70">
{brand.companyName}
</div>
<div
className={cn(
"truncate font-semibold text-foreground",
compact ? "text-sm" : "text-base",
)}
>
{brand.productName}
</div>
{subtitle ? (
<div className="truncate text-xs text-muted-foreground">{subtitle}</div>
) : null}
</div>
</div>
);
return href ? (
<Link to={href} className="inline-flex">
{content}
</Link>
) : (
content
);
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import { LockKeyhole, Mail, ShieldCheck } from "lucide-react";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input"; import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label"; import { Label } from "../components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { useAuth } from "../../hooks/useAuth"; import { useAuth } from "../../hooks/useAuth";
import { AuthShell } from "../components/AuthShell";
export function LoginPage() { export function LoginPage() {
const { login, loginPending, loginError } = useAuth(); const { login, loginPending, loginError } = useAuth();
...@@ -16,16 +17,31 @@ export function LoginPage() { ...@@ -16,16 +17,31 @@ export function LoginPage() {
}; };
return ( return (
<div className="min-h-screen flex items-center justify-center bg-background px-4"> <AuthShell
<Card className="w-full max-w-sm"> title="登录企业工作台"
<CardHeader className="text-center"> description="使用企业账号进入中科集团 AI 短剧工业化平台,继续推进剧本、设定、分镜与视频任务。"
<CardTitle className="text-2xl">YaoAI Comic Studio</CardTitle> footer={
<CardDescription>登录您的账号</CardDescription> <div className="flex items-center justify-between gap-3">
</CardHeader> <div className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<CardContent> <ShieldCheck className="h-4 w-4 text-primary" />
<form onSubmit={handleSubmit} className="space-y-4"> 统一身份认证与团队空间隔离
</div>
<div>
没有账号?
<Link to="/register" className="ml-1 font-medium text-primary hover:underline">
立即注册
</Link>
</div>
</div>
}
>
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email">邮箱</Label> <Label htmlFor="email" className="text-sm text-foreground">
邮箱账号
</Label>
<div className="relative">
<Mail className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input <Input
id="email" id="email"
type="email" type="email"
...@@ -33,10 +49,20 @@ export function LoginPage() { ...@@ -33,10 +49,20 @@ export function LoginPage() {
required required
value={form.email} value={form.email}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
className="h-11 rounded-xl border-border/90 bg-white pl-10 shadow-sm"
/> />
</div> </div>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="password">密码</Label> <div className="flex items-center justify-between">
<Label htmlFor="password" className="text-sm text-foreground">
登录密码
</Label>
<span className="text-xs text-muted-foreground">建议使用企业统一密码</span>
</div>
<div className="relative">
<LockKeyhole className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input <Input
id="password" id="password"
type="password" type="password"
...@@ -44,28 +70,25 @@ export function LoginPage() { ...@@ -44,28 +70,25 @@ export function LoginPage() {
required required
value={form.password} value={form.password}
onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
className="h-11 rounded-xl border-border/90 bg-white pl-10 shadow-sm"
/> />
</div> </div>
</div>
{loginError && ( {loginError ? (
<p className="text-sm text-destructive"> <div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{loginError instanceof Error ? loginError.message : "登录失败"} {loginError instanceof Error ? loginError.message : "登录失败,请检查账号和密码。"}
</p> </div>
)} ) : null}
<Button type="submit" className="w-full" disabled={loginPending}> <Button
{loginPending ? "登录中..." : "登录"} type="submit"
className="h-12 w-full rounded-xl bg-[linear-gradient(135deg,#0f74d8_0%,#1ea5ff_100%)] text-base font-medium shadow-[0_16px_30px_rgba(15,116,216,0.22)] transition hover:translate-y-[-1px] hover:shadow-[0_20px_38px_rgba(15,116,216,0.26)]"
disabled={loginPending}
>
{loginPending ? "正在登录..." : "进入工作台"}
</Button> </Button>
<p className="text-center text-sm text-muted-foreground">
没有账号?{" "}
<Link to="/register" className="text-primary underline-offset-4 hover:underline">
立即注册
</Link>
</p>
</form> </form>
</CardContent> </AuthShell>
</Card>
</div>
); );
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import { Building2, LockKeyhole, Mail, UserRound } from "lucide-react";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input"; import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label"; import { Label } from "../components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { useAuth } from "../../hooks/useAuth"; import { useAuth } from "../../hooks/useAuth";
import { AuthShell } from "../components/AuthShell";
export function RegisterPage() { export function RegisterPage() {
const { register, registerPending, registerError } = useAuth(); const { register, registerPending, registerError } = useAuth();
...@@ -26,26 +27,45 @@ export function RegisterPage() { ...@@ -26,26 +27,45 @@ export function RegisterPage() {
}; };
return ( return (
<div className="min-h-screen flex items-center justify-center bg-background px-4"> <AuthShell
<Card className="w-full max-w-sm"> title="创建企业账号"
<CardHeader className="text-center"> description="注册后即可创建团队空间,统一管理短剧项目、成员权限、资产沉淀与 AI 生成流程。"
<CardTitle className="text-2xl">创建账号</CardTitle> footer={
<CardDescription>免费开始使用 YaoAI Comic Studio</CardDescription> <div className="flex items-center justify-between gap-3">
</CardHeader> <span className="text-xs text-muted-foreground">支持个人试用,也支持企业团队空间接入</span>
<CardContent> <div>
<form onSubmit={handleSubmit} className="space-y-4"> 已有账号?
<Link to="/login" className="ml-1 font-medium text-primary hover:underline">
立即登录
</Link>
</div>
</div>
}
>
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="username">用户名</Label> <Label htmlFor="username" className="text-sm text-foreground">
用户名
</Label>
<div className="relative">
<UserRound className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input <Input
id="username" id="username"
placeholder="your-name" placeholder="your-name"
required required
value={form.username} value={form.username}
onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))}
className="h-11 rounded-xl border-border/90 bg-white pl-10 shadow-sm"
/> />
</div> </div>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email">邮箱</Label> <Label htmlFor="email" className="text-sm text-foreground">
邮箱
</Label>
<div className="relative">
<Mail className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input <Input
id="email" id="email"
type="email" type="email"
...@@ -53,10 +73,17 @@ export function RegisterPage() { ...@@ -53,10 +73,17 @@ export function RegisterPage() {
required required
value={form.email} value={form.email}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
className="h-11 rounded-xl border-border/90 bg-white pl-10 shadow-sm"
/> />
</div> </div>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="password">密码</Label> <Label htmlFor="password" className="text-sm text-foreground">
密码
</Label>
<div className="relative">
<LockKeyhole className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input <Input
id="password" id="password"
type="password" type="password"
...@@ -65,39 +92,44 @@ export function RegisterPage() { ...@@ -65,39 +92,44 @@ export function RegisterPage() {
minLength={8} minLength={8}
value={form.password} value={form.password}
onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
className="h-11 rounded-xl border-border/90 bg-white pl-10 shadow-sm"
/> />
</div> </div>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="tenantName"> <div className="flex items-center justify-between">
工作空间名称 <span className="text-muted-foreground text-xs">(可选)</span> <Label htmlFor="tenantName" className="text-sm text-foreground">
工作空间名称
</Label> </Label>
<span className="text-xs text-muted-foreground">可选</span>
</div>
<div className="relative">
<Building2 className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input <Input
id="tenantName" id="tenantName"
placeholder="我的工作室" placeholder="我的工作室"
value={form.tenantName} value={form.tenantName}
onChange={(e) => setForm((f) => ({ ...f, tenantName: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, tenantName: e.target.value }))}
className="h-11 rounded-xl border-border/90 bg-white pl-10 shadow-sm"
/> />
</div> </div>
</div>
{registerError && ( {registerError ? (
<p className="text-sm text-destructive"> <div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{registerError instanceof Error ? registerError.message : "注册失败"} {registerError instanceof Error ? registerError.message : "注册失败,请稍后重试。"}
</p> </div>
)} ) : null}
<Button type="submit" className="w-full" disabled={registerPending}> <Button
{registerPending ? "注册中..." : "注册"} type="submit"
className="h-12 w-full rounded-xl bg-[linear-gradient(135deg,#0f74d8_0%,#1ea5ff_100%)] text-base font-medium shadow-[0_16px_30px_rgba(15,116,216,0.22)] transition hover:translate-y-[-1px] hover:shadow-[0_20px_38px_rgba(15,116,216,0.26)]"
disabled={registerPending}
>
{registerPending ? "正在注册..." : "创建账号"}
</Button> </Button>
<p className="text-center text-sm text-muted-foreground">
已有账号?{" "}
<Link to="/login" className="text-primary underline-offset-4 hover:underline">
立即登录
</Link>
</p>
</form> </form>
</CardContent> </AuthShell>
</Card>
</div>
); );
} }
This diff is collapsed.
import { useState } from "react"; import { useState } from "react";
import { useNavigate, useParams } from "react-router"; import { useNavigate, useParams } from "react-router";
import { ArrowRight, Check, Loader2 } from "lucide-react"; import { ArrowRight, Check, Loader2 } from "lucide-react";
import { useUpdateProject } from "../../hooks/useProjects"; import { useUpdateProject } from "../../hooks/useProjects";
...@@ -20,10 +20,9 @@ export function StyleSelection() { ...@@ -20,10 +20,9 @@ export function StyleSelection() {
<div className="h-full overflow-auto bg-background p-6"> <div className="h-full overflow-auto bg-background p-6">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<div className="mb-8 text-center"> <div className="mb-8 text-center">
<h1 className="mb-2 text-3xl font-semibold text-foreground">选择项目视觉风格</h1> <h1 className="mb-2 text-3xl font-semibold text-foreground">閫夋嫨椤圭洰瑙嗚椋庢牸</h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
这里选择的是视觉呈现方式,不是题材类型。AI 会根据所选风格影响人物、场景与后续图像生成。 杩欓噷閫夋嫨鐨勬槸瑙嗚鍛堢幇鏂瑰紡锛屼笉鏄鏉愮被鍨嬨€侫I 浼氭牴鎹墍閫夐鏍煎奖鍝嶄汉鐗┿€佸満鏅笌鍚庣画鍥惧儚鐢熸垚銆? </p>
</p>
</div> </div>
<div className="mb-8 grid grid-cols-3 gap-4"> <div className="mb-8 grid grid-cols-3 gap-4">
...@@ -61,13 +60,13 @@ export function StyleSelection() { ...@@ -61,13 +60,13 @@ export function StyleSelection() {
<button <button
onClick={handleContinue} onClick={handleContinue}
disabled={!selectedStyle || updateProject.isPending} disabled={!selectedStyle || updateProject.isPending}
className="flex w-full items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-[#5b5ff9] to-[#8b5ff9] py-3 text-white transition-all hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:shadow-none" className="flex w-full items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-[#0f74d8] to-[#1ea5ff] py-3 text-white transition-all hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:shadow-none"
> >
{updateProject.isPending ? ( {updateProject.isPending ? (
<Loader2 className="h-5 w-5 animate-spin" /> <Loader2 className="h-5 w-5 animate-spin" />
) : ( ) : (
<> <>
<span>继续</span> <span>缁х画</span>
<ArrowRight className="h-5 w-5" /> <ArrowRight className="h-5 w-5" />
</> </>
)} )}
...@@ -76,3 +75,4 @@ export function StyleSelection() { ...@@ -76,3 +75,4 @@ export function StyleSelection() {
</div> </div>
); );
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import logoImage from "../imports/ab7d1ce5faf061d2af2b02d93e11935.png";
export const brand = {
companyName: "中科集团",
productName: "AI 短剧工业化平台",
shortProductName: "短剧工业化平台",
tagline: "从剧本拆解到视频成片的企业级创作中台",
heroTitle: "面向企业协同的 AI 短剧生产工作台",
heroDescription:
"统一管理剧本、大纲、角色、场景、分镜与视频任务,让团队在同一条生产链路中协作推进。",
logo: logoImage,
};
export const brandMetrics = [
{ label: "流程阶段", value: "6+" },
{ label: "团队协作", value: "多角色" },
{ label: "内容资产", value: "统一管理" },
];
export const brandHighlights = [
"品牌化工作台与中科集团统一视觉体系",
"覆盖剧本、设定、分镜、视频的完整生产链路",
"支持团队协作、资产沉淀与项目进度追踪",
];
:root {
--font-sans-brand:
"Avenir Next",
"Segoe UI",
"PingFang SC",
"Hiragino Sans GB",
"Microsoft YaHei",
"Noto Sans SC",
sans-serif;
}
...@@ -2,43 +2,43 @@ ...@@ -2,43 +2,43 @@
:root { :root {
--font-size: 14px; --font-size: 14px;
--background: #f8f9fa; --background: #eef4f9;
--foreground: #1a1a1a; --foreground: #12263f;
--card: #ffffff; --card: rgba(255, 255, 255, 0.92);
--card-foreground: #1a1a1a; --card-foreground: #12263f;
--popover: #ffffff; --popover: #ffffff;
--popover-foreground: #1a1a1a; --popover-foreground: #12263f;
--primary: #5b5ff9; --primary: #0f74d8;
--primary-foreground: #ffffff; --primary-foreground: #ffffff;
--secondary: #f1f3f5; --secondary: #d9e7f4;
--secondary-foreground: #1a1a1a; --secondary-foreground: #163b62;
--muted: #f8f9fa; --muted: #f4f8fc;
--muted-foreground: #6c757d; --muted-foreground: #5d6f84;
--accent: #e7f5ff; --accent: #e5f1fb;
--accent-foreground: #1971c2; --accent-foreground: #0f4d8f;
--destructive: #f03e3e; --destructive: #d95050;
--destructive-foreground: #ffffff; --destructive-foreground: #ffffff;
--border: rgba(0, 0, 0, 0.06); --border: rgba(16, 74, 132, 0.12);
--input: rgba(0, 0, 0, 0.06); --input: rgba(16, 74, 132, 0.12);
--input-background: #ffffff; --input-background: rgba(255, 255, 255, 0.92);
--switch-background: #dee2e6; --switch-background: #c3d4e5;
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-normal: 400; --font-weight-normal: 400;
--ring: #5b5ff9; --ring: #0f74d8;
--chart-1: #5b5ff9; --chart-1: #0f74d8;
--chart-2: #22b8cf; --chart-2: #28a5ea;
--chart-3: #51cf66; --chart-3: #2fbe93;
--chart-4: #ff6b6b; --chart-4: #ff8b52;
--chart-5: #fcc419; --chart-5: #f2c84b;
--radius: 0.75rem; --radius: 1rem;
--sidebar: #ffffff; --sidebar: rgba(255, 255, 255, 0.86);
--sidebar-foreground: #1a1a1a; --sidebar-foreground: #12263f;
--sidebar-primary: #5b5ff9; --sidebar-primary: #0f74d8;
--sidebar-primary-foreground: #ffffff; --sidebar-primary-foreground: #ffffff;
--sidebar-accent: #f8f9fa; --sidebar-accent: #eff5fb;
--sidebar-accent-foreground: #1a1a1a; --sidebar-accent-foreground: #123c67;
--sidebar-border: rgba(0, 0, 0, 0.06); --sidebar-border: rgba(16, 74, 132, 0.12);
--sidebar-ring: #5b5ff9; --sidebar-ring: #0f74d8;
} }
.dark { .dark {
...@@ -126,6 +126,26 @@ ...@@ -126,6 +126,26 @@
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-family: var(--font-sans-brand);
min-height: 100vh;
background-image:
radial-gradient(circle at top left, rgba(30, 165, 255, 0.18), transparent 26%),
radial-gradient(circle at bottom right, rgba(12, 59, 106, 0.12), transparent 24%),
linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.72));
background-attachment: fixed;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(15, 87, 157, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(15, 87, 157, 0.035) 1px, transparent 1px);
background-size: 28px 28px;
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.7), transparent 92%);
z-index: -1;
} }
/** /**
...@@ -178,4 +198,9 @@ ...@@ -178,4 +198,9 @@
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
line-height: 1.5; line-height: 1.5;
} }
::selection {
background: rgba(15, 116, 216, 0.18);
color: var(--foreground);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment