fix(welcome): 品牌首页 - 圆角化下一幕按钮并移除底部进度细线
独立首页未继承工作台样式,原生按钮和全宽进度细线破坏电影卷轴观感;同时把贡献者名单接到首页。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
36
web/src/pages/welcome/contributors-card.tsx
Normal file
36
web/src/pages/welcome/contributors-card.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { ArrowUpRight, UsersRound } from "lucide-react";
|
||||
|
||||
import { CometCard } from "@/components/ui/aceternity/comet-card";
|
||||
|
||||
import { welcomeContributors } from "./contributors";
|
||||
|
||||
const githubContributors = "https://github.com/ddcat-ai/open-ai-canvas#贡献者与团队";
|
||||
|
||||
export function WelcomeContributorsCard() {
|
||||
if (!welcomeContributors.length) return null;
|
||||
|
||||
return (
|
||||
<section className="welcome-contributors" aria-labelledby="welcome-contributors-title">
|
||||
<CometCard containerClassName="welcome-contributors-perspective" className="welcome-contributors-card" rotateDepth={3.5} translateDepth={3}>
|
||||
<div className="welcome-contributors-heading">
|
||||
<span className="welcome-contributors-kicker"><UsersRound size={15} />Contributors</span>
|
||||
<div>
|
||||
<h3 id="welcome-contributors-title">一起把故事搬上银幕</h3>
|
||||
<p>{welcomeContributors.length} 位创作者参与产品、开发、测试与社区建设</p>
|
||||
</div>
|
||||
<a href={githubContributors} target="_blank" rel="noreferrer" aria-label="在 GitHub README 查看贡献者清单">
|
||||
查看清单<ArrowUpRight size={14} />
|
||||
</a>
|
||||
</div>
|
||||
<ul className="welcome-contributors-list" aria-label="项目贡献者">
|
||||
{welcomeContributors.map((contributor) => (
|
||||
<li key={contributor.name} title={contributor.signature || contributor.name}>
|
||||
<img src={contributor.avatar} alt="" loading="lazy" />
|
||||
<span>{contributor.name}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CometCard>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
45
web/src/pages/welcome/contributors.ts
Normal file
45
web/src/pages/welcome/contributors.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import readmeMarkdown from "../../../../README.md?raw";
|
||||
|
||||
const contributorAvatarModules = import.meta.glob("../../../../assets/user-*", {
|
||||
eager: true,
|
||||
import: "default",
|
||||
query: "?url",
|
||||
}) as Record<string, string>;
|
||||
|
||||
export type WelcomeContributor = {
|
||||
name: string;
|
||||
avatar: string;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
function plainText(value: string) {
|
||||
return value
|
||||
.replace(/<br\s*\/?>/gi, " · ")
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/\[([^\]]+)]\([^)]+\)/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function parseContributors(markdown: string): WelcomeContributor[] {
|
||||
const section = markdown.match(/## 贡献者与团队([\s\S]*?)(?=\n##\s)/)?.[1];
|
||||
if (!section) return [];
|
||||
|
||||
return section
|
||||
.split("\n")
|
||||
.filter((line) => /^\|\s*<img\s/i.test(line))
|
||||
.flatMap((line) => {
|
||||
const columns = line.slice(1, line.lastIndexOf("|")).split("|").map((column) => column.trim());
|
||||
const image = columns[0]?.match(/src="([^"]+)"[^>]*alt="([^"]*)"/i);
|
||||
const name = plainText(columns[1]?.split(/<br\s*\/?>/i)[0] ?? image?.[2] ?? "");
|
||||
const avatarPath = image?.[1];
|
||||
if (!avatarPath || !name) return [];
|
||||
|
||||
const avatar = contributorAvatarModules[`../../../../${avatarPath}`];
|
||||
if (!avatar) return [];
|
||||
|
||||
return [{ name, avatar, signature: plainText(columns[3] ?? "") }];
|
||||
});
|
||||
}
|
||||
|
||||
export const welcomeContributors = parseContributors(readmeMarkdown);
|
||||
@@ -1,5 +1,13 @@
|
||||
import { Component, lazy, Suspense, useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { Button, ConfigProvider, Select, Tabs } from "antd";
|
||||
import { ArrowDown, ArrowRight, ArrowUpRight, Code2, Menu, Pause, Play, X } from "lucide-react";
|
||||
|
||||
import { BrandLogo } from "@/components/brand/brand-logo";
|
||||
import { IconButton } from "@/components/ui/base/buttons";
|
||||
import { getAntThemeConfig } from "@/lib/app-theme";
|
||||
import { useAppearanceStore } from "@/stores/use-appearance-store";
|
||||
|
||||
import { WelcomeContributorsCard } from "./contributors-card";
|
||||
import { chapters, getWelcomeLook, showcases, welcomeLooks, type WelcomeLook } from "./story";
|
||||
import "./welcome.css";
|
||||
|
||||
@@ -8,6 +16,7 @@ const github = "https://github.com/ddcat-ai/open-ai-canvas";
|
||||
|
||||
export default function WelcomePage() {
|
||||
const [look, setLook] = useState(getWelcomeLook);
|
||||
const appearance = useAppearanceStore((state) => state.appearance);
|
||||
const restorePickerFocus = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!restorePickerFocus.current) return;
|
||||
@@ -28,10 +37,14 @@ export default function WelcomePage() {
|
||||
restorePickerFocus.current = true;
|
||||
setLook(next);
|
||||
};
|
||||
return <WelcomeExperience key={look.id} look={look} onLookChange={changeLook} />;
|
||||
return (
|
||||
<ConfigProvider theme={getAntThemeConfig(true, appearance.activeSkin)}>
|
||||
<WelcomeExperience key={look.id} look={look} brandName={appearance.brandName} onLookChange={changeLook} />
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function WelcomeExperience({ look, onLookChange }: { look: WelcomeLook; onLookChange: (id: string) => void }) {
|
||||
function WelcomeExperience({ look, brandName, onLookChange }: { look: WelcomeLook; brandName: string; onLookChange: (id: string) => void }) {
|
||||
const storyRef = useRef<HTMLElement>(null);
|
||||
const progressRef = useRef(0);
|
||||
const [chapter, setChapter] = useState(0);
|
||||
@@ -40,7 +53,7 @@ function WelcomeExperience({ look, onLookChange }: { look: WelcomeLook; onLookCh
|
||||
const [failed, setFailed] = useState(false);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [menu, setMenu] = useState(false);
|
||||
const [showcase, setShowcase] = useState(0);
|
||||
const [showcase, setShowcase] = useState(1);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
@@ -87,18 +100,19 @@ function WelcomeExperience({ look, onLookChange }: { look: WelcomeLook; onLookCh
|
||||
<div className="welcome-page">
|
||||
<a className="welcome-skip" href="#workbench">前往工作台介绍</a>
|
||||
<header className="welcome-header">
|
||||
<a className="welcome-brand" href="/welcome" aria-label="影策首页"><img src="/logo.svg" alt="" />影策</a>
|
||||
<a className="welcome-brand" href="/welcome" aria-label={`${brandName}首页`}>
|
||||
<BrandLogo theme="dark" className="welcome-brand-logo" alt="" fallback={<span className="welcome-brand-logo is-fallback" />} />
|
||||
{brandName}
|
||||
</a>
|
||||
<nav className={menu ? "welcome-nav is-open" : "welcome-nav"} aria-label="首页导航">
|
||||
<a href="#workbench" onClick={() => setMenu(false)}>工作台</a>
|
||||
<a href={github} target="_blank" rel="noreferrer">GitHub<ArrowUpRight size={13} /></a>
|
||||
</nav>
|
||||
<a className="welcome-header-cta" href="/create">开始创作<ArrowUpRight size={16} /></a>
|
||||
<button className="welcome-icon mobile-menu" aria-label={menu ? "关闭菜单" : "打开菜单"} aria-expanded={menu} onClick={() => setMenu(!menu)}>{menu ? <X /> : <Menu />}</button>
|
||||
<Button className="welcome-header-cta" type="primary" href="/create" icon={<ArrowUpRight size={16} />} iconPlacement="end">开始创作</Button>
|
||||
<IconButton className="welcome-icon mobile-menu" variant="ghost" size="lg" icon={menu ? X : Menu} aria-label={menu ? "关闭菜单" : "打开菜单"} aria-expanded={menu} onClick={() => setMenu(!menu)} />
|
||||
</header>
|
||||
<aside className="welcome-look-picker" aria-label="首页素材版本">
|
||||
<select id="welcome-look-select" aria-label="素材版本" value={look.id} onChange={(event) => onLookChange(event.target.value)}>
|
||||
{welcomeLooks.map((item) => <option key={item.id} value={item.id}>{item.label}</option>)}
|
||||
</select>
|
||||
<Select id="welcome-look-select" aria-label="素材版本" value={look.id} options={welcomeLooks.map((item) => ({ label: item.label, value: item.id }))} onChange={onLookChange} popupMatchSelectWidth={false} />
|
||||
{look.credit && <a href={`/welcome/credits.html#${look.id}`} target="_blank" rel="noreferrer" title={look.credit}>演示素材 · CC BY<ArrowUpRight size={12} /></a>}
|
||||
</aside>
|
||||
|
||||
@@ -111,28 +125,27 @@ function WelcomeExperience({ look, onLookChange }: { look: WelcomeLook; onLookCh
|
||||
{chapters.map((item, index) => (
|
||||
<section key={item.id} className={`welcome-chapter ${index === 0 ? "welcome-opening" : ""} ${index === chapter ? "is-active" : ""}`} aria-hidden={index !== chapter}>
|
||||
{index === 0 ? <h1>{item.title}</h1> : <h2>{item.title.split(",").map((part, partIndex, parts) => <span key={part}>{part}{partIndex < parts.length - 1 ? "," : ""}</span>)}</h2>}
|
||||
{"subtitle" in item && <><p className="welcome-subtitle">{item.subtitle}</p>{look.video && <button className="welcome-film-link" onClick={() => setPlaying(true)} tabIndex={chapter === 0 ? 0 : -1}><span className="welcome-play-mark"><Play size={17} fill="currentColor" /></span><span>观看片段</span></button>}</>}
|
||||
{"subtitle" in item && <><p className="welcome-subtitle">{item.subtitle}</p>{look.video && <Button type="text" className="welcome-film-link" onClick={() => setPlaying(true)} tabIndex={chapter === 0 ? 0 : -1} icon={<span className="welcome-play-mark"><Play size={17} fill="currentColor" /></span>}>观看片段</Button>}</>}
|
||||
</section>
|
||||
))}
|
||||
<div className="welcome-stage-bottom">
|
||||
<button className="welcome-next" aria-label={chapter === 5 ? "进入创作现场" : "下一幕"} onClick={() => chapter < 5 ? jumpTo(chapter + 1) : document.getElementById("workbench")?.scrollIntoView({ behavior: reduced ? "instant" : "smooth" })}><ArrowDown size={20} /></button>
|
||||
<IconButton className="welcome-next" variant="ghost" size="lg" icon={ArrowDown} aria-label={chapter === 5 ? "进入创作现场" : "下一幕"} onClick={() => chapter < 5 ? jumpTo(chapter + 1) : document.getElementById("workbench")?.scrollIntoView({ behavior: reduced ? "instant" : "smooth" })} />
|
||||
<nav className="welcome-chapter-nav" aria-label="故事章节">{chapters.map((item, index) => <button key={item.id} className={chapter === index ? "is-current" : ""} aria-label={item.label} title={item.label} aria-current={chapter === index ? "step" : undefined} onClick={() => jumpTo(index)}><span /></button>)}</nav>
|
||||
<button className="welcome-icon" title={paused || staticScene ? "播放动画" : "暂停动画"} aria-label={paused || staticScene ? "播放动画" : "暂停动画"} aria-pressed={paused || staticScene} disabled={staticScene} onClick={() => setPaused(!paused)}>{paused || staticScene ? <Play size={16} /> : <Pause size={16} />}</button>
|
||||
<IconButton className="welcome-icon" variant="ghost" icon={paused || staticScene ? Play : Pause} title={paused || staticScene ? "播放动画" : "暂停动画"} aria-label={paused || staticScene ? "播放动画" : "暂停动画"} aria-pressed={paused || staticScene} disabled={staticScene} onClick={() => setPaused(!paused)} />
|
||||
</div>
|
||||
<div className="welcome-scroll-progress" aria-hidden="true" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="workbench" className="welcome-workbench">
|
||||
<div className="welcome-section-heading"><h2>让想象,有处落笔。</h2></div>
|
||||
<div className="welcome-workbench-bar"><div role="tablist" aria-label="工作台预览">{showcases.map((item, index) => <button key={item.name} role="tab" id={`preview-tab-${index}`} aria-controls="workbench-preview" aria-selected={showcase === index} tabIndex={showcase === index ? 0 : -1} onClick={() => setShowcase(index)} onKeyDown={(event) => { if (event.key === "ArrowRight" || event.key === "ArrowLeft") { event.preventDefault(); const next = (index + (event.key === "ArrowRight" ? 1 : 2)) % 3; setShowcase(next); document.getElementById(`preview-tab-${next}`)?.focus(); } }}>{item.name}</button>)}</div><a href={active.href}>进入{active.name}<ArrowUpRight size={16} /></a></div>
|
||||
<div id="workbench-preview" role="tabpanel" aria-labelledby={`preview-tab-${showcase}`} className="welcome-workbench-preview"><img src={active.image} alt={`影策${active.name}界面`} loading="lazy" /></div>
|
||||
<div className="welcome-workbench-bar"><Tabs activeKey={String(showcase)} aria-label="工作台预览" items={showcases.map((item, index) => ({ key: String(index), label: item.name }))} onChange={(key) => setShowcase(Number(key))} /><Button type="link" href={active.href} icon={<ArrowUpRight size={16} />} iconPlacement="end">进入{active.name}</Button></div>
|
||||
<div id="workbench-preview" role="tabpanel" className="welcome-workbench-preview"><img src={active.image} alt={`${brandName}${active.name}界面`} loading="lazy" /></div>
|
||||
<div className="welcome-workbench-caption"><p>{active.detail}</p></div>
|
||||
</section>
|
||||
|
||||
<section className="welcome-ending"><h2>你的故事,<br />现在开始。</h2><a className="welcome-primary" href="/create">开始创作<ArrowRight size={20} /></a><a className="welcome-source" href={github} target="_blank" rel="noreferrer"><Code2 size={16} />GitHub<ArrowUpRight size={14} /></a></section>
|
||||
<section className="welcome-ending"><h2>你的故事,<br />现在开始。</h2><Button className="welcome-primary" type="primary" size="large" href="/create" icon={<ArrowRight size={20} />} iconPlacement="end">开始创作</Button><a className="welcome-source" href={github} target="_blank" rel="noreferrer"><Code2 size={16} />GitHub<ArrowUpRight size={14} /></a><WelcomeContributorsCard /></section>
|
||||
</main>
|
||||
<footer className="welcome-footer"><a href="/welcome">影策</a><span>开源 AI 影视创作工作台</span><a href={`${github}/blob/main/LICENSE`} target="_blank" rel="noreferrer">Open Source · MIT License<ArrowUpRight size={12} /></a></footer>
|
||||
<footer className="welcome-footer"><a href="/welcome">{brandName}</a><span>开源 AI 影视创作工作台</span><a href={`${github}/blob/main/LICENSE`} target="_blank" rel="noreferrer">Open Source · MIT License<ArrowUpRight size={12} /></a></footer>
|
||||
{look.credit && <div className="welcome-media-credit"><a href={`/welcome/credits.html#${look.id}`} target="_blank" rel="noreferrer">{look.credit} · 署名与许可<ArrowUpRight size={12} /></a></div>}
|
||||
{playing && look.video && <FilmDialog look={look} onClose={() => setPlaying(false)} videoRef={videoRef} />}
|
||||
</div>
|
||||
@@ -142,7 +155,7 @@ function WelcomeExperience({ look, onLookChange }: { look: WelcomeLook; onLookCh
|
||||
function FilmDialog({ look, onClose, videoRef }: { look: WelcomeLook; onClose: () => void; videoRef: React.RefObject<HTMLVideoElement | null> }) {
|
||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||
useEffect(() => { const dialog = dialogRef.current; dialog?.showModal(); return () => dialog?.close(); }, []);
|
||||
return <dialog ref={dialogRef} className="welcome-film-dialog" aria-label={`${look.title}影片片段`} onCancel={onClose} onClick={(event) => { if (event.target === event.currentTarget) onClose(); }}><div><button autoFocus className="welcome-icon" aria-label="关闭影片" onClick={onClose}><X /></button><video ref={videoRef} src={look.video} poster={look.frames[0]} controls autoPlay muted playsInline /><p>{look.credit ?? look.title}</p></div></dialog>;
|
||||
return <dialog ref={dialogRef} className="welcome-film-dialog" aria-label={`${look.title}影片片段`} onCancel={onClose} onClick={(event) => { if (event.target === event.currentTarget) onClose(); }}><div><IconButton autoFocus className="welcome-icon" variant="ghost" size="lg" icon={X} aria-label="关闭影片" onClick={onClose} /><video ref={videoRef} src={look.video} poster={look.frames[0]} controls autoPlay muted playsInline /><p>{look.credit ?? look.title}</p></div></dialog>;
|
||||
}
|
||||
|
||||
class SceneBoundary extends Component<{ children: ReactNode; onError: () => void }, { failed: boolean }> {
|
||||
|
||||
@@ -27,21 +27,24 @@ body:has(.welcome-page) { margin: 0; background: #080808; }
|
||||
.welcome-page button { font: inherit; cursor: pointer; color: inherit; }
|
||||
.welcome-page a { text-decoration: none; color: inherit; }
|
||||
.welcome-page a:focus-visible, .welcome-page button:focus-visible { outline: 2px solid var(--welcome-accent); outline-offset: 6px; }
|
||||
.welcome-page select:focus-visible { outline: 2px solid var(--welcome-accent); outline-offset: 4px; }
|
||||
.welcome-page button:disabled { cursor: default; opacity: .4; }
|
||||
.welcome-page svg { flex-shrink: 0; }
|
||||
.welcome-skip { position: fixed; top: -100px; left: 20px; background: var(--welcome-paper); color: var(--welcome-black) !important; padding: 12px; z-index: 50; }
|
||||
.welcome-skip:focus { top: 12px; }
|
||||
.welcome-header { height: var(--welcome-header-height); position: fixed; inset: 0 0 auto; z-index: var(--welcome-z-navigation); display: flex; align-items: center; gap: 42px; padding: 0 var(--welcome-gutter); background: linear-gradient(#080808cc, #08080800); }
|
||||
.welcome-brand { display: flex; align-items: center; gap: 12px; font-size: 18px; font-weight: 500; white-space: nowrap; }
|
||||
.welcome-brand img { width: 26px; height: 26px; filter: grayscale(1) brightness(0) invert(1); }
|
||||
.welcome-brand-logo { display: block; width: 26px; height: 26px; object-fit: contain; }
|
||||
.welcome-brand-logo.is-fallback { background: currentColor; mask: url("/logo.svg") center / contain no-repeat; -webkit-mask: url("/logo.svg") center / contain no-repeat; }
|
||||
.welcome-nav { margin-left: auto; display: flex; align-items: center; gap: 34px; }
|
||||
.welcome-nav a { display: inline-flex; align-items: center; gap: 5px; color: var(--welcome-paper); font-size: 13px; transition: opacity 180ms; }
|
||||
.welcome-nav a:hover { opacity: .65; }
|
||||
.welcome-header-cta { display: flex; align-items: center; justify-content: center; gap: 24px; padding: 11px 19px; background: var(--welcome-paper); color: var(--welcome-black) !important; border-radius: var(--welcome-radius); font-size: 12px; }
|
||||
.welcome-header-cta:hover { background: var(--welcome-accent); }
|
||||
.welcome-header-cta.ant-btn { display: flex; align-items: center; justify-content: center; gap: 24px; height: 40px; padding: 0 19px; border-radius: var(--welcome-radius); color: var(--welcome-black) !important; font-size: 12px; }
|
||||
.welcome-look-picker { position: fixed; top: calc(var(--welcome-header-height) + 6px); right: var(--welcome-gutter); z-index: calc(var(--welcome-z-navigation) - 1); width: 164px; }
|
||||
.welcome-look-picker select { display: block; width: 100%; height: 38px; padding: 0 8px; border: 1px solid var(--welcome-border); border-radius: var(--welcome-radius); background: var(--welcome-black); color: var(--welcome-paper); font: inherit; font-size: 12px; color-scheme: dark; cursor: pointer; }
|
||||
.welcome-look-picker .ant-select { display: flex; width: 100%; align-items: center; border-color: var(--welcome-border) !important; background: color-mix(in srgb, var(--welcome-black) 82%, transparent) !important; color: var(--welcome-paper); font-size: 12px; white-space: nowrap; backdrop-filter: blur(14px); }
|
||||
.welcome-look-picker .ant-select-content { min-width: 0; }
|
||||
.welcome-look-picker .ant-select-suffix { display: inline-flex; flex: none; align-items: center; margin-left: 8px; color: var(--welcome-paper); }
|
||||
.welcome-look-picker .ant-select-selector { border-color: var(--welcome-border) !important; background: transparent !important; }
|
||||
.welcome-look-picker .ant-select-selection-item, .welcome-look-picker .ant-select-arrow { color: var(--welcome-paper); }
|
||||
.welcome-look-picker > a { display: flex; align-items: center; justify-content: flex-end; gap: 4px; padding: 7px 0; font-size: 10px; color: var(--welcome-muted); text-shadow: 0 1px 4px var(--welcome-black); }
|
||||
.welcome-media-credit { padding: 0 var(--welcome-gutter) 26px; color: var(--welcome-muted); font-size: 11px; }
|
||||
.welcome-media-credit a { display: inline-flex; align-items: center; gap: 5px; }
|
||||
@@ -69,34 +72,72 @@ body:has(.welcome-page) { margin: 0; background: #080808; }
|
||||
.chapter-2 .welcome-chapter { text-align: right; }
|
||||
.chapter-5 .welcome-chapter { top: 17%; bottom: auto; text-align: center; }
|
||||
.chapter-5 .welcome-chapter h2 { font-size: 58px; }
|
||||
.welcome-film-link { position: absolute; right: 0; bottom: 0; border: 0; background: none; padding: 10px 0 10px 16px; display: flex; align-items: center; gap: 15px; font-size: 12px !important; pointer-events: auto; }
|
||||
.welcome-film-link.ant-btn { position: absolute; right: 0; bottom: 0; display: flex; align-items: center; gap: 15px; height: auto; padding: 10px 0 10px 16px; color: var(--welcome-paper); font-size: 12px; pointer-events: auto; }
|
||||
.welcome-film-link.ant-btn:hover { color: var(--welcome-paper) !important; background: transparent !important; }
|
||||
.welcome-play-mark { display: grid; place-items: center; width: 64px; height: 64px; border: 1px solid #ffffff70; border-radius: 50%; transition: background 180ms, transform 180ms; }
|
||||
.welcome-film-link:hover .welcome-play-mark { background: #ffffff20; transform: scale(1.06); }
|
||||
.welcome-stage-bottom { position: absolute; inset: auto var(--welcome-gutter) 25px; display: flex; align-items: center; justify-content: space-between; gap: 20px; }
|
||||
.welcome-next { border: 0; background: none; display: grid; place-items: center; width: 44px; height: 44px; }
|
||||
.welcome-next {
|
||||
appearance: none;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 0;
|
||||
color: var(--welcome-paper);
|
||||
background: #ffffff18;
|
||||
border: 1px solid #ffffff4d;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(16px);
|
||||
transition: background 180ms, border-color 180ms, transform 180ms;
|
||||
}
|
||||
.welcome-next:hover {
|
||||
background: #ffffff2e;
|
||||
border-color: #ffffff8a;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
.welcome-next:active { transform: translateY(3px) scale(.96); }
|
||||
.welcome-next svg { width: 18px; height: 18px; }
|
||||
.welcome-chapter-nav { display: flex; gap: 4px; }
|
||||
.welcome-chapter-nav button { display: grid; place-items: center; border: 0; background: none; width: 32px; height: 44px; padding: 0; }
|
||||
.welcome-chapter-nav button span { display: block; width: 22px; height: 1px; background: #ffffff48; transition: background .2s, height .2s; }
|
||||
.welcome-chapter-nav .is-current span { height: 2px; background: var(--welcome-paper); }
|
||||
.welcome-chapter-nav button:hover span { background: var(--welcome-paper); }
|
||||
.welcome-scroll-progress { position: absolute; bottom: 0; left: 0; width: 100%; height: 1px; background: #ffffff50; transform-origin: left; transform: scaleX(var(--story-progress, 0)); }
|
||||
.welcome-workbench { position: relative; padding: 100px var(--welcome-gutter) 70px; max-width: calc(var(--welcome-content) + var(--welcome-gutter) * 2); margin: auto; scroll-margin-top: var(--welcome-header-height); }
|
||||
.welcome-section-heading { margin-bottom: 50px; }
|
||||
.welcome-section-heading h2 { font-size: 56px; font-weight: 400; line-height: 1.4; }
|
||||
.welcome-workbench-bar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 26px; }
|
||||
.welcome-workbench-bar [role="tablist"] { display: flex; gap: 32px; }
|
||||
.welcome-workbench-bar button { background: none; border: 0; padding: 12px 0; border-bottom: 1px solid transparent; color: var(--welcome-muted); }
|
||||
.welcome-workbench-bar button[aria-selected="true"] { border-bottom-color: var(--welcome-paper); color: var(--welcome-paper); }
|
||||
.welcome-workbench-bar a { display: flex; gap: 9px; align-items: center; font-size: 12px; color: var(--welcome-muted); }
|
||||
.welcome-workbench-bar .ant-tabs, .welcome-workbench-bar .ant-tabs-nav { margin: 0; }
|
||||
.welcome-workbench-bar .ant-tabs-nav::before { border-bottom: 0; }
|
||||
.welcome-workbench-bar .ant-tabs-nav-list { gap: 32px; }
|
||||
.welcome-workbench-bar .ant-tabs-tab { margin: 0; padding: 12px 0; color: var(--welcome-muted); }
|
||||
.welcome-workbench-bar .ant-tabs-tab:hover, .welcome-workbench-bar .ant-tabs-tab-active .ant-tabs-tab-btn { color: var(--welcome-paper) !important; }
|
||||
.welcome-workbench-bar .ant-tabs-ink-bar { background: var(--welcome-paper); }
|
||||
.welcome-workbench-bar > .ant-btn { display: flex; gap: 9px; align-items: center; height: auto; padding: 0; color: var(--welcome-muted); font-size: 12px; }
|
||||
.welcome-workbench-bar > .ant-btn:hover { color: var(--welcome-paper) !important; }
|
||||
.welcome-workbench-preview { aspect-ratio: 16/9; background: #141414; overflow: hidden; border-radius: var(--welcome-radius); }
|
||||
.welcome-workbench-preview img { width: 100%; height: 100%; object-fit: contain; }
|
||||
.welcome-workbench-caption { padding: 24px 0 0; }
|
||||
.welcome-workbench-caption p { color: var(--welcome-muted); font-size: 13px; }
|
||||
.welcome-ending { text-align: center; padding: 120px 24px 125px; }
|
||||
.welcome-ending h2 { font-weight: 400; font-size: 92px; line-height: 1.2; margin-bottom: 42px; }
|
||||
.welcome-primary { display: inline-flex; align-items: center; justify-content: center; gap: 42px; height: 52px; padding: 0 27px; background: var(--welcome-paper); color: var(--welcome-black) !important; border-radius: var(--welcome-radius); font-size: 14px; font-weight: 500; transition: background 180ms, transform 180ms; }
|
||||
.welcome-primary:hover { background: var(--welcome-accent); transform: translateY(-2px); }
|
||||
.welcome-primary.ant-btn { display: inline-flex; align-items: center; justify-content: center; gap: 42px; height: 52px; padding: 0 27px; border-radius: var(--welcome-radius); color: var(--welcome-black) !important; font-size: 14px; font-weight: 500; transition: background 180ms, transform 180ms; }
|
||||
.welcome-primary.ant-btn:hover { transform: translateY(-2px); }
|
||||
.welcome-source { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 27px; font-size: 12px; color: var(--welcome-muted) !important; }
|
||||
.welcome-contributors { width: 100%; max-width: 1280px; margin: 48px auto 0; text-align: left; }
|
||||
.welcome-contributors-perspective { height: auto; perspective: 1200px; }
|
||||
.welcome-contributors-card { overflow: hidden; border: 1px solid var(--welcome-border); border-radius: 12px; background: radial-gradient(circle at 12% 0%, #ffffff13, transparent 36%), linear-gradient(135deg, #151515 0%, #0d0d0d 68%); box-shadow: 0 28px 80px #0000005c; padding: 32px; transform-style: preserve-3d; }
|
||||
.welcome-contributors-heading { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 24px; padding-bottom: 28px; border-bottom: 1px solid var(--welcome-border); }
|
||||
.welcome-contributors-kicker { display: inline-flex; align-items: center; gap: 8px; color: var(--welcome-muted); font-size: 10px; letter-spacing: .16em; text-transform: uppercase; }
|
||||
.welcome-contributors-heading h3 { font-size: 24px; font-weight: 500; line-height: 1.35; }
|
||||
.welcome-contributors-heading p { margin-top: 4px; color: var(--welcome-muted); font-size: 12px; }
|
||||
.welcome-contributors-heading > a { display: inline-flex; align-items: center; gap: 6px; color: var(--welcome-muted); font-size: 12px; transition: color 180ms; }
|
||||
.welcome-contributors-heading > a:hover { color: var(--welcome-paper); }
|
||||
.welcome-contributors-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 18px 24px; margin: 28px 0 0; padding: 0; list-style: none; }
|
||||
.welcome-contributors-list li { display: flex; min-width: 0; align-items: center; gap: 10px; color: var(--welcome-muted); font-size: 12px; transition: color 180ms, transform 180ms; }
|
||||
.welcome-contributors-list li:hover { color: var(--welcome-paper); transform: translateY(-1px); }
|
||||
.welcome-contributors-list img { width: 42px; height: 42px; flex: none; border: 1px solid #ffffff24; border-radius: 50%; object-fit: cover; background: #1b1b1b; }
|
||||
.welcome-contributors-list span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.welcome-footer { display: flex; justify-content: space-between; gap: 20px; align-items: center; padding: 25px var(--welcome-gutter); color: var(--welcome-muted); font-size: 11px; }
|
||||
.welcome-footer > a:first-child { color: var(--welcome-paper); font-size: 15px; font-weight: 500; }
|
||||
.welcome-footer a:last-child { display: flex; gap: 5px; align-items: center; }
|
||||
@@ -113,7 +154,7 @@ body:has(.welcome-page) { margin: 0; background: #080808; }
|
||||
.welcome-header { gap: 14px; }
|
||||
.welcome-look-picker { width: 148px; }
|
||||
.welcome-brand { font-size: 15px; gap: 8px; }
|
||||
.welcome-brand img { width: 22px; height: 22px; }
|
||||
.welcome-brand-logo { width: 22px; height: 22px; }
|
||||
.welcome-header-cta { margin-left: auto; padding: 9px 12px; gap: 10px; font-size: 11px; }
|
||||
.mobile-menu { display: grid; width: 34px; height: 44px; }
|
||||
.welcome-nav { display: none; position: absolute; top: var(--welcome-header-height); left: 0; right: 0; padding: 25px; background: var(--welcome-black); }
|
||||
@@ -140,12 +181,19 @@ body:has(.welcome-page) { margin: 0; background: #080808; }
|
||||
.welcome-workbench { padding-top: 68px; padding-bottom: 40px; }
|
||||
.welcome-section-heading { margin-bottom: 28px; }
|
||||
.welcome-section-heading h2 { font-size: 32px; }
|
||||
.welcome-workbench-bar [role="tablist"] { gap: 21px; }
|
||||
.welcome-workbench-bar button { font-size: 12px; }
|
||||
.welcome-workbench-bar > a { font-size: 0; gap: 0; }
|
||||
.welcome-workbench-bar .ant-tabs-nav-list { gap: 21px; }
|
||||
.welcome-workbench-bar .ant-tabs-tab { font-size: 12px; }
|
||||
.welcome-workbench-bar > .ant-btn { width: 32px; padding: 0; font-size: 0; gap: 0; }
|
||||
.welcome-workbench-caption p { font-size: 12px; }
|
||||
.welcome-ending { padding: 65px 24px 70px; }
|
||||
.welcome-ending h2 { font-size: 58px; margin-bottom: 30px; }
|
||||
.welcome-contributors { margin-top: 40px; }
|
||||
.welcome-contributors-card { padding: 24px; }
|
||||
.welcome-contributors-heading { grid-template-columns: 1fr auto; gap: 12px; }
|
||||
.welcome-contributors-kicker { grid-column: 1 / -1; }
|
||||
.welcome-contributors-heading p { max-width: 32ch; }
|
||||
.welcome-contributors-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 12px; margin-top: 24px; }
|
||||
.welcome-contributors-list img { width: 36px; height: 36px; }
|
||||
.welcome-footer { flex-wrap: wrap; gap: 12px; font-size: 10px; }
|
||||
.welcome-footer > span { display: none; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user