Files
laimiao-web-new/index.html
T

1400 lines
41 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#F5F8EF" />
<title>来秒AI|让每个行业的灵感,都能一秒成片</title>
<meta
name="description"
content="来秒AI 是面向品牌与创作者的一站式 AI 内容工作流,从电商零售到旅游海报,快速产出可发布的图片。"
/>
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='9' fill='%2308482D'/%3E%3Cpath d='M18.6 3 7.5 18h6.7l-1.7 11L24.5 13h-6.6l.7-10Z' fill='%23DAE7C7'/%3E%3C/svg%3E"
/>
<link rel="preconnect" href="https://fonts.googleapis.cn" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.cn/css2?family=Caveat:wght@600&family=Inter+Tight:wght@700&family=Inter:wght@400;500;600&family=Ma+Shan+Zheng&family=Noto+Sans+SC:wght@400;500;700;900&display=swap"
rel="stylesheet"
/>
<style>
:root {
--u: min(0.0694444vw, 0.0925926vh);
--page: #f5f8ef;
--ink: #101410;
--forest: #08482d;
--mint: #dae7c7;
--muted: #5e5e61;
--ease: cubic-bezier(0.22, 1, 0.36, 1);
}
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
margin: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: var(--page);
color: var(--ink);
font-family: Inter, "Noto Sans SC", -apple-system, BlinkMacSystemFont, "PingFang SC",
"Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
color: inherit;
}
.frame {
position: relative;
flex: none;
width: calc(1440 * var(--u));
height: calc(1080 * var(--u));
overflow: hidden;
background: var(--page);
}
.topbar {
position: absolute;
inset: 0 0 auto 0;
height: 0;
z-index: 20;
}
.logo {
position: absolute;
left: calc(37 * var(--u));
top: calc(36 * var(--u));
display: inline-flex;
align-items: center;
gap: calc(9 * var(--u));
color: var(--ink);
font-size: calc(24 * var(--u));
font-weight: 700;
letter-spacing: -0.02em;
text-decoration: none;
white-space: nowrap;
}
.logo svg {
display: block;
width: calc(28 * var(--u));
height: calc(28 * var(--u));
}
.pill {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
font-weight: 500;
text-decoration: none;
transition: background 0.2s ease, transform 0.2s ease;
}
.pill--login {
position: absolute;
left: calc(1211 * var(--u));
top: calc(29 * var(--u));
width: calc(92 * var(--u));
height: calc(40 * var(--u));
border: 1px solid rgba(16, 20, 16, 0.1);
background: #fff;
color: var(--ink);
font-size: calc(15 * var(--u));
}
.pill--signup {
position: absolute;
left: calc(1304 * var(--u));
top: calc(29 * var(--u));
width: calc(97 * var(--u));
height: calc(40 * var(--u));
background: var(--forest);
color: #fff;
font-size: calc(15 * var(--u));
}
.pill--login:hover {
transform: translateY(-1px);
background: #fbfcf8;
}
.pill--signup:hover {
transform: translateY(-1px);
background: #063a24;
}
.headline {
position: absolute;
left: 0;
top: calc(176 * var(--u));
z-index: 10;
width: 100%;
margin: 0;
color: var(--ink);
font-family: "Noto Sans SC", "Inter Tight", Inter, sans-serif;
font-size: calc(72 * var(--u));
font-weight: 700;
line-height: calc(80 * var(--u));
text-align: center;
}
.headline span {
display: block;
white-space: nowrap;
}
.headline span:first-child {
letter-spacing: -0.02em;
}
.headline span:last-child {
letter-spacing: -0.015em;
}
.subline {
position: absolute;
left: 0;
top: calc(360 * var(--u));
z-index: 10;
width: 100%;
margin: 0;
color: var(--muted);
font-size: calc(18 * var(--u));
font-weight: 400;
line-height: calc(27 * var(--u));
text-align: center;
}
.note {
position: absolute;
z-index: 30;
margin: 0;
color: var(--forest);
font-family: "Ma Shan Zheng", Caveat, "Noto Sans SC", cursive;
font-weight: 400;
pointer-events: none;
}
.note--seen {
left: calc(1212 * var(--u));
top: calc(310 * var(--u));
font-size: calc(40 * var(--u));
line-height: 0.96;
transform: rotate(16deg);
transform-origin: left top;
}
.note--zero {
left: calc(492 * var(--u));
top: calc(944 * var(--u));
font-size: calc(32 * var(--u));
line-height: 1;
transform: rotate(24deg);
transform-origin: left top;
}
.doodle {
position: absolute;
z-index: 30;
overflow: visible;
pointer-events: none;
}
.doodle--seen {
left: calc(1170 * var(--u));
top: calc(404 * var(--u));
width: calc(180 * var(--u));
height: calc(150 * var(--u));
}
.doodle--zero {
left: calc(596 * var(--u));
top: calc(955 * var(--u));
width: calc(34 * var(--u));
height: calc(28 * var(--u));
}
.doodle path {
fill: none;
stroke: var(--forest);
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
.doodle--seen path,
.doodle--zero path {
stroke-width: 2.3;
}
.tabs {
position: absolute;
left: 0;
right: 0;
top: calc(462 * var(--u));
z-index: 25;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: calc(9 * var(--u));
}
.tab {
display: inline-flex;
align-items: center;
justify-content: center;
height: calc(36 * var(--u));
padding: 0 calc(18 * var(--u));
border: 1px solid rgba(8, 72, 45, 0.16);
border-radius: 999px;
background: rgba(255, 255, 255, 0.55);
color: var(--forest);
font-family: inherit;
font-size: calc(14.5 * var(--u));
font-weight: 500;
letter-spacing: 0.01em;
white-space: nowrap;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease,
transform 0.2s ease;
}
.tab:hover {
border-color: rgba(8, 72, 45, 0.42);
background: #fff;
transform: translateY(-1px);
}
.tab:active {
transform: scale(0.97);
}
.tab.is-active {
border-color: var(--forest);
background: var(--forest);
color: #fff;
}
.tab:focus-visible {
outline: 2px solid var(--forest);
outline-offset: 2px;
}
.cards {
position: absolute;
inset: 0;
z-index: 5;
pointer-events: none;
opacity: 1;
transition: opacity 0.25s ease;
}
.cards.is-swapping {
opacity: 0.25;
}
.cards.is-dragging .card {
cursor: grabbing;
}
.card {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
background: #dfe7d5;
cursor: grab;
pointer-events: auto;
touch-action: pan-y;
user-select: none;
-webkit-user-select: none;
will-change: transform, width, height, clip-path;
}
.card:active {
cursor: grabbing;
}
.card img {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
filter: saturate(0.86) contrast(1.02) brightness(1.02);
pointer-events: none;
-webkit-user-drag: none;
}
.card::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(218, 231, 199, 0.08) 0%,
rgba(218, 231, 199, 0) 40%,
rgba(8, 72, 45, 0.22) 100%
);
pointer-events: none;
}
.cta {
position: absolute;
left: calc(626 * var(--u));
top: calc(936 * var(--u));
z-index: 30;
display: flex;
align-items: center;
justify-content: center;
width: calc(190 * var(--u));
height: calc(74 * var(--u));
border: calc(1.6 * var(--u)) dashed rgba(8, 72, 45, 0.5);
border-radius: 999px;
}
.cta a {
display: inline-flex;
align-items: center;
justify-content: center;
width: calc(172 * var(--u));
height: calc(58 * var(--u));
border-radius: 999px;
background: var(--forest);
color: #fff;
font-size: calc(18 * var(--u));
font-weight: 500;
letter-spacing: 0.02em;
text-decoration: none;
transition: background 0.2s ease, transform 0.2s ease;
}
.cta a:hover {
transform: translateY(-1px);
background: #063a24;
}
.bottom {
display: contents;
}
.lightbox {
position: fixed;
inset: 0;
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
padding: 4vh 5vw;
background: rgba(6, 26, 18, 0.82);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
opacity: 0;
visibility: hidden;
transition: opacity 0.24s ease, visibility 0.24s ease;
}
.lightbox.is-open {
opacity: 1;
visibility: visible;
}
.lightbox {
flex-direction: column;
gap: 14px;
}
.lightbox__bar {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 10px 8px 16px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
}
.lightbox__skill {
color: #fff;
font-size: calc(13 * var(--u));
letter-spacing: 0.01em;
white-space: nowrap;
}
.lightbox__skill::before {
content: "技能 · ";
color: rgba(255, 255, 255, 0.6);
}
.lightbox__cta {
display: inline-flex;
align-items: center;
justify-content: center;
height: calc(34 * var(--u));
padding: 0 calc(16 * var(--u));
border: 0;
border-radius: 999px;
background: #fff;
color: var(--forest);
font-family: inherit;
font-size: calc(13 * var(--u));
font-weight: 600;
white-space: nowrap;
cursor: pointer;
transition: transform 0.2s ease, background 0.2s ease;
}
.lightbox__cta:hover {
background: var(--mint);
transform: translateY(-1px);
}
.lightbox__cta:focus-visible {
outline: 2px solid #fff;
outline-offset: 2px;
}
.card__skill {
position: absolute;
left: 50%;
bottom: calc(10 * var(--u));
transform: translateX(-50%);
z-index: 3;
max-width: calc(100% - calc(16 * var(--u)));
padding: calc(3 * var(--u)) calc(8 * var(--u));
border-radius: 999px;
background: rgba(8, 72, 45, 0.72);
color: #fff;
font-size: calc(11 * var(--u));
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
}
.lightbox__img {
display: block;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
border-radius: calc(10 * var(--u));
background: #fff;
box-shadow: 0 34px 90px -34px rgba(0, 0, 0, 0.65);
transform: scale(0.94);
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.lightbox.is-open .lightbox__img {
transform: scale(1);
}
.lightbox__close {
position: absolute;
top: 22px;
right: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
padding: 0;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: #fff;
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
}
.lightbox__close:hover {
background: rgba(255, 255, 255, 0.24);
transform: translateY(-1px);
}
.lightbox__close svg {
width: 18px;
height: 18px;
}
.lightbox__close:focus-visible {
outline: 2px solid #fff;
outline-offset: 2px;
}
html.has-lightbox,
html.has-lightbox body {
overflow: hidden;
}
@media (max-width: 820px) {
:root {
--u: 0.15625vw;
}
html,
body {
height: auto;
min-height: 100%;
}
body {
display: block;
overflow-x: hidden;
overflow-y: auto;
}
.frame {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: auto;
min-height: 100svh;
padding: 18px 0 26px;
}
.topbar {
position: static;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: auto;
padding: 0 18px;
}
.logo {
position: static;
gap: 8px;
font-size: 20px;
}
.logo svg {
width: 24px;
height: 24px;
}
.pill--login {
display: none;
}
.pill--signup {
position: static;
width: auto;
height: 38px;
padding: 0 18px;
font-size: 14px;
}
.headline {
position: static;
width: 100%;
margin: 16px 0 0;
padding: 0 16px;
font-size: clamp(34px, 8.2vw, 48px);
line-height: 1.06;
}
.headline span {
white-space: normal;
}
.headline span:last-child {
letter-spacing: -0.02em;
}
.lightbox__skill {
font-size: 12px;
}
.lightbox__cta {
height: 32px;
padding: 0 14px;
font-size: 12px;
}
.card__skill {
bottom: 8px;
padding: 2px 6px;
font-size: 10px;
}
.subline {
position: static;
width: 100%;
max-width: 360px;
margin: 12px auto 0;
padding: 0 18px;
font-size: 14px;
line-height: 1.5;
}
.subline br {
display: none;
}
.note--seen,
.doodle--seen,
.doodle--zero {
display: none;
}
.tabs {
position: static;
width: 100%;
margin: 14px 0 0;
padding: 0 16px;
gap: 8px;
}
.tab {
height: 34px;
padding: 0 14px;
font-size: 13.5px;
}
.cards {
position: relative;
inset: auto;
flex: none;
width: min(100%, 420px);
height: 250px;
margin: 14px auto 0;
overflow: hidden;
}
.bottom {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
margin-top: auto;
padding: 20px 18px 0;
}
.note--zero {
position: static;
flex: none;
font-size: 28px;
transform: rotate(-7deg);
}
.cta {
position: static;
flex: none;
width: 190px;
height: 74px;
border-width: 1.6px;
}
.cta a {
width: 172px;
height: 58px;
font-size: 17px;
}
}
@media (prefers-reduced-motion: reduce) {
.cards,
.card,
.tab,
.cta a,
.pill {
transition: none;
}
}
</style>
</head>
<body>
<main class="frame" id="frame">
<header class="topbar">
<a class="logo" href="/" aria-label="返回来秒AI 首页">
<svg viewBox="0 0 32 32" aria-hidden="true">
<path d="M19 2 5 19h8l-2 11L27 12h-9l1-10Z" fill="#08482D" />
<path d="M19 2 11 12" fill="none" stroke="#DAE7C7" stroke-width="1.4" stroke-linecap="round" />
</svg>
<span>来秒AI</span>
</a>
<a class="pill pill--login" href="/login/?v=20261107">登录</a>
<a class="pill pill--signup" href="/login/?v=20261107">免费注册</a>
</header>
<h1 class="headline">
<span>让每个行业的灵感,</span>
<span>都能一秒成片</span>
</h1>
<p class="subline">从电商零售,到旅游海报,来秒AI 把创意快速变成可发布的图片。</p>
<p class="note note--seen">好内容<br />被看见</p>
<svg class="doodle doodle--seen" viewBox="0 0 180 150" aria-hidden="true">
<path pathLength="1" d="M64 6 C102 38 112 64 82 92 C58 114 52 128 78 144" />
<path pathLength="1" d="M78 144 L63 131" />
<path pathLength="1" d="M78 144 L91 128" />
</svg>
<div class="tabs" id="industries" role="tablist" aria-label="选择行业"></div>
<div class="cards" id="cards" aria-label="行业案例"></div>
<div
class="lightbox"
id="lightbox"
role="dialog"
aria-modal="true"
aria-label="案例预览"
aria-hidden="true"
>
<button class="lightbox__close" id="lightboxClose" type="button" aria-label="关闭预览">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M6 6 18 18M18 6 6 18"
fill="none"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
/>
</svg>
</button>
<img class="lightbox__img" id="lightboxImg" alt="" referrerpolicy="no-referrer" />
<div class="lightbox__bar">
<span class="lightbox__skill" id="lightboxSkill"></span>
<button class="lightbox__cta" id="lightboxCta" type="button">按同样风格生图</button>
</div>
</div>
<div class="bottom">
<p class="note note--zero">零成本</p>
<svg class="doodle doodle--zero" viewBox="0 0 34 28" aria-hidden="true">
<path pathLength="1" d="M2 22 C11 22 19 17 30 10" />
<path pathLength="1" d="M30 10 L21 8" />
<path pathLength="1" d="M30 10 L25 17" />
</svg>
<div class="cta">
<a href="/generate">开始创作</a>
</div>
</div>
</main>
<script>
(function () {
"use strict";
// 每个行业可用的技能(与 team-api skill 库一致),用于图片标注与「按同样风格生图」
const SKILLS_BY_INDUSTRY = {
travel: [
{ id: "travel-general", name: "标准通用风格" },
{ id: "travel-simple", name: "清新简约风格" },
{ id: "travel-illustration", name: "插画漫画风格" },
{ id: "02-skill", name: "家庭亲子风格" },
{ id: "03-skill", name: "高级杂志封面风格" },
{ id: "05-skill", name: "视觉冲击风格" },
],
ecommerce: [],
};
function buildIndustryImages(id, count) {
const skills = SKILLS_BY_INDUSTRY[id] || [];
return Array.from({ length: count }, function (_, index) {
const skill = skills.length ? skills[index % skills.length] : null;
return {
src: "industries/" + id + "/" + String(index + 1).padStart(2, "0") + ".webp",
skillId: skill ? skill.id : "",
skillName: skill ? skill.name : "",
};
});
}
// 旅游案例墙:按文件名里的风格标注对应技能;展示时不同风格交叉摆放
const TRIP_BASE = "https://public.laimiaoai.com/laimiao-trip/";
const TRAVEL_SKILLS = {
illustration: { id: "travel-illustration", name: "插画漫画风格" },
simple: { id: "travel-simple", name: "清新简约风格" },
general: { id: "travel-general", name: "标准通用风格" },
magazine: { id: "03-skill", name: "高级杂志封面风格" },
};
const TRAVEL_GROUPS = [
{
skill: "illustration",
files: ["%E6%8F%92%E7%94%BB1.png", "%E6%8F%92%E7%94%BB10.jpg", "%E6%8F%92%E7%94%BB3.png", "%E6%8F%92%E7%94%BB7.png"],
},
{
skill: "simple",
files: ["%E7%AE%80%E7%BA%A61.png", "%E7%AE%80%E7%BA%A62.jpg", "%E7%AE%80%E7%BA%A63.jpg"],
},
{
skill: "general",
files: ["%E9%80%9A%E7%94%A816.jpg", "%E9%80%9A%E7%94%A82.png", "%E9%80%9A%E7%94%A820.png", "%E9%80%9A%E7%94%A823.png", "%E9%80%9A%E7%94%A824.png", "%E9%80%9A%E7%94%A84.png"],
},
{
skill: "magazine",
files: ["%E9%AB%98%E7%BA%A7%E6%9D%82%E5%BF%97%E9%A3%8E3.png", "%E9%AB%98%E7%BA%A7%E6%9D%82%E5%BF%97%E9%A3%8E4.png", "%E9%AB%98%E7%BA%A7%E6%9D%82%E5%BF%97%E9%A3%8E6.jpg", "%E9%AB%98%E7%BA%A7%E6%9D%82%E5%BF%97%E9%A3%8E6.png"],
},
];
// 交叉摆放:每一张尽量与上一张风格不同;数量多的风格优先补齐,避免同风格连排
function interleaveTravelImages(groups) {
const pools = groups.map(function (group) {
return { skill: group.skill, items: group.files.slice() };
});
const out = [];
let lastSkill = "";
while (pools.some(function (pool) { return pool.items.length; })) {
const remaining = pools.filter(function (pool) { return pool.items.length; });
const candidates = remaining.filter(function (pool) { return pool.skill !== lastSkill; });
const pickFrom = candidates.length ? candidates : remaining;
let chosen = pickFrom[0];
for (let i = 1; i < pickFrom.length; i++) {
if (pickFrom[i].items.length > chosen.items.length) chosen = pickFrom[i];
}
const file = chosen.items.shift();
lastSkill = chosen.skill;
const skill = TRAVEL_SKILLS[chosen.skill];
out.push({ src: TRIP_BASE + file, skillId: skill.id, skillName: skill.name });
}
return out;
}
const TRAVEL_IMAGES = interleaveTravelImages(TRAVEL_GROUPS);
const INDUSTRIES = [
{ id: "travel", name: "旅游", images: TRAVEL_IMAGES },
{ id: "ecommerce", name: "电商零售", images: buildIndustryImages("ecommerce", 8) },
];
let COUNT = 8;
const PITCH = 196;
const RADIUS = 15;
const SPEED = 50;
let PERIOD = PITCH * COUNT;
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const layer = document.getElementById("cards");
const tabsEl = document.getElementById("industries");
const lightbox = document.getElementById("lightbox");
const lightboxImg = document.getElementById("lightboxImg");
const lightboxClose = document.getElementById("lightboxClose");
const lightboxSkill = document.getElementById("lightboxSkill");
const lightboxCta = document.getElementById("lightboxCta");
const cards = [];
let activeIndustry = 0;
let swapTimer = 0;
let clock = null;
let mobile = false;
let u = 1;
let cyMobile = 205;
let dragging = false;
let dragOffset = 0;
let dragStartX = 0;
let dragStartOffset = 0;
let lastPointerX = 0;
let lastPointerTime = 0;
let releaseVelocity = 0;
let downX = 0;
let downY = 0;
let moved = false;
let autoOrigin = 0;
let flingOffset = 0;
let flingVelocity = 0;
let lastFrame = 0;
function makeCard(industry, index) {
const el = document.createElement("div");
el.className = "card";
el.dataset.index = String(index);
el.setAttribute("role", "button");
el.setAttribute("tabindex", "0");
el.setAttribute("aria-label", "放大预览:" + industry.name + "案例 " + (index + 1));
const item = industry.images[index];
const img = document.createElement("img");
img.alt = industry.name + "案例 " + (index + 1);
img.draggable = false;
img.decoding = "async";
img.referrerPolicy = "no-referrer";
el.appendChild(img);
const badge = document.createElement("span");
badge.className = "card__skill";
badge.textContent = (item && item.skillName) || industry.name;
el.appendChild(badge);
layer.appendChild(el);
cards.push({ index, el, img, src: item ? item.src : "" });
}
function buildCards(industry) {
layer.textContent = "";
cards.length = 0;
COUNT = industry.images.length;
PERIOD = PITCH * (COUNT || 1);
for (let i = 0; i < COUNT; i++) makeCard(industry, i);
}
buildCards(INDUSTRIES[0]);
function buildTabs() {
for (let i = 0; i < INDUSTRIES.length; i++) {
const button = document.createElement("button");
button.type = "button";
button.className = "tab" + (i === 0 ? " is-active" : "");
button.textContent = INDUSTRIES[i].name;
button.setAttribute("role", "tab");
button.setAttribute("aria-selected", i === 0 ? "true" : "false");
button.addEventListener("click", function () {
selectIndustry(i);
});
tabsEl.appendChild(button);
}
}
function selectIndustry(index) {
if (index === activeIndustry) return;
activeIndustry = index;
for (let i = 0; i < tabsEl.children.length; i++) {
const tab = tabsEl.children[i];
tab.classList.toggle("is-active", i === index);
tab.setAttribute("aria-selected", i === index ? "true" : "false");
}
const industry = INDUSTRIES[index];
window.clearTimeout(swapTimer);
layer.classList.add("is-swapping");
swapTimer = window.setTimeout(function () {
buildCards(industry);
layer.classList.remove("is-swapping");
}, 180);
}
function preloadIndustries() {
for (let i = 0; i < INDUSTRIES.length; i++) {
if (i === activeIndustry) continue;
const images = INDUSTRIES[i].images;
for (let j = 0; j < images.length; j++) {
const image = new Image();
image.referrerPolicy = "no-referrer";
image.src = images[j] && images[j].src ? images[j].src : images[j];
}
}
}
function roundedQuad(points, radius) {
const n = points.length;
let d = "";
for (let i = 0; i < n; i++) {
const point = points[i];
const prev = points[(i - 1 + n) % n];
const next = points[(i + 1) % n];
const v1x = prev.x - point.x;
const v1y = prev.y - point.y;
const v2x = next.x - point.x;
const v2y = next.y - point.y;
const l1 = Math.hypot(v1x, v1y) || 1;
const l2 = Math.hypot(v2x, v2y) || 1;
const r = Math.min(radius, l1 / 2, l2 / 2);
const ax = point.x + (v1x / l1) * r;
const ay = point.y + (v1y / l1) * r;
const bx = point.x + (v2x / l2) * r;
const by = point.y + (v2y / l2) * r;
d += (i === 0 ? "M" : "L") + ax.toFixed(2) + " " + ay.toFixed(2);
d += "Q" + point.x.toFixed(2) + " " + point.y.toFixed(2) + " " + bx.toFixed(2) + " " + by.toFixed(2);
}
return d + "Z";
}
function measure() {
mobile = window.matchMedia("(max-width: 820px)").matches;
if (mobile) {
const height = layer.clientHeight || 250;
u = (layer.clientWidth || window.innerWidth) / 640;
cyMobile = height / u / 2;
} else {
u = Math.min(window.innerWidth / 1440, window.innerHeight / 1080);
}
}
function offsetNow() {
if (dragging) return dragOffset;
const t = clock ? clock.currentTime / 1000 : 0;
return (reduceMotion ? 0 : SPEED * t) + autoOrigin + flingOffset;
}
function render() {
const offset = offsetNow();
const cx = mobile ? 320 : 720;
const norm = mobile ? 314 : 706;
const baseCy = mobile ? cyMobile : 715;
const frameW = mobile ? 640 : 1440;
for (let i = 0; i < cards.length; i++) {
const card = cards[i];
let raw = (card.index * PITCH - offset) % PERIOD;
raw = ((raw % PERIOD) + PERIOD) % PERIOD;
const t = raw >= PERIOD / 2 ? raw - PERIOD : raw;
const x = cx + t * (1 + 0.083 * Math.pow(t / norm, 2));
const d = Math.abs(x - cx) / norm;
const w = 170 + 48 * d * d;
const cy = baseCy - 12 * d * d;
const xL = x - w / 2;
const xR = x + w / 2;
if (!card.img.getAttribute("src") && xR > -460 && xL < frameW + 460) {
card.img.src = card.src;
}
if (xR < -20 || xL > frameW + 20) {
card.el.style.visibility = "hidden";
continue;
}
card.el.style.visibility = "visible";
const qL = Math.abs(xL - cx) / norm;
const qR = Math.abs(xR - cx) / norm;
let hL = 280 + 92 * Math.pow(qL, 2.6);
let hR = 280 + 92 * Math.pow(qR, 2.6);
if (mobile) {
const cap = cyMobile * 1.76;
hL = Math.min(hL, cap);
hR = Math.min(hR, cap);
}
const h = Math.max(hL, hR);
const wPx = w * u;
const hPx = h * u;
card.el.style.width = wPx + "px";
card.el.style.height = hPx + "px";
card.el.style.transform =
"translate3d(" + xL * u + "px," + (cy - h / 2) * u + "px,0)";
const points = [
{ x: 0, y: ((h - hL) / 2) * u },
{ x: wPx, y: ((h - hR) / 2) * u },
{ x: wPx, y: ((h + hR) / 2) * u },
{ x: 0, y: ((h + hL) / 2) * u },
];
const clip = 'path("' + roundedQuad(points, RADIUS * u) + '")';
card.el.style.clipPath = clip;
card.el.style.webkitClipPath = clip;
}
}
let lightboxItem = null;
let lightboxIndustry = null;
function openLightbox(index) {
const industry = INDUSTRIES[activeIndustry];
const item = industry.images[index];
if (!item) return;
lightboxItem = item;
lightboxIndustry = industry;
lightboxImg.src = item.src;
lightboxImg.alt = industry.name + "案例 " + (index + 1);
lightboxSkill.textContent = item.skillName || industry.name;
lightbox.classList.add("is-open");
lightbox.setAttribute("aria-hidden", "false");
document.documentElement.classList.add("has-lightbox");
if (lightboxClose) lightboxClose.focus({ preventScroll: true });
}
function startWithSameSkill() {
if (!lightboxIndustry) return;
const params = new URLSearchParams();
if (lightboxItem && lightboxItem.skillId) params.set("skill", lightboxItem.skillId);
if (lightboxIndustry.name) params.set("industry", lightboxIndustry.name);
const target = "/generate" + (params.toString() ? "?" + params.toString() : "");
goEntry(target);
}
function closeLightbox() {
if (!lightbox.classList.contains("is-open")) return;
lightbox.classList.remove("is-open");
lightbox.setAttribute("aria-hidden", "true");
document.documentElement.classList.remove("has-lightbox");
}
function onPointerDown(event) {
if (event.pointerType === "mouse" && event.button !== 0) return;
const current = offsetNow();
autoOrigin = current - (reduceMotion ? 0 : SPEED * (clock.currentTime / 1000));
flingOffset = 0;
flingVelocity = 0;
dragging = true;
dragOffset = current;
dragStartX = event.clientX;
dragStartOffset = current;
lastPointerX = event.clientX;
lastPointerTime = performance.now();
releaseVelocity = 0;
downX = event.clientX;
downY = event.clientY;
moved = false;
layer.classList.add("is-dragging");
if (layer.setPointerCapture) {
try {
layer.setPointerCapture(event.pointerId);
} catch (error) {
/* capture can fail when the pointer is already released */
}
}
}
function onPointerMove(event) {
if (!dragging) return;
if (Math.abs(event.clientX - downX) > 6 || Math.abs(event.clientY - downY) > 6) moved = true;
const dx = event.clientX - dragStartX;
dragOffset = dragStartOffset - dx / u;
const now = performance.now();
const dt = Math.max(8, now - lastPointerTime);
releaseVelocity = (-(event.clientX - lastPointerX) / u) / (dt / 1000);
lastPointerX = event.clientX;
lastPointerTime = now;
}
function onPointerUp(event) {
if (!dragging) return;
dragging = false;
layer.classList.remove("is-dragging");
if (layer.hasPointerCapture && layer.hasPointerCapture(event.pointerId)) {
layer.releasePointerCapture(event.pointerId);
}
autoOrigin = dragOffset - (reduceMotion ? 0 : SPEED * (clock.currentTime / 1000));
flingOffset = 0;
const limit = 2600;
releaseVelocity = Math.max(-limit, Math.min(limit, releaseVelocity));
flingVelocity = reduceMotion || Math.abs(releaseVelocity) < 24 ? 0 : releaseVelocity;
if (!moved) {
const hit = document.elementFromPoint(event.clientX, event.clientY);
const cardEl = hit && hit.closest ? hit.closest(".card") : null;
if (cardEl && layer.contains(cardEl)) openLightbox(Number(cardEl.dataset.index));
}
}
function frameLoop(now) {
const dt = Math.min(0.05, lastFrame ? (now - lastFrame) / 1000 : 0);
lastFrame = now;
if (!dragging && !reduceMotion && flingVelocity !== 0) {
flingOffset += flingVelocity * dt;
flingVelocity *= Math.pow(0.05, dt);
if (Math.abs(flingVelocity) < 2) flingVelocity = 0;
}
render();
requestAnimationFrame(frameLoop);
}
function entrance() {
if (reduceMotion) return;
const topbar = document.querySelector(".topbar");
const lines = document.querySelectorAll(".headline span");
const subline = document.querySelector(".subline");
const tabs = document.querySelector(".tabs");
const cta = document.querySelector(".cta");
const notes = document.querySelectorAll(".note");
const doodles = document.querySelectorAll(".doodle path");
const ease = "cubic-bezier(0.22, 1, 0.36, 1)";
const animate = function (el, frames, options) {
el.animate(frames, Object.assign({ fill: "backwards", easing: ease }, options));
};
animate(
topbar,
[
{ opacity: 0, transform: "translateY(-18px)" },
{ opacity: 1, transform: "translateY(0)" },
],
{ duration: 700 },
);
for (let i = 0; i < lines.length; i++) {
animate(
lines[i],
[
{ opacity: 0, transform: "translateY(" + 26 * u + "px)", filter: "blur(10px)" },
{ opacity: 1, transform: "translateY(0)", filter: "blur(0px)" },
],
{ duration: 850, delay: 300 + i * 110 },
);
}
animate(
subline,
[
{ opacity: 0, transform: "translateY(" + 16 * u + "px)" },
{ opacity: 1, transform: "translateY(0)" },
],
{ duration: 700, delay: 560 },
);
animate(
tabs,
[
{ opacity: 0, transform: "translateY(" + 10 * u + "px)" },
{ opacity: 1, transform: "translateY(0)" },
],
{ duration: 650, delay: 630 },
);
animate(
layer,
[
{ opacity: 0, transform: "translateY(" + 64 * u + "px)" },
{ opacity: 1, transform: "translateY(0)" },
],
{ duration: 900, delay: 720 },
);
animate(
cta,
[
{ opacity: 0, transform: "scale(0.9)" },
{ opacity: 1, transform: "scale(1)" },
],
{ duration: 600, delay: 940 },
);
for (let i = 0; i < doodles.length; i++) {
animate(
doodles[i],
[{ strokeDashoffset: 1 }, { strokeDashoffset: 0 }],
{ duration: 650, delay: 1040 + i * 80 },
);
}
for (let i = 0; i < notes.length; i++) {
animate(notes[i], [{ opacity: 0 }, { opacity: 1 }], {
duration: 700,
delay: 1240 + i * 120,
});
}
}
clock = document.documentElement.animate([{ opacity: 1 }, { opacity: 1 }], {
duration: 1e9,
iterations: 1,
});
buildTabs();
measure();
requestAnimationFrame(frameLoop);
entrance();
window.setTimeout(preloadIndustries, 900);
layer.addEventListener("pointerdown", onPointerDown);
layer.addEventListener("pointermove", onPointerMove);
layer.addEventListener("pointerup", onPointerUp);
layer.addEventListener("pointercancel", onPointerUp);
layer.addEventListener("keydown", function (event) {
if (event.key !== "Enter" && event.key !== " ") return;
const cardEl = event.target && event.target.closest ? event.target.closest(".card") : null;
if (!cardEl) return;
event.preventDefault();
openLightbox(Number(cardEl.dataset.index));
});
if (lightboxCta) lightboxCta.addEventListener("click", startWithSameSkill);
lightbox.addEventListener("click", function (event) {
if (event.target === lightbox) {
closeLightbox();
return;
}
if (event.target.closest && event.target.closest("#lightboxClose")) closeLightbox();
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") closeLightbox();
});
let resizeTimer = 0;
window.addEventListener("resize", function () {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(measure, 120);
});
function isLoggedIn() {
return !!localStorage.getItem("access_token");
}
function goEntry(target) {
const dest = target || "/generate";
if (isLoggedIn()) {
window.location.href = dest;
return;
}
const params = new URLSearchParams({ v: "20261107", redirect: dest });
window.location.href = "/login/?" + params.toString();
}
document.querySelectorAll("a.pill--login, a.pill--signup").forEach(function (link) {
link.addEventListener("click", function (event) {
event.preventDefault();
goEntry("/generate");
});
});
document.querySelectorAll('a[href="/generate"]').forEach(function (link) {
link.addEventListener("click", function (event) {
event.preventDefault();
goEntry(link.getAttribute("href") || "/generate");
});
});
})();
</script>
</body>
</html>