Files
2026-07-13 21:35:40 +08:00

458 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{PRODUCT_NAME}} — 即将推出</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family={{DISPLAY_FONT_URL}}:wght@700;800&family={{BODY_FONT_URL}}:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: {{BG_EXPRESSION}};
--fg: {{FG_EXPRESSION}};
--accent: {{ACCENT_EXPRESSION}};
--deco: {{DECO_EXPRESSION}};
--deco-stripe: {{STRIPE_EXPRESSION}};
--input-border: {{BORDER_EXPRESSION}};
--success: {{SUCCESS_EXPRESSION}};
--btn-label: {{BTN_LABEL_EXPRESSION}};
--ticker-bg: {{TICKER_BG_EXPRESSION}};
--ticker-fg: {{TICKER_FG_EXPRESSION}};
--deco-stroke: {{DECO_STROKE_EXPRESSION}};
--logo-shadow: {{LOGO_SHADOW_EXPRESSION}};
--logo-fg: {{LOGO_FG_EXPRESSION}};
--font-body: {{BODY_FONT_CSS}}, sans-serif;
--font-display: {{DISPLAY_FONT_CSS}}, serif;
}
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
overflow-x: hidden;
}
body {
font-family: var(--font-body);
background-color: var(--bg);
color: var(--fg);
display: flex;
flex-direction: column;
min-height: 100svh;
overflow-x: hidden;
overflow-y: auto;
}
/* Logo — reusable component */
.logo {
position: absolute;
top: 20px;
left: 24px;
display: flex;
align-items: center;
gap: 12px;
z-index: 10;
}
.logo-container {
width: 48px;
height: 48px;
flex-shrink: 0;
border-radius: 50%;
background: var(--accent);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
color: var(--logo-fg);
font-family: var(--font-display);
font-size: 20px;
font-weight: 700;
letter-spacing: -0.02em;
}
.logo-container svg {
width: 100%;
height: 100%;
object-fit: contain;
}
.logo-text {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.logo-name {
font-family: var(--font-display);
font-weight: 700;
font-size: 16px;
color: var(--fg);
}
.logo-dot {
font-size: 8px;
color: var(--accent);
opacity: 0.6;
}
/* Upper section — hero */
.upper {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 60px 24px 40px;
position: relative;
z-index: 2;
}
.headline {
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(48px, 7vw, 80px);
letter-spacing: -0.02em;
line-height: 1.1;
color: var(--fg);
max-width: 900px;
margin: 0 auto 18px;
}
.subtext {
font-size: clamp(14px, 1.3vw, 16px);
font-weight: 400;
line-height: 1.5;
color: var(--fg);
opacity: 0.75;
max-width: 480px;
margin-bottom: 40px;
}
/* Form */
.form-row {
display: flex;
gap: 8px;
width: 100%;
max-width: 560px;
justify-content: center;
}
.form-row input {
flex: 1;
min-width: 0;
padding: 14px 16px;
font-family: var(--font-body);
font-size: 14px;
color: var(--fg);
background: transparent;
border: 1.5px solid var(--input-border);
border-radius: 3px;
outline: none;
transition: border-color 0.2s;
}
.form-row input::placeholder {
color: var(--input-border);
opacity: 0.7;
}
.form-row input:focus {
border-color: var(--fg);
outline: 2px solid var(--fg);
outline-offset: 2px;
}
.form-row button {
flex-shrink: 0;
padding: 14px 24px;
font-family: var(--font-body);
font-size: 14px;
font-weight: 600;
color: var(--btn-label);
background: var(--fg);
border: none;
border-radius: 2px;
cursor: pointer;
white-space: nowrap;
transition: opacity 0.15s;
}
.form-row button:hover {
opacity: 0.85;
}
.success-msg {
display: none;
margin-top: 16px;
font-size: 14px;
font-weight: 500;
color: var(--success);
}
.success-msg.visible {
display: block;
}
/* Decoration section */
.deco-section {
flex: 1;
position: relative;
overflow: hidden;
min-height: 25vh;
display: flex;
flex-direction: column;
}
/* Coil pattern */
.coil-row {
display: block;
width: 100%;
height: 60px;
flex-shrink: 0;
background: var(--bg);
}
/* Accent stripe */
.stripe {
width: 100%;
height: 12px;
background: var(--deco-stripe);
flex-shrink: 0;
}
/* Floor with grid */
.grid-floor {
flex: 1;
position: relative;
background-color: var(--deco);
min-height: 80px;
}
.grid-svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* Ticker ribbon */
.ticker-wrap {
width: 100%;
overflow: hidden;
background: var(--ticker-bg);
padding: 10px 0;
flex-shrink: 0;
}
.ticker-track {
display: flex;
gap: 24px;
white-space: nowrap;
animation: ticker-scroll 20s linear infinite;
}
.ticker-unit {
font-family: var(--font-body);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ticker-fg);
flex-shrink: 0;
display: flex;
align-items: center;
gap: 12px;
margin-right: -12px;
}
.ticker-star {
color: var(--deco-stripe);
margin-right: 4px;
}
@keyframes ticker-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
.ticker-track {
animation: none;
}
}
/* Responsive */
@media (max-width: 600px) {
.logo {
left: 16px;
top: 16px;
}
.logo-container {
width: 40px;
height: 40px;
}
.logo-name {
font-size: 13px;
}
.upper {
padding: 50px 20px 30px;
}
.headline {
margin: 0 auto 12px;
}
.subtext {
margin-bottom: 30px;
}
.form-row {
flex-direction: column;
align-items: stretch;
}
.form-row input,
.form-row button {
width: 100%;
}
}
</style>
</head>
<body>
<!-- Logo placeholder — replace or customize -->
<div class="logo">
<div class="logo-container" data-od-id="logo-mark">
{{LOGO_MARK}}
</div>
<div class="logo-text">
<span class="logo-name" data-od-id="logo-name">{{PRODUCT_NAME}}</span>
<span class="logo-dot">·</span>
</div>
</div>
<!-- Hero section -->
<section class="upper">
<h1 class="headline" data-od-id="headline">{{PRODUCT_NAME}}<br>即将上线</h1>
<p class="subtext" data-od-id="tagline">{{TAGLINE}}</p>
<form class="form-row" id="waitlist-form" data-od-id="form">
<input type="text" name="firstname" placeholder="名字">
<input type="email" name="email" placeholder="工作邮箱" required>
<button type="submit">加入候补名单</button>
</form>
<p class="success-msg" id="success-msg" role="status">您已加入名单,我们会与您联系。</p>
</section>
<!-- Decorative section -->
<div class="deco-section">
<!-- Coil pattern (optional) -->
<svg class="coil-row" viewBox="0 0 1440 60" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" data-od-id="coil">
<g fill="none" style="stroke: var(--deco-stroke);" stroke-width="1.5">
<path d="M 9,60 A 41,30 0 0 1 91,60"/>
<path d="M 94,60 A 41,30 0 0 1 176,60"/>
<path d="M 179,60 A 41,30 0 0 1 261,60"/>
<path d="M 264,60 A 41,30 0 0 1 346,60"/>
<path d="M 349,60 A 41,30 0 0 1 431,60"/>
<path d="M 434,60 A 41,30 0 0 1 516,60"/>
<path d="M 519,60 A 41,30 0 0 1 601,60"/>
<path d="M 604,60 A 41,30 0 0 1 686,60"/>
<path d="M 689,60 A 41,30 0 0 1 771,60"/>
<path d="M 774,60 A 41,30 0 0 1 856,60"/>
<path d="M 859,60 A 41,30 0 0 1 941,60"/>
<path d="M 944,60 A 41,30 0 0 1 1026,60"/>
<path d="M 1029,60 A 41,30 0 0 1 1111,60"/>
<path d="M 1114,60 A 41,30 0 0 1 1196,60"/>
<path d="M 1199,60 A 41,30 0 0 1 1281,60"/>
<path d="M 1284,60 A 41,30 0 0 1 1366,60"/>
<path d="M 1369,60 A 41,30 0 0 1 1451,60"/>
</g>
</svg>
<!-- Accent stripe -->
<div class="stripe" data-od-id="stripe"></div>
<!-- Floor with perspective grid -->
<div class="grid-floor">
<svg class="grid-svg" viewBox="0 0 1280 400" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" data-od-id="grid">
<g style="stroke: var(--deco-stroke);" stroke-width="1" fill="none">
<!-- Radiating lines from center top -->
<line x1="640" y1="0" x2="0" y2="400"/>
<line x1="640" y1="0" x2="128" y2="400"/>
<line x1="640" y1="0" x2="256" y2="400"/>
<line x1="640" y1="0" x2="384" y2="400"/>
<line x1="640" y1="0" x2="512" y2="400"/>
<line x1="640" y1="0" x2="640" y2="400"/>
<line x1="640" y1="0" x2="768" y2="400"/>
<line x1="640" y1="0" x2="896" y2="400"/>
<line x1="640" y1="0" x2="1024" y2="400"/>
<line x1="640" y1="0" x2="1152" y2="400"/>
<line x1="640" y1="0" x2="1280" y2="400"/>
<!-- Horizontal grid lines -->
<line x1="0" y1="80" x2="1280" y2="80"/>
<line x1="0" y1="160" x2="1280" y2="160"/>
<line x1="0" y1="240" x2="1280" y2="240"/>
<line x1="0" y1="320" x2="1280" y2="320"/>
</g>
</svg>
</div>
</div>
<!-- Ticker ribbon -->
<div class="ticker-wrap" aria-hidden="true" data-od-id="ticker">
<div class="ticker-track">
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<!-- Duplicate for seamless loop -->
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
<span class="ticker-unit">即将推出 <span class="ticker-star"></span></span>
</div>
</div>
<script>
document.getElementById('waitlist-form').addEventListener('submit', function(e) {
e.preventDefault();
if (!this.checkValidity()) {
this.reportValidity();
return;
}
this.style.display = 'none';
document.getElementById('success-msg').classList.add('visible');
});
</script>
</body>
</html>