docs(product): commit design references under docs/product/reference/
Copy the founder's design handoff bundle (.proto/design/) into docs/product/reference/ byte-for-byte so dispatch agents running in git worktrees can read the HTML prototypes, veect-codebase/ prototype, upload PNGs, and remaining bundle files (ADR-029: reference only, never vendored into packages/). Ignore-list entries so whole-codebase auditors and formatters skip reference material: .prettierignore (byte preservation through lint-staged), .fallowrc.json ignorePatterns, root ESLint ignores, and the coverage:diff allowlist in scripts/coverage/diff.mjs (+ unit test). .DS_Store files skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
437
docs/product/reference/project/Veect Landing.dc.html
Normal file
437
docs/product/reference/project/Veect Landing.dc.html
Normal file
@@ -0,0 +1,437 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="./support.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<x-dc>
|
||||
<helmet>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600&family=Fragment+Mono&family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
|
||||
<style>
|
||||
html,body{margin:0;padding:0;background:#0E0E0D;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;}
|
||||
a{color:#F4F4F2;text-decoration:none} a:hover{color:#F4F4F2}
|
||||
*{box-sizing:border-box}
|
||||
button{font:inherit;color:inherit;background:none;border:none;padding:0;cursor:pointer;text-align:left}
|
||||
::selection{background:rgba(127,127,120,.3)}
|
||||
*:focus-visible{outline:2px solid #F4F4F2;outline-offset:2px;border-radius:2px}
|
||||
@keyframes cursorBlink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){*{animation-duration:.01ms !important;transition-duration:.01ms !important}html{scroll-behavior:auto}}
|
||||
</style>
|
||||
</helmet>
|
||||
<div style="background:#0E0E0D;color:#F4F4F2;font-family:'Instrument Sans',sans-serif;font-size:15px;line-height:1.5;overflow-x:hidden;">
|
||||
|
||||
<div data-ruler style="position:fixed;left:0;top:0;bottom:0;width:34px;z-index:40;border-right:1px solid #161615;background:repeating-linear-gradient(to bottom,transparent 0 11px,#262624 11px 12px);pointer-events:none;">
|
||||
<div data-ruler-dot style="position:absolute;left:0;top:0;width:34px;height:2px;background:#F4F4F2;"></div>
|
||||
</div>
|
||||
|
||||
<nav data-nav style="position:fixed;top:0;left:0;right:0;z-index:50;display:flex;align-items:center;gap:clamp(12px,2.5vw,26px);padding:14px clamp(16px,4vw,40px) 14px clamp(50px,7vw,66px);transition:background .25s,border-color .25s;border-bottom:1px solid transparent;">
|
||||
<a href="#top" style="display:flex;align-items:center;gap:9px;color:#F4F4F2;">
|
||||
<span style="display:flex;align-items:baseline;gap:3px;flex:0 0 auto;"><span style="font-size:15px;font-weight:600;letter-spacing:-.035em;line-height:1;">veect</span><span style="width:4px;height:4px;background:currentColor;flex:0 0 auto;"></span></span>
|
||||
</a>
|
||||
<span style="flex:1"></span>
|
||||
<span data-navlinks style="display:flex;align-items:center;gap:clamp(12px,2.5vw,26px);">
|
||||
<a href="#system" style="font-size:13px;color:#A6A6A1;">System</a>
|
||||
<a href="#refusal" style="font-size:13px;color:#A6A6A1;">Constraint</a>
|
||||
<a href="#code" style="font-size:13px;color:#A6A6A1;">Code</a>
|
||||
<a href="#craft" style="font-size:13px;color:#A6A6A1;">Craft</a>
|
||||
</span>
|
||||
<a href="Veect.dc.html" style="background:#F4F4F2;color:#0E0E0D;padding:8px 16px;border-radius:2px;font-size:13px;font-weight:600;">Open the canvas</a>
|
||||
</nav>
|
||||
|
||||
<section id="top" data-screen-label="Hero" style="min-height:100vh;display:flex;align-items:center;padding:110px clamp(18px,5vw,40px) 70px clamp(50px,7vw,66px);position:relative;overflow:hidden;">
|
||||
|
||||
|
||||
<div style="width:100%;max-width:1120px;margin:0 auto;display:flex;flex-wrap:wrap;gap:64px;align-items:center;position:relative;">
|
||||
<div style="flex:1 1 420px;display:flex;flex-direction:column;align-items:flex-start;">
|
||||
<span data-reveal style="font-family:'Fragment Mono',monospace;font-size:11px;letter-spacing:.14em;color:#82827C;">DESIGN-SYSTEM-NATIVE CANVAS</span>
|
||||
<h1 style="font-size:clamp(40px,6.2vw,76px);line-height:1.05;letter-spacing:-2.2px;font-weight:600;margin:22px 0 0;">
|
||||
<span style="display:block;overflow:hidden;"><span data-hline style="display:block;">Your system in.</span></span>
|
||||
<span style="display:block;overflow:hidden;"><span data-hline style="display:block;">Real React out.</span></span>
|
||||
</h1>
|
||||
<p data-reveal style="font-size:17px;line-height:1.6;color:#A6A6A1;max-width:460px;margin:22px 0 0;text-wrap:pretty;">Veect composes screens from your own tokens and components — and the code it exports imports your real library. Nothing generic, nothing thrown away.</p>
|
||||
<div data-reveal style="display:flex;flex-wrap:wrap;gap:12px;margin-top:32px;align-items:center;">
|
||||
<a data-magnetic href="Veect.dc.html" style="display:inline-block;background:#F4F4F2;color:#0E0E0D;padding:13px 22px;border-radius:2px;font-size:14.5px;font-weight:600;">Open the canvas →</a>
|
||||
<a data-magnetic href="#code" style="display:inline-block;border:1px solid #3A3A37;color:#F4F4F2;padding:13px 22px;border-radius:2px;font-size:14.5px;font-weight:500;">Read the code</a>
|
||||
</div>
|
||||
<span data-reveal style="font-family:'Fragment Mono',monospace;font-size:10.5px;color:#5A5A56;margin-top:26px;">try it — the panel on the right is live</span>
|
||||
</div>
|
||||
|
||||
<div data-reveal style="flex:1 1 360px;max-width:430px;display:flex;flex-direction:column;gap:14px;">
|
||||
<div style="display:flex;flex-direction:column;gap:12px;background:#161615;border:1px solid #262624;border-radius:2px;padding:16px;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;letter-spacing:.1em;color:#82827C;">FIVE TOKENS — WATCH THE KIT FOLLOW</span>
|
||||
<div style="display:flex;gap:10px;align-items:center;">
|
||||
<sc-for list="{{ brandSwatches }}" as="item" hint-placeholder-count="4">
|
||||
<button onClick="{{ item.onPick }}" title="{{ item.hex }}" style="{{ item.style }}"></button>
|
||||
</sc-for>
|
||||
<span style="flex:1"></span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10.5px;color:#82827C;">{{ brandHex }}</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;align-items:center;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#82827C;width:52px;">radius</span>
|
||||
<input type="range" min="0" max="20" step="2" value="{{ radiusVal }}" onChange="{{ onRadius }}" style="flex:1;accent-color:#F4F4F2;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10.5px;color:#A6A6A1;width:34px;text-align:right;">{{ radiusLabel }}</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;">
|
||||
<sc-for list="{{ fontSegs }}" as="item" hint-placeholder-count="3">
|
||||
<button onClick="{{ item.onClick }}" style="{{ item.style }}">{{ item.label }}</button>
|
||||
</sc-for>
|
||||
</div>
|
||||
</div>
|
||||
<div data-kit style="{{ kitVars }}">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<span style="background:var(--ka);color:#fff;border-radius:var(--krb);padding:10px 18px;font-family:var(--kf);font-weight:600;font-size:14px;transition:all .45s cubic-bezier(.2,.7,.2,1);">Get started</span>
|
||||
<span style="border:1px solid var(--kl);color:var(--ki);border-radius:var(--krb);padding:10px 18px;font-family:var(--kf);font-weight:600;font-size:14px;transition:all .45s cubic-bezier(.2,.7,.2,1) .05s;">Docs</span>
|
||||
<span style="flex:1"></span>
|
||||
<span style="background:var(--kd);color:var(--kas);border-radius:2px;padding:4px 12px;font-family:var(--kf);font-weight:600;font-size:11px;transition:all .45s cubic-bezier(.2,.7,.2,1) .1s;">NEW</span>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:6px;">
|
||||
<span style="font-size:12.5px;font-weight:600;color:var(--ki);font-family:var(--kf);transition:all .45s cubic-bezier(.2,.7,.2,1) .12s;">Email</span>
|
||||
<div style="border:1px solid var(--kl);border-radius:var(--krb);padding:10px 13px;color:var(--km);font-size:13.5px;font-family:var(--kf);transition:all .45s cubic-bezier(.2,.7,.2,1) .15s;">you@company.com</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<span style="width:34px;height:20px;border-radius:2px;background:var(--ka);position:relative;transition:all .45s cubic-bezier(.2,.7,.2,1) .2s;"><span style="position:absolute;top:2.5px;right:2.5px;width:15px;height:15px;border-radius:2px;background:#fff;"></span></span>
|
||||
<span style="font-size:12.5px;color:var(--ki);font-family:var(--kf);font-weight:500;transition:all .45s cubic-bezier(.2,.7,.2,1) .2s;">Notifications</span>
|
||||
<span style="flex:1"></span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:9.5px;color:#8A8279;">your tokens · live</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-screen-label="Proof" style="padding:0 clamp(18px,5vw,40px) 0 clamp(50px,7vw,66px);">
|
||||
<div style="max-width:1120px;margin:0 auto;display:flex;flex-wrap:wrap;gap:clamp(24px,5vw,64px);padding:30px 0;border-top:1px solid #262624;">
|
||||
<div data-reveal style="display:flex;flex-direction:column;gap:7px;min-width:170px;flex:1;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:30px;color:#F4F4F2;letter-spacing:-1px;">0</span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#82827C;letter-spacing:.08em;line-height:1.6;">UNREGISTERED ELEMENTS<br>ACROSS A 100-PROMPT EVAL</span>
|
||||
</div>
|
||||
<div data-reveal style="display:flex;flex-direction:column;gap:7px;min-width:170px;flex:1;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:30px;color:#F4F4F2;letter-spacing:-1px;"><10%</span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#82827C;letter-spacing:.08em;line-height:1.6;">LOC CHANGED PRE-MERGE —<br>THE ENGINEER IS THE JUDGE</span>
|
||||
</div>
|
||||
<div data-reveal style="display:flex;flex-direction:column;gap:7px;min-width:170px;flex:1;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:30px;color:#F4F4F2;letter-spacing:-1px;"><20 min</span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#82827C;letter-spacing:.08em;line-height:1.6;">SIGN-IN TO YOUR FIRST<br>EXPORTED, ON-BRAND SCREEN</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="system" data-screen-label="Wedge" style="padding:clamp(64px,10vw,110px) clamp(18px,5vw,40px) clamp(64px,10vw,110px) clamp(50px,7vw,66px);border-top:1px solid #262624;">
|
||||
<div style="max-width:1120px;margin:0 auto;display:flex;flex-direction:column;">
|
||||
<span data-reveal style="font-family:'Fragment Mono',monospace;font-size:11px;letter-spacing:.14em;color:#82827C;">WHY IT HOLDS</span>
|
||||
<h2 data-reveal style="font-size:clamp(28px,3.6vw,44px);letter-spacing:-1.2px;font-weight:600;margin:18px 0 0;max-width:640px;text-wrap:balance;">Other tools re-invent your UI. Veect is constrained to it.</h2>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:16px;margin-top:48px;">
|
||||
<div data-reveal style="flex:1 1 280px;background:#161615;border:1px solid #262624;border-radius:2px;padding:26px;display:flex;flex-direction:column;gap:10px;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10.5px;letter-spacing:.1em;color:#F4F4F2;">01 — YOUR COMPONENTS</span>
|
||||
<span style="font-size:16px;font-weight:600;">The canvas renders the real thing</span>
|
||||
<span style="font-size:13.5px;line-height:1.6;color:#A6A6A1;">One runtime backs canvas, preview and export. "Roughly how it'll look" is the lie that makes handoff lossy — Veect never tells it.</span>
|
||||
</div>
|
||||
<div data-reveal style="flex:1 1 280px;background:#161615;border:1px solid #262624;border-radius:2px;padding:26px;display:flex;flex-direction:column;gap:10px;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10.5px;letter-spacing:.1em;color:#F4F4F2;">02 — YOUR TOKENS</span>
|
||||
<span style="font-size:16px;font-weight:600;">Change once, everywhere</span>
|
||||
<span style="font-size:13.5px;line-height:1.6;color:#A6A6A1;">Edit brand-500 and watch every bound component ripple on canvas and in code — the system stays the single source of truth.</span>
|
||||
</div>
|
||||
<div data-reveal style="flex:1 1 280px;background:#161615;border:1px solid #262624;border-radius:2px;padding:26px;display:flex;flex-direction:column;gap:10px;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10.5px;letter-spacing:.1em;color:#F4F4F2;">03 — REAL IMPORTS</span>
|
||||
<span style="font-size:16px;font-weight:600;">Code an engineer would merge</span>
|
||||
<span style="font-size:13.5px;line-height:1.6;color:#A6A6A1;">Export imports your mapped library — import paths, prop names and all. Not inlined clones. That is the whole trust bar.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="refusal" data-screen-label="Refusal" style="padding:clamp(64px,10vw,110px) clamp(18px,5vw,40px) clamp(64px,10vw,110px) clamp(50px,7vw,66px);border-top:1px solid #262624;">
|
||||
<div style="max-width:1120px;margin:0 auto;display:flex;flex-wrap:wrap;gap:56px;align-items:center;">
|
||||
<div style="flex:1 1 380px;display:flex;flex-direction:column;align-items:flex-start;">
|
||||
<span data-reveal style="font-family:'Fragment Mono',monospace;font-size:11px;letter-spacing:.14em;color:#82827C;">THE GRACEFUL REFUSAL</span>
|
||||
<h2 data-reveal style="font-size:clamp(28px,3.6vw,44px);letter-spacing:-1.2px;font-weight:600;margin:18px 0 0;text-wrap:balance;">When AI can't stay on-system, it says so.</h2>
|
||||
<p data-reveal style="font-size:15px;line-height:1.65;color:#A6A6A1;max-width:440px;margin:18px 0 0;">Ask for something your registry can't express and Veect refuses — then offers to compose it from your primitives. It never fails open to generic markup. Across a 100-prompt eval: zero unregistered elements.</p>
|
||||
</div>
|
||||
<div data-reveal style="flex:1 1 360px;max-width:470px;background:#161615;border:1px solid #262624;border-radius:2px;padding:18px;display:flex;flex-direction:column;gap:12px;">
|
||||
<div style="display:flex;justify-content:flex-end;">
|
||||
<span style="background:rgba(244,244,242,.10);border:1px solid rgba(244,244,242,.22);border-radius:2px 12px 4px 12px;padding:9px 13px;font-size:13px;">A hero with a spinning 3D globe</span>
|
||||
</div>
|
||||
<div style="display:flex;">
|
||||
<div style="background:#161615;border:1px solid #262624;border-radius:2px 12px 12px 4px;padding:11px 14px;font-size:13px;line-height:1.6;max-width:88%;display:flex;flex-direction:column;gap:9px;">
|
||||
<span data-typewriter style="color:#F4F4F2;min-height:42px;">You don't have a component for this yet.</span>
|
||||
<span data-refactions style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||
<span style="border:1px solid #3A3A37;color:#F4F4F2;padding:6px 11px;border-radius:2px;font-size:12px;font-weight:500;">Compose from primitives</span>
|
||||
<span style="color:#82827C;padding:6px 4px;font-size:12px;">Not now</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:9.5px;color:#5A5A56;">an error state, elevated to a signature moment</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="code" data-screen-label="Code" style="padding:clamp(64px,10vw,110px) clamp(18px,5vw,40px) clamp(64px,10vw,110px) clamp(50px,7vw,66px);border-top:1px solid #262624;">
|
||||
<div style="max-width:1120px;margin:0 auto;display:flex;flex-wrap:wrap;gap:56px;align-items:center;">
|
||||
<div data-reveal style="flex:1 1 400px;max-width:540px;background:#161311;border:1px solid #262624;border-radius:2px;overflow:hidden;">
|
||||
<div style="display:flex;align-items:center;gap:8px;padding:11px 14px;border-bottom:1px solid #262624;">
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10.5px;color:#F4F4F2;background:#1B1B1A;border:1px solid #262624;border-radius:2px;padding:3px 8px;">Home.tsx</span>
|
||||
<span style="flex:1"></span>
|
||||
|
||||
<span style="font-size:11px;color:#A6A6A1;">in sync</span>
|
||||
</div>
|
||||
<div style="padding:14px 0;font-family:'Fragment Mono',monospace;font-size:12px;line-height:1.8;">
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#F4F4F2;">import <span style="color:#A6A6A1;">{ Button, Card, TextField }</span> from <span style="color:#D9A961;">"@solstice/ui"</span>;</div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#5A5A56;"> </div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;"><span style="color:#F4F4F2;">export default function</span> <span style="color:#F4F4F2;font-weight:600;">Home</span>() {</div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;"> <span style="color:#F4F4F2;">return</span> (</div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;"> <main className=<span style="color:#D9A961;">"flex flex-col bg-white"</span>></div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;"> <<span style="color:#F4F4F2;font-weight:600;">Button</span> kind=<span style="color:#D9A961;">"primary"</span>>Start free</<span style="color:#F4F4F2;font-weight:600;">Button</span>></div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;"> </main></div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;"> );</div>
|
||||
<div data-codeline style="padding:0 16px;white-space:pre;color:#A6A6A1;">}</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;padding:9px 14px;border-top:1px solid #262624;font-family:'Fragment Mono',monospace;font-size:9.5px;color:#73736E;">
|
||||
<span>prettier ✓</span><span>tsc --noEmit ✓</span><span style="flex:1"></span><span>imports your library</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1 1 380px;display:flex;flex-direction:column;align-items:flex-start;">
|
||||
<span data-reveal style="font-family:'Fragment Mono',monospace;font-size:11px;letter-spacing:.14em;color:#82827C;">TRUST IS EARNED IN THE OPEN</span>
|
||||
<h2 data-reveal style="font-size:clamp(28px,3.6vw,44px);letter-spacing:-1.2px;font-weight:600;margin:18px 0 0;text-wrap:balance;">The code is a keystroke away — and it's yours.</h2>
|
||||
<p data-reveal style="font-size:15px;line-height:1.65;color:#A6A6A1;max-width:440px;margin:18px 0 0;">Nudge a component and the code updates in lockstep. Deterministic output — same tree, same code — that passes Prettier, tsc and your reviewer's patience.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="craft" data-screen-label="Craft" style="padding:clamp(64px,10vw,110px) clamp(18px,5vw,40px) clamp(64px,10vw,110px) clamp(50px,7vw,66px);border-top:1px solid #262624;">
|
||||
<div style="max-width:1120px;margin:0 auto;display:flex;flex-wrap:wrap;gap:56px;align-items:center;">
|
||||
<div style="flex:1 1 380px;display:flex;flex-direction:column;align-items:flex-start;">
|
||||
<span data-reveal style="font-family:'Fragment Mono',monospace;font-size:11px;letter-spacing:.14em;color:#82827C;">POLISH — THE IMPECCABLE PASS</span>
|
||||
<h2 data-reveal style="font-size:clamp(28px,3.6vw,44px);letter-spacing:-1.2px;font-weight:600;margin:18px 0 0;text-wrap:balance;">On-brand and genuinely well-designed.</h2>
|
||||
<p data-reveal style="font-size:15px;line-height:1.65;color:#A6A6A1;max-width:440px;margin:18px 0 0;">One pass critiques spacing, hierarchy and contrast against a craft standard — and fixes them in your tokens, with the why. Try it.</p>
|
||||
<div data-reveal style="display:flex;flex-direction:column;gap:8px;margin-top:22px;">
|
||||
<sc-for list="{{ polishChips }}" as="item" hint-placeholder-count="3">
|
||||
<span style="{{ item.style }}">{{ item.label }}</span>
|
||||
</sc-for>
|
||||
</div>
|
||||
</div>
|
||||
<div data-reveal style="flex:1 1 360px;max-width:470px;display:flex;flex-direction:column;gap:12px;">
|
||||
<div style="background:#FFFFFF;border:1px solid #ECE8E2;border-radius:2px;padding:34px 30px;display:flex;flex-direction:column;align-items:center;text-align:center;overflow:hidden;">
|
||||
<span style="{{ demoBadgeStyle }}">New — shared team calendars</span>
|
||||
<span style="{{ demoHeadStyle }}">Scheduling that feels like sunlight</span>
|
||||
<span style="{{ demoSubStyle }}">Every timezone, handled kindly.</span>
|
||||
<div style="{{ demoCtaRowStyle }}">
|
||||
<span style="{{ demoCta1Style }}">Start free</span>
|
||||
<span style="{{ demoCta2Style }}">Book a demo</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<button onClick="{{ togglePolish }}" style="{{ polishBtnStyle }}">{{ polishBtnLabel }}</button>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#5A5A56;">{{ polishNote }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-screen-label="CTA" style="padding:clamp(80px,12vw,130px) clamp(18px,5vw,40px) 60px clamp(50px,7vw,66px);border-top:1px solid #262624;">
|
||||
<div style="max-width:1120px;margin:0 auto;display:flex;flex-direction:column;align-items:flex-start;gap:28px;">
|
||||
<h2 data-reveal style="font-size:clamp(34px,5vw,64px);letter-spacing:-1.8px;font-weight:600;line-height:1.05;max-width:760px;text-wrap:balance;">What you design is what ships.</h2>
|
||||
<a data-reveal data-magnetic href="Veect.dc.html" style="display:inline-block;background:#F4F4F2;color:#0E0E0D;padding:15px 26px;border-radius:2px;font-size:15px;font-weight:600;">Open the canvas →</a>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:24px;align-items:center;width:100%;margin-top:60px;padding-top:24px;border-top:1px solid #262624;">
|
||||
<span style="display:flex;align-items:center;gap:8px;">
|
||||
<span style="display:flex;align-items:baseline;gap:3px;flex:0 0 auto;"><span style="font-size:14px;font-weight:600;letter-spacing:-.035em;line-height:1;">veect</span><span style="width:4px;height:4px;background:currentColor;flex:0 0 auto;"></span></span>
|
||||
</span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#5A5A56;">working name · design-partner MVP</span>
|
||||
<span style="flex:1"></span>
|
||||
<span style="font-family:'Fragment Mono',monospace;font-size:10px;color:#5A5A56;">tokens in · react out · impeccable by default</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</x-dc>
|
||||
<script type="text/x-dc" data-dc-script>
|
||||
class Component extends DCLogic {
|
||||
state = { brand: '#E4572E', radius: 12, font: 'Bricolage Grotesque', polished: false };
|
||||
|
||||
BRANDS = ['#E4572E', '#2A6FDB', '#1F8A5B', '#111827'];
|
||||
FONTS = ['Bricolage Grotesque', 'Space Grotesk', 'Instrument Sans'];
|
||||
MONO = "'Fragment Mono',monospace";
|
||||
|
||||
hexRgb(h) { h = h.replace('#', ''); return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; }
|
||||
mix(a, b, t) {
|
||||
const x = this.hexRgb(a), y = this.hexRgb(b);
|
||||
const c = (v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0');
|
||||
return '#' + c(x[0] + (y[0] - x[0]) * t) + c(x[1] + (y[1] - x[1]) * t) + c(x[2] + (y[2] - x[2]) * t);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
const g = window.gsap;
|
||||
const tw = document.querySelector('[data-typewriter]');
|
||||
if (tw) {
|
||||
const full = "You don't have a component for this yet. Want me to compose one from your primitives?";
|
||||
let started = false;
|
||||
const acts = document.querySelector('[data-refactions]');
|
||||
if (acts && !reduced) { acts.style.opacity = '0'; acts.style.transform = 'translateY(6px)'; }
|
||||
const showActs = () => { if (acts && g) g.to(acts, { opacity: 1, y: 0, duration: 0.5, ease: 'power2.out' }); else if (acts) acts.style.opacity = '1'; };
|
||||
const start = () => {
|
||||
if (started) return; started = true;
|
||||
if (reduced || !tw) { tw.textContent = full; showActs(); return; }
|
||||
tw.textContent = '';
|
||||
let i = 0;
|
||||
this.tick = setInterval(() => {
|
||||
i += 1;
|
||||
tw.textContent = full.slice(0, i);
|
||||
if (i >= full.length) { clearInterval(this.tick); showActs(); }
|
||||
}, 22);
|
||||
};
|
||||
const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) start(); }), { threshold: 0.4 });
|
||||
io.observe(tw); this.io = io;
|
||||
}
|
||||
if (g && window.ScrollTrigger && !reduced) {
|
||||
g.registerPlugin(window.ScrollTrigger);
|
||||
document.querySelectorAll('[data-reveal]').forEach((el) => {
|
||||
g.fromTo(el, { y: 26, opacity: 0 }, { y: 0, opacity: 1, duration: 0.7, ease: 'power3.out', scrollTrigger: { trigger: el, start: 'top 86%', once: true } });
|
||||
});
|
||||
document.querySelectorAll('[data-codeline]').forEach((el, i) => {
|
||||
g.fromTo(el, { x: 14, opacity: 0 }, { x: 0, opacity: 1, duration: 0.45, ease: 'power2.out', delay: i * 0.07, scrollTrigger: { trigger: el.parentElement, start: 'top 80%', once: true } });
|
||||
});
|
||||
}
|
||||
// headline mask reveal
|
||||
if (g && !reduced) {
|
||||
g.fromTo('[data-hline]', { yPercent: 112 }, { yPercent: 0, duration: 0.9, ease: 'power4.out', stagger: 0.12, delay: 0.15 });
|
||||
}
|
||||
// 3D tilt on the live kit card
|
||||
const kit = document.querySelector('[data-kit]');
|
||||
if (kit && !reduced && window.matchMedia('(pointer:fine)').matches) {
|
||||
const par = kit.parentElement;
|
||||
if (par) par.style.perspective = '1100px';
|
||||
kit.style.willChange = 'transform';
|
||||
this.onKitMove = (ev) => {
|
||||
const r = kit.getBoundingClientRect();
|
||||
const rx = ((ev.clientY - r.top) / r.height - 0.5) * -8;
|
||||
const ry = ((ev.clientX - r.left) / r.width - 0.5) * 10;
|
||||
if (g) g.to(kit, { rotateX: rx, rotateY: ry, duration: 0.5, ease: 'power2.out', transformPerspective: 1100 });
|
||||
};
|
||||
this.onKitLeave = () => { if (g) g.to(kit, { rotateX: 0, rotateY: 0, duration: 0.7, ease: 'power3.out' }); };
|
||||
kit.addEventListener('pointermove', this.onKitMove);
|
||||
kit.addEventListener('pointerleave', this.onKitLeave);
|
||||
}
|
||||
// magnetic CTAs
|
||||
if (g && !reduced && window.matchMedia('(pointer:fine)').matches) {
|
||||
document.querySelectorAll('[data-magnetic]').forEach((el) => {
|
||||
el.addEventListener('pointermove', (ev) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
g.to(el, { x: (ev.clientX - r.left - r.width / 2) * 0.18, y: (ev.clientY - r.top - r.height / 2) * 0.25, duration: 0.4, ease: 'power2.out' });
|
||||
});
|
||||
el.addEventListener('pointerleave', () => g.to(el, { x: 0, y: 0, duration: 0.55, ease: 'elastic.out(1,0.55)' }));
|
||||
});
|
||||
}
|
||||
// responsive chrome: hide ruler + nav links on narrow viewports
|
||||
this.onResize = () => {
|
||||
const narrow = window.innerWidth < 760;
|
||||
const ruler = document.querySelector('[data-ruler]');
|
||||
const links = document.querySelector('[data-navlinks]');
|
||||
if (ruler) ruler.style.display = narrow ? 'none' : 'block';
|
||||
if (links) links.style.display = narrow ? 'none' : 'flex';
|
||||
};
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
// ambient glow drift
|
||||
const glow = document.querySelector('[data-glow]');
|
||||
if (glow && g && !reduced) g.to(glow, { y: 34, x: -20, duration: 7, yoyo: true, repeat: -1, ease: 'sine.inOut' });
|
||||
this.spy = ['system', 'refusal', 'code', 'craft'].map((id) => ({ id, el: document.getElementById(id) }));
|
||||
this.links = Array.from(document.querySelectorAll('[data-navlinks] a'));
|
||||
this.onScroll = () => {
|
||||
const doc = document.documentElement;
|
||||
const p = doc.scrollTop / Math.max(1, doc.scrollHeight - doc.clientHeight);
|
||||
const dot = document.querySelector('[data-ruler-dot]');
|
||||
if (dot) dot.style.top = (p * (window.innerHeight - 2)) + 'px';
|
||||
const nav = document.querySelector('[data-nav]');
|
||||
if (nav) {
|
||||
nav.style.background = doc.scrollTop > 24 ? 'rgba(14,14,13,.88)' : 'transparent';
|
||||
nav.style.backdropFilter = doc.scrollTop > 24 ? 'blur(10px)' : 'none';
|
||||
nav.style.borderBottomColor = doc.scrollTop > 24 ? '#262624' : 'transparent';
|
||||
}
|
||||
// scroll-spy: quiet accent on the active section link
|
||||
if (this.spy && this.links) {
|
||||
const y = doc.scrollTop + window.innerHeight * 0.38;
|
||||
let active = -1;
|
||||
this.spy.forEach((sct, i) => { if (sct.el && sct.el.offsetTop <= y) active = i; });
|
||||
this.links.forEach((a, i) => {
|
||||
a.style.color = i === active ? '#F4F4F2' : '#A6A6A1';
|
||||
a.style.borderBottom = i === active ? '1px solid #F4F4F2' : '1px solid transparent';
|
||||
a.style.paddingBottom = '3px';
|
||||
});
|
||||
}
|
||||
};
|
||||
window.addEventListener('scroll', this.onScroll, { passive: true });
|
||||
this.onScroll();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.tick) clearInterval(this.tick);
|
||||
if (this.io) this.io.disconnect();
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
if (this.onResize) window.removeEventListener('resize', this.onResize);
|
||||
if (window.ScrollTrigger) window.ScrollTrigger.getAll().forEach((t) => t.kill());
|
||||
}
|
||||
|
||||
renderVals() {
|
||||
const s = this.state;
|
||||
const ka = s.brand;
|
||||
const kas = this.mix(ka, '#000000', 0.28);
|
||||
const kd = this.mix(ka, '#FFFFFF', 0.86);
|
||||
const krb = Math.max(4, Math.round(s.radius * 0.66)) + 'px';
|
||||
const seg = (label, active, onClick) => ({
|
||||
label, onClick,
|
||||
style: { flex: 1, textAlign: 'center', padding: '6px 8px', borderRadius: '2px', fontSize: '11px', fontFamily: this.MONO, background: active ? '#1B1B1A' : 'transparent', color: active ? '#F4F4F2' : '#82827C', border: active ? '1px solid #3A3A37' : '1px solid #262624' }
|
||||
});
|
||||
const p = s.polished;
|
||||
const trans = 'all .45s cubic-bezier(.2,.7,.2,1)';
|
||||
const chip = (label, done) => ({
|
||||
label: (done ? '✓ ' : '· ') + label,
|
||||
style: { alignSelf: 'flex-start', fontFamily: this.MONO, fontSize: '11px', padding: '5px 11px', borderRadius: '2px', transition: trans, color: done ? '#F4F4F2' : '#A6A6A1', background: done ? 'rgba(244,244,242,.08)' : '#161615', border: done ? '1px solid rgba(244,244,242,.22)' : '1px solid #262624', textDecoration: done ? 'line-through' : 'none' }
|
||||
});
|
||||
return {
|
||||
brandHex: s.brand.toUpperCase(),
|
||||
brandSwatches: this.BRANDS.map((hex) => ({
|
||||
hex,
|
||||
onPick: () => this.setState({ brand: hex }),
|
||||
style: { width: '28px', height: '28px', borderRadius: '2px', background: hex, flex: '0 0 auto', boxShadow: s.brand === hex ? '0 0 0 2px #161615, 0 0 0 4px #F4F4F2' : 'inset 0 0 0 1px rgba(255,255,255,.14)' }
|
||||
})),
|
||||
radiusVal: s.radius,
|
||||
radiusLabel: s.radius + 'px',
|
||||
onRadius: (e) => this.setState({ radius: +e.target.value }),
|
||||
fontSegs: this.FONTS.map((f) => seg(f.split(' ')[0], s.font === f, () => this.setState({ font: f }))),
|
||||
kitVars: {
|
||||
display: 'flex', flexDirection: 'column', gap: '16px', background: '#FFFFFF', border: '1px solid #ECE8E2', borderRadius: '2px', padding: '20px',
|
||||
'--ka': ka, '--kas': kas, '--kd': kd, '--ki': '#1C1917', '--km': '#78716C', '--kl': '#E7E5E4', '--krb': krb, '--kf': "'" + s.font + "',sans-serif"
|
||||
},
|
||||
polishChips: [
|
||||
chip('gap 18px → 16px (space-4)', p),
|
||||
chip('display heading → font-bold', p),
|
||||
chip('subcopy → neutral-600 · AA 7.2:1', p)
|
||||
],
|
||||
demoBadgeStyle: { alignSelf: 'center', background: kd, color: kas, borderRadius: '2px', padding: '4px 12px', fontSize: '11.5px', fontWeight: 600, fontFamily: "'Bricolage Grotesque',sans-serif", transition: trans },
|
||||
demoHeadStyle: { fontFamily: "'Bricolage Grotesque',sans-serif", fontSize: '30px', lineHeight: 1.12, letterSpacing: '-.6px', color: '#1C1917', maxWidth: '340px', marginTop: p ? '16px' : '18px', fontWeight: p ? 700 : 400, transition: trans, textWrap: 'balance' },
|
||||
demoSubStyle: { fontSize: '14.5px', lineHeight: 1.55, color: p ? '#57534E' : '#A8A29E', marginTop: p ? '12px' : '18px', transition: trans },
|
||||
demoCtaRowStyle: { display: 'flex', gap: '12px', marginTop: p ? '20px' : '18px', transition: trans },
|
||||
demoCta1Style: { background: ka, color: '#fff', borderRadius: krb, padding: '10px 18px', fontFamily: "'Bricolage Grotesque',sans-serif", fontWeight: 600, fontSize: '14px', transition: trans },
|
||||
demoCta2Style: { background: p ? '#FFFFFF' : ka, color: p ? '#1C1917' : '#fff', border: p ? '1px solid #D6D3D1' : '1px solid transparent', borderRadius: krb, padding: '10px 18px', fontFamily: "'Bricolage Grotesque',sans-serif", fontWeight: 600, fontSize: '14px', transition: trans },
|
||||
togglePolish: () => this.setState({ polished: !p }),
|
||||
polishBtnLabel: p ? 'Undo' : '✧ Run Polish',
|
||||
polishBtnStyle: p
|
||||
? { color: '#82827C', fontSize: '13px', fontWeight: 500, padding: '11px 6px' }
|
||||
: { background: '#F4F4F2', color: '#0E0E0D', padding: '11px 20px', borderRadius: '2px', fontSize: '13.5px', fontWeight: 600 },
|
||||
polishNote: p ? '3 fixes — all in Solstice tokens' : 'weight 400 display · gap off-scale · AA fail'
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user