carlos / carloku Public

Merge pull request #1 from carlosframework/flagship-redesign

Flagship redesign: canon app-platform landing at Fly/Railway craft
Paul Campbell pushed by claude agent, operated by paul@keymail.dev d0a5b98a173553922b89ed72fe78248fdec2ab99 parent bacc3d0parent 4b33310
11 files changed, +1100 −120
  • .impeccable/design.json +145 −0
  • DESIGN.md +227 −0
  • fonts/Geist-Variable.woff2 binary
  • fonts/GeistMono-Variable.woff2 binary
  • fonts/LICENSE-OFL.txt +92 −0
  • get-started/index.html +41 −13
  • img/dashboard-dark.png binary
  • img/dashboard-light.png binary
  • index.html +174 −45
  • site.css +365 −62
  • site.js +56 −0
diff --git a/.impeccable/design.json b/.impeccable/design.json
new file mode 100644
index 0000000..0705c28
--- /dev/null
+++ b/.impeccable/design.json
@@ -0,0 +1,145 @@
+{
+ "schemaVersion": 2,
+ "generatedAt": "2026-08-06T23:59:00Z",
+ "title": "Design System: Carloku",
+ "extensions": {
+ "colorMeta": {
+ "carloku-purple": { "role": "primary", "displayName": "Carloku Purple", "canonical": "#5b3e96", "tonalRamp": ["#160f26", "#241a3e", "#352659", "#483475", "#5b3e96", "#7d63b5", "#a48fd0", "#d6cbea"] },
+ "lifted-purple": { "role": "primary", "displayName": "Lifted Purple (dark-scheme accent, terminal prompt)", "canonical": "#b394f2", "tonalRamp": ["#1c1230", "#33235a", "#4c3684", "#6a4fb0", "#8a6fd6", "#b394f2", "#cdb7f7", "#e8defb"] },
+ "button-purple-dark-scheme": { "role": "primary", "displayName": "Dark-Scheme Button Purple", "canonical": "#6a48c8", "tonalRamp": ["#150e28", "#271a48", "#3b286f", "#513897", "#6a48c8", "#7d5cdb", "#a289e8", "#d1c4f4"] },
+ "ink": { "role": "neutral", "displayName": "Ink (purple-cast near-black)", "canonical": "#1d1927", "tonalRamp": ["#0e0c13", "#1d1927", "#332d42", "#4d4562", "#6a6183", "#8b83a3", "#b1abc4", "#dcd8e6"] },
+ "paper": { "role": "neutral", "displayName": "Paper", "canonical": "#ffffff", "tonalRamp": ["#14111c", "#2b2638", "#4a4459", "#6e687e", "#948fa3", "#bab6c6", "#dedbe6", "#ffffff"] },
+ "night": { "role": "neutral", "displayName": "Night (dark-scheme background)", "canonical": "#0e0c13", "tonalRamp": ["#0e0c13", "#14111c", "#221e2e", "#363044", "#4e475f", "#6b647e", "#918aa3", "#c4bfd1"] },
+ "band-purple": { "role": "primary", "displayName": "Band Purple (full-bleed band)", "canonical": "#221a35", "tonalRamp": ["#120d1d", "#221a35", "#33284f", "#48396e", "#5f4d8e", "#7c6bab", "#a094c7", "#cdc5e2"] },
+ "terminal-black": { "role": "neutral", "displayName": "Terminal Black (scheme-fixed)", "canonical": "#120f1a", "tonalRamp": ["#120f1a", "#1b1626", "#2a2338", "#3d3450", "#564b6e", "#746a8d", "#9a92b0", "#ccc7d9"] },
+ "terminal-ok-green": { "role": "tertiary", "displayName": "Terminal OK Green (terminal-only)", "canonical": "#7ee2a8", "tonalRamp": ["#0b2417", "#14402a", "#1f5f3f", "#2d8157", "#43a571", "#5fc78d", "#7ee2a8", "#b6f0cf"] },
+ "muted": { "role": "neutral", "displayName": "Muted (secondary copy)", "canonical": "#5d5570", "tonalRamp": ["#171422", "#2b2739", "#413c52", "#5d5570", "#7a7290", "#a9a0c2", "#c6bfd9", "#e4e0ee"] },
+ "hairline": { "role": "neutral", "displayName": "Hairline", "canonical": "rgba(29, 25, 39, 0.1)", "tonalRamp": ["rgba(29,25,39,0.16)", "rgba(29,25,39,0.1)", "rgba(242,239,248,0.09)", "rgba(242,239,248,0.16)", "rgba(255,255,255,0.08)", "rgba(242,238,249,0.14)", "rgba(242,238,249,0.12)", "rgba(255,255,255,0.06)"] }
+ },
+ "typographyMeta": {
+ "display": { "displayName": "Display", "purpose": "Hero headline only; text-wrap: balance; may end in an accent-purple period." },
+ "headline": { "displayName": "Headline", "purpose": "Section h2s; closing CTA scales up to clamp(2rem, 4vw, 3rem), guide h2s down to 1.45rem." },
+ "title": { "displayName": "Title", "purpose": "Feature and step h3s." },
+ "lede": { "displayName": "Lede", "purpose": "Hero standfirst (34rem max) and section ledes (1.08rem, 38rem max), muted color, text-wrap: pretty." },
+ "body": { "displayName": "Body", "purpose": "Default prose; guide copy capped at 42rem." },
+ "mono": { "displayName": "Mono", "purpose": "Terminal, pre blocks, inline command chips, step numerals; tabular-nums inside the terminal." }
+ },
+ "shadows": [
+ { "name": "frame", "value": "0 1px 2px rgba(20, 12, 40, 0.05), 0 12px 32px -12px rgba(20, 12, 40, 0.18)", "purpose": "Framed dark media (terminal, dashboard frame), light scheme. Dark scheme: 0 1px 2px rgba(0,0,0,0.4), 0 16px 40px -16px rgba(0,0,0,0.55)." },
+ { "name": "button-primary", "value": "0 1px 2px rgba(20, 12, 40, 0.2), 0 6px 16px -6px color-mix(in srgb, var(--btn-bg) 55%, transparent)", "purpose": "Primary buttons on page surfaces; removed inside the band." }
+ ],
+ "motion": [
+ { "name": "state-transition", "value": "120ms–160ms ease-out", "purpose": "Hover/scroll state changes: nav color 120ms, header hairline 160ms, buttons 130ms." },
+ { "name": "terminal-typing", "value": "14ms per character; 120–260ms line delays; 450ms initial delay", "purpose": "Hero terminal types its session once on load; skipped under prefers-reduced-motion." },
+ { "name": "caret-blink", "value": "caret 1.1s steps(1) infinite", "purpose": "Terminal caret blink." },
+ { "name": "reduced-motion", "value": "*, *::before, *::after { animation: none !important; transition: none !important; }", "purpose": "Global kill switch under prefers-reduced-motion, plus scroll-behavior: auto." }
+ ],
+ "breakpoints": [
+ { "name": "header-wrap", "value": "44rem" },
+ { "name": "single-column", "value": "62rem" }
+ ]
+ },
+ "components": [
+ {
+ "name": "Primary Button",
+ "kind": "button",
+ "refersTo": "button-primary",
+ "description": "The main CTA: Carloku Purple fill, white text, purple-cast ambient shadow, press-down active state.",
+ "html": "<a class=\"ds-btn-primary\" href=\"#\">Get started</a>",
+ "css": ".ds-btn-primary { display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none; font-family: var(--sans, 'Geist', ui-sans-serif, system-ui, sans-serif); font-weight: 580; font-size: 1rem; border-radius: 0.65rem; padding: 0.72rem 1.4rem; background: var(--btn-bg, #5b3e96); color: var(--btn-ink, #ffffff); box-shadow: 0 1px 2px rgba(20, 12, 40, 0.2), 0 6px 16px -6px rgba(91, 62, 150, 0.55); transition: background 130ms ease-out, transform 130ms ease-out; } .ds-btn-primary:hover { background: var(--btn-bg-hover, #4c3380); } .ds-btn-primary:active { transform: translateY(1px); } .ds-btn-primary:focus-visible { outline: 2px solid var(--accent, #5b3e96); outline-offset: 3px; }"
+ },
+ {
+ "name": "Quiet Button",
+ "kind": "button",
+ "refersTo": "button-quiet",
+ "description": "Secondary CTA: hairline border, ink text; hover turns accent over a soft purple wash.",
+ "html": "<a class=\"ds-btn-quiet\" href=\"#\">Open the dashboard</a>",
+ "css": ".ds-btn-quiet { display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none; font-family: var(--sans, 'Geist', ui-sans-serif, system-ui, sans-serif); font-weight: 580; font-size: 1rem; border-radius: 0.65rem; padding: 0.72rem 1.4rem; color: var(--ink, #1d1927); border: 1px solid var(--line-strong, rgba(29, 25, 39, 0.16)); transition: background 130ms ease-out, border-color 130ms ease-out, color 130ms ease-out, transform 130ms ease-out; } .ds-btn-quiet:hover { border-color: var(--accent, #5b3e96); color: var(--accent, #5b3e96); background: var(--accent-soft, rgba(91, 62, 150, 0.08)); } .ds-btn-quiet:active { transform: translateY(1px); } .ds-btn-quiet:focus-visible { outline: 2px solid var(--accent, #5b3e96); outline-offset: 3px; }"
+ },
+ {
+ "name": "Header CTA",
+ "kind": "button",
+ "refersTo": "head-cta",
+ "description": "Compact quiet-button variant in accent color, used once in the sticky header.",
+ "html": "<a class=\"ds-head-cta\" href=\"#\">Dashboard</a>",
+ "css": ".ds-head-cta { font-family: var(--sans, 'Geist', ui-sans-serif, system-ui, sans-serif); color: var(--accent, #5b3e96); font-weight: 560; font-size: 0.95rem; text-decoration: none; white-space: nowrap; border: 1px solid var(--line-strong, rgba(29, 25, 39, 0.16)); border-radius: 0.55rem; padding: 0.42rem 0.95rem; transition: border-color 120ms ease-out, background 120ms ease-out, color 120ms ease-out; } .ds-head-cta:hover { border-color: var(--accent, #5b3e96); background: var(--accent-soft, rgba(91, 62, 150, 0.08)); } .ds-head-cta:focus-visible { outline: 2px solid var(--accent, #5b3e96); outline-offset: 3px; }"
+ },
+ {
+ "name": "Code Chip",
+ "kind": "chip",
+ "refersTo": "code-chip",
+ "description": "Inline command incantation: mono on an accent-soft wash. The landing page's jargon budget is three of these.",
+ "html": "<code class=\"ds-code-chip\">carlos ship</code>",
+ "css": ".ds-code-chip { font-family: var(--mono, 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 0.88em; background: var(--accent-soft, rgba(91, 62, 150, 0.08)); color: var(--accent-ink, #4c3380); padding: 0.12em 0.4em; border-radius: 0.3em; }"
+ },
+ {
+ "name": "Terminal Frame",
+ "kind": "custom",
+ "refersTo": "terminal-frame",
+ "description": "The signature component: scheme-fixed dark terminal with chrome bar, prompt grammar, and OK-green success lines.",
+ "html": "<div class=\"ds-term\"><div class=\"ds-term-bar\"><i></i><i></i><i></i><span>you@laptop — carlos</span></div><div class=\"ds-term-body\"><span class=\"ds-line ds-cmd\">carlos ship -app hello -kind static -version v4 ./public</span><span class=\"ds-line ds-ok\">shipped hello v4</span><span class=\"ds-line ds-cmd\">carlos promote -app hello v4 stable</span><span class=\"ds-line ds-ok\">promoted hello v4 -&gt; stable</span><span class=\"ds-line\"><span class=\"ds-caret\"></span></span></div></div>",
+ "css": ".ds-term { background: #120f1a; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 0.85rem; box-shadow: 0 1px 2px rgba(20, 12, 40, 0.05), 0 12px 32px -12px rgba(20, 12, 40, 0.18); overflow: hidden; font-family: var(--mono, 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 0.86rem; font-variant-numeric: tabular-nums; line-height: 1.75; } .ds-term-bar { display: flex; align-items: center; gap: 0.45rem; background: #1b1626; border-bottom: 1px solid rgba(255, 255, 255, 0.08); padding: 0.6rem 0.9rem; } .ds-term-bar i { width: 0.68rem; height: 0.68rem; border-radius: 50%; background: #8d84a6; opacity: 0.28; } .ds-term-bar span { margin-left: 0.4rem; color: #8d84a6; font-size: 0.78rem; letter-spacing: 0.01em; } .ds-term-body { padding: 1.05rem 1.15rem 1.2rem; } .ds-line { display: block; white-space: pre-wrap; word-break: break-word; color: #9d93b8; } .ds-cmd { color: #ede9f7; } .ds-cmd::before { content: '$ '; color: #b394f2; font-weight: 560; } .ds-ok { color: #7ee2a8; } .ds-caret { display: inline-block; width: 0.55em; height: 1.05em; background: #b394f2; vertical-align: text-bottom; animation: ds-caret 1.1s steps(1) infinite; } @keyframes ds-caret { 50% { opacity: 0; } }"
+ },
+ {
+ "name": "Feature List Item",
+ "kind": "card",
+ "refersTo": "",
+ "description": "Borderless feature row: inline stroke-SVG icon in an accent-soft tile, title + muted copy, hairline rule between siblings.",
+ "html": "<ul class=\"ds-features\"><li><div class=\"ds-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg></div><div><h3>Certificates</h3><p>Point your domain at Carloku and the padlock appears. Renewals happen without you.</p></div></li><li><div class=\"ds-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/></svg></div><div><h3>Idle servers</h3><p>Apps doze when nobody's around and wake the instant someone shows up.</p></div></li></ul>",
+ "css": ".ds-features { list-style: none; padding: 0; margin: 0; font-family: var(--sans, 'Geist', ui-sans-serif, system-ui, sans-serif); } .ds-features li { display: grid; grid-template-columns: auto 1fr; gap: 1.15rem; align-items: start; padding: 1.35rem 0; } .ds-features li + li { border-top: 1px solid var(--line, rgba(29, 25, 39, 0.1)); } .ds-icon { width: 2.4rem; height: 2.4rem; border-radius: 0.6rem; display: grid; place-items: center; background: var(--accent-soft, rgba(91, 62, 150, 0.08)); color: var(--accent, #5b3e96); margin-top: 0.15rem; } .ds-icon svg { width: 1.25rem; height: 1.25rem; } .ds-features h3 { font-size: 1.08rem; font-weight: 620; letter-spacing: -0.01em; margin: 0 0 0.3rem; color: var(--ink, #1d1927); } .ds-features p { color: var(--muted, #5d5570); font-size: 0.97rem; margin: 0; line-height: 1.6; }"
+ },
+ {
+ "name": "Nav Link",
+ "kind": "nav",
+ "refersTo": "",
+ "description": "Sticky-header nav link: muted at weight 480, hover to ink, color transition only.",
+ "html": "<nav class=\"ds-nav\"><a href=\"#\">How it works</a><a href=\"#\">Open source</a><a href=\"#\">Get started</a></nav>",
+ "css": ".ds-nav { display: flex; gap: 1.75rem; align-items: center; font-family: var(--sans, 'Geist', ui-sans-serif, system-ui, sans-serif); } .ds-nav a { color: var(--muted, #5d5570); text-decoration: none; font-size: 0.95rem; font-weight: 480; white-space: nowrap; transition: color 120ms ease-out; } .ds-nav a:hover { color: var(--ink, #1d1927); } .ds-nav a:focus-visible { outline: 2px solid var(--accent, #5b3e96); outline-offset: 3px; border-radius: 0.25em; }"
+ },
+ {
+ "name": "Step Numeral",
+ "kind": "custom",
+ "refersTo": "",
+ "description": "Counter-driven mono numeral in an accent-soft circle, hung above each workflow step.",
+ "html": "<ol class=\"ds-steps\"><li><h3><code>carlos ship</code></h3><p>Your app goes up, and you get a private link to the real, running thing.</p></li><li><h3><code>carlos promote</code></h3><p>The exact thing you just looked at becomes the public thing.</p></li></ol>",
+ "css": ".ds-steps { list-style: none; padding: 0; margin: 0; counter-reset: ds-step; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.75rem; font-family: var(--sans, 'Geist', ui-sans-serif, system-ui, sans-serif); } .ds-steps li { position: relative; padding-top: 1.15rem; } .ds-steps li::before { content: counter(ds-step); counter-increment: ds-step; position: absolute; top: -0.9rem; left: 0; font-family: var(--mono, 'Geist Mono', ui-monospace, monospace); font-weight: 560; font-size: 0.82rem; color: var(--accent, #5b3e96); background: var(--accent-soft, rgba(91, 62, 150, 0.08)); width: 1.85rem; height: 1.85rem; border-radius: 50%; display: grid; place-items: center; } .ds-steps h3 { font-size: 1.12rem; font-weight: 620; letter-spacing: -0.01em; margin: 0.9rem 0 0.45rem; color: var(--ink, #1d1927); } .ds-steps p { color: var(--muted, #5d5570); font-size: 0.98rem; margin: 0; line-height: 1.6; } .ds-steps code { font-family: var(--mono, 'Geist Mono', ui-monospace, monospace); font-size: 0.88em; background: var(--accent-soft, rgba(91, 62, 150, 0.08)); color: var(--accent-ink, #4c3380); padding: 0.12em 0.4em; border-radius: 0.3em; }"
+ }
+ ],
+ "narrative": {
+ "northStar": "The Quiet Standard",
+ "overview": "Carloku's site is the category-standard app-platform page — hero left, terminal right, three steps, feature list, full-bleed band, closing CTA — played completely straight and won on finish, not on invention. Every surface is white (or near-black) paper crossed by hairline borders; a single purple carries the entire brand voice; Geist and Geist Mono do all the talking. The one theatrical object is the hero terminal, which types a real ship → promote → rollback session and wears the same dark clothes in both color schemes. The system is genuinely two-scheme: prefers-color-scheme drives a full token swap declared as CSS custom properties on :root. The build is dependency-free by rule — no build step, no external requests, self-hosted variable fonts only, vanilla JS — so the token layer is the stylesheet; there is no second source of truth.",
+ "keyCharacteristics": [
+ "One accent purple per scheme; its rarity is its authority.",
+ "Hairline borders (1px, low-alpha ink) do almost all the structural work; shadows exist only under framed media and the primary button.",
+ "Variable-font weights at non-standard steps (480, 560, 580, 620, 650, 680) — the ramp is tuned, not defaulted.",
+ "A terminal that never changes scheme: fixed dark tokens (--term-*) with no dark-mode override.",
+ "Exactly one deep-purple full-bleed band per page.",
+ "Motion is small and fast (120–160ms ease-out) with a total prefers-reduced-motion kill switch."
+ ],
+ "rules": [
+ { "name": "The One Voice Rule", "body": "One purple per scheme carries links, focus, selection, buttons, and icons. No second accent hue ever appears on page surfaces; the terminal's green is terminal furniture, not a palette member.", "section": "colors" },
+ { "name": "The Terminal Stays Dark Rule", "body": "--term-* tokens have no dark-scheme override. Anything framed as a terminal or console capture (.term, .dash-frame) sits on Terminal Black in both schemes.", "section": "colors" },
+ { "name": "The No-Pure-Black Rule", "body": "Ink and backgrounds are always purple-cast (#1d1927, #0e0c13, #120f1a) — never #000.", "section": "colors" },
+ { "name": "The Off-Grid Weight Rule", "body": "Weights come from the variable axis at tuned steps — 480 (nav), 560 (emphasis/mono), 580 (buttons), 620/650/680 (titles up to display) — never the flat 400/700 defaults.", "section": "typography" },
+ { "name": "The Tighter-As-Bigger Rule", "body": "Tracking tightens with size: -0.01em at title, -0.025em at headline, -0.032em at display; body stays normal.", "section": "typography" },
+ { "name": "The Purple Period Rule", "body": "A display or closing headline may end its sentence with an accent-colored full stop (.dot) — the accent's only appearance inside running display type.", "section": "typography" },
+ { "name": "The Two-Shadows Rule", "body": "Only framed dark media and the primary button cast shadows. Cards, sections, the header, and everything else are flat, defined by hairlines and tint shifts.", "section": "elevation" }
+ ],
+ "dos": [
+ "Do keep both color schemes complete: every new token needs a light value on :root and a dark value in the prefers-color-scheme: dark block, plus a theme-color meta if it changes page chrome.",
+ "Do draw structure with hairlines and tint shifts (--line, --bg-tint), reserving shadows for framed dark media and the primary button.",
+ "Do put anything terminal- or console-shaped on the fixed --term-* palette in both schemes.",
+ "Do use tuned variable weights (480/560/580/620/650/680) and tighter tracking as type grows.",
+ "Do keep motion at 120–160ms ease-out, and gate all animation and smooth scroll behind prefers-reduced-motion (the global kill switch already exists — don't bypass it).",
+ "Do stay dependency-free: no build step, no external requests, self-hosted fonts only, inline SVG icons, vanilla JS."
+ ],
+ "donts": [
+ "Don't introduce a second accent hue on page surfaces; the terminal's OK green never leaves the terminal.",
+ "Don't use pure #000 or #fff for ink/dark backgrounds — near-blacks are purple-cast (#1d1927, #0e0c13, #120f1a). (The one white exception: text on purple button fills.)",
+ "Don't box content in bordered cards; siblings separate with single hairline rules or whitespace.",
+ "Don't add a second full-bleed purple band to a page — one per page is the ceiling.",
+ "Don't use hard-offset shadows, borders heavier than 1px, or icon fonts/CDN icons.",
+ "Don't let dark-scheme buttons use the lifted accent (#b394f2) as a fill; buttons keep their own deeper fills (#6a48c8/#7d5cdb) so white text stays legible."
+ ]
+ }
+}
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..02be172
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,227 @@
+---
+name: Carloku
+description: The hosted CARLOS platform — the category-standard app-platform page, out-finished in one purple.
+colors:
+ carloku-purple: "#5b3e96"
+ carloku-purple-deep: "#4c3380"
+ carloku-purple-soft: "rgba(91, 62, 150, 0.08)"
+ lifted-purple: "#b394f2"
+ button-purple-dark-scheme: "#6a48c8"
+ ink: "#1d1927"
+ moon-ink: "#f2eff8"
+ paper: "#ffffff"
+ night: "#0e0c13"
+ paper-tint: "#f8f7fb"
+ night-tint: "#14111c"
+ muted: "#5d5570"
+ muted-dark-scheme: "#a9a0c2"
+ band-purple: "#221a35"
+ band-purple-dark-scheme: "#261b44"
+ terminal-black: "#120f1a"
+ terminal-chrome: "#1b1626"
+ terminal-ink: "#ede9f7"
+ terminal-out: "#9d93b8"
+ terminal-ok-green: "#7ee2a8"
+ hairline: "rgba(29, 25, 39, 0.1)"
+ hairline-strong: "rgba(29, 25, 39, 0.16)"
+ hairline-dark-scheme: "rgba(242, 239, 248, 0.09)"
+typography:
+ display:
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, sans-serif"
+ fontSize: "clamp(2.6rem, 5.4vw, 4.1rem)"
+ fontWeight: 680
+ lineHeight: 1.04
+ letterSpacing: "-0.032em"
+ headline:
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, sans-serif"
+ fontSize: "clamp(1.7rem, 3vw, 2.3rem)"
+ fontWeight: 650
+ lineHeight: 1.15
+ letterSpacing: "-0.025em"
+ title:
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, sans-serif"
+ fontSize: "1.08rem"
+ fontWeight: 620
+ letterSpacing: "-0.01em"
+ body:
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, sans-serif"
+ fontSize: "1rem"
+ fontWeight: 400
+ lineHeight: 1.6
+ lede:
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, sans-serif"
+ fontSize: "1.17rem"
+ fontWeight: 400
+ lineHeight: 1.65
+ mono:
+ fontFamily: "Geist Mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
+ fontSize: "0.86rem"
+ fontWeight: 400
+ lineHeight: 1.75
+rounded:
+ chip: "0.3em"
+ control: "0.65rem"
+ frame: "0.85rem"
+spacing:
+ gutter: "1.5rem"
+ section: "5.5rem"
+ band: "6rem"
+ close: "7rem"
+components:
+ button-primary:
+ backgroundColor: "{colors.carloku-purple}"
+ textColor: "#ffffff"
+ rounded: "{rounded.control}"
+ padding: "0.72rem 1.4rem"
+ button-primary-hover:
+ backgroundColor: "{colors.carloku-purple-deep}"
+ button-quiet:
+ textColor: "{colors.ink}"
+ rounded: "{rounded.control}"
+ padding: "0.72rem 1.4rem"
+ head-cta:
+ textColor: "{colors.carloku-purple}"
+ rounded: "0.55rem"
+ padding: "0.42rem 0.95rem"
+ code-chip:
+ backgroundColor: "{colors.carloku-purple-soft}"
+ textColor: "{colors.carloku-purple-deep}"
+ rounded: "{rounded.chip}"
+ padding: "0.12em 0.4em"
+ terminal-frame:
+ backgroundColor: "{colors.terminal-black}"
+ rounded: "{rounded.frame}"
+---
+
+# Design System: Carloku
+
+## Overview
+
+**Creative North Star: "The Quiet Standard"**
+
+Carloku's site is the category-standard app-platform page — hero left, terminal right, three steps, feature list, full-bleed band, closing CTA — played completely straight and won on finish, not on invention. Every surface is white (or near-black) paper crossed by hairline borders; a single purple carries the entire brand voice; Geist and Geist Mono do all the talking. The one theatrical object is the hero terminal, which types a real `ship → promote → rollback` session and wears the same dark clothes in both color schemes.
+
+The system is genuinely two-scheme: `prefers-color-scheme` drives a full token swap (white-with-purple light, near-black-with-purple dark) declared as CSS custom properties on `:root`. Both schemes are required, not optional. The build is dependency-free by rule — no build step, no external requests, self-hosted variable fonts only, vanilla JS — so the token layer *is* the stylesheet; there is no second source of truth.
+
+**Key Characteristics:**
+- One accent purple per scheme; its rarity is its authority.
+- Hairline borders (1px, low-alpha ink) do almost all the structural work; shadows exist only under framed media and the primary button.
+- Variable-font weights at non-standard steps (480, 560, 580, 620, 650, 680) — the ramp is tuned, not defaulted.
+- A terminal that never changes scheme: fixed dark tokens (`--term-*`) with no dark-mode override.
+- Exactly one deep-purple full-bleed band per page.
+- Motion is small and fast (120–160ms ease-out) with a total `prefers-reduced-motion` kill switch.
+
+## Colors
+
+Two mirrored monochrome-plus-purple schemes: purple is the only voice, everything else is ink, paper, and hairlines.
+
+### Primary
+- **Carloku Purple** (#5b3e96): the light-scheme accent and primary button fill; also the hardcoded brand-mark fill in the logo SVG and favicon. Used for links, focus outlines, `::selection`, step numerals, and feature-icon strokes.
+- **Carloku Purple Deep** (#4c3380): hover state of the primary button; text color of inline code chips (as `--accent-ink`).
+- **Carloku Purple Soft** (rgba(91, 62, 150, 0.08)): the accent at 8% alpha — background of code chips, icon tiles, step numerals, and quiet-button hover washes. In dark scheme it becomes the lifted purple at 10% alpha.
+- **Lifted Purple** (#b394f2): the dark-scheme accent (links, focus, icons) — and, in *both* schemes, the terminal's prompt color and caret. Dark-scheme buttons use their own fill (**#6a48c8**, hover #7d5cdb) because the lifted accent is too light to carry white text.
+
+### Neutral
+- **Ink** (#1d1927): body text, light scheme — a purple-cast near-black, never pure #000. Dark-scheme ink is **Moon Ink** (#f2eff8).
+- **Paper** (#ffffff) / **Night** (#0e0c13): page backgrounds. Each has a tint sibling (**#f8f7fb** / **#14111c**) used only for the alternating `.tint` section.
+- **Muted** (#5d5570 light / #a9a0c2 dark): all secondary copy — ledes, feature paragraphs, footer. A step fainter (`--faint`: #6e6880 / #928aab) handles captions and asides.
+- **Hairlines** (rgba(29,25,39,0.1) light / rgba(242,239,248,0.09) dark): every border on page surfaces; a `-strong` sibling at 0.16 alpha for quiet-button borders and note rules.
+
+### Terminal (scheme-fixed)
+- **Terminal Black** (#120f1a) with chrome bar **#1b1626**: the terminal and dashboard-frame background in both schemes.
+- **Terminal Ink** (#ede9f7) for typed commands, **Terminal Out** (#9d93b8) for program output, **#8d84a6** for chrome titles and comments.
+- **Terminal OK Green** (#7ee2a8): success lines only. This is the only non-purple hue in the entire system, and it exists only inside the terminal.
+
+### Band
+- **Band Purple** (#221a35 light / #261b44 dark): the one full-bleed deep-purple section. It carries its own sub-palette: ink #f2eef9, muted #b9aed6, hairlines at 12–14% alpha of the band ink, and *inverted* buttons (band-ink fill, band-bg text).
+
+### Named Rules
+**The One Voice Rule.** One purple per scheme carries links, focus, selection, buttons, and icons. No second accent hue ever appears on page surfaces; the terminal's green is terminal furniture, not a palette member.
+**The Terminal Stays Dark Rule.** `--term-*` tokens have no dark-scheme override. Anything framed as a terminal or console capture (`.term`, `.dash-frame`) sits on Terminal Black in both schemes.
+**The No-Pure-Black Rule.** Ink and backgrounds are always purple-cast (#1d1927, #0e0c13, #120f1a) — never #000.
+
+## Typography
+
+**Display/Body Font:** Geist variable (with ui-sans-serif, system-ui fallback) — self-hosted woff2, weight axis 100–900, preloaded.
+**Mono Font:** Geist Mono variable (with ui-monospace, SFMono-Regular fallback) — commands, code chips, terminal, step numerals.
+
+**Character:** One contemporary grotesk doing everything, differentiated by finely-tuned variable weights and progressively tighter tracking as size grows. Mono is the product's own voice — commands appear as incantations, never paragraphs.
+
+### Hierarchy
+- **Display** (680, clamp(2.6rem, 5.4vw, 4.1rem), 1.04, -0.032em): the hero headline only; `text-wrap: balance`, with the sentence-final period in accent purple (`.dot`).
+- **Headline** (650, clamp(1.7rem, 3vw, 2.3rem), 1.15, -0.025em): section h2s. The closing CTA scales it up (clamp(2rem, 4vw, 3rem)); guide-page h2s scale it down (1.45rem).
+- **Title** (620, 1.08–1.12rem, -0.01em): feature and step h3s.
+- **Lede** (400, 1.17rem, 1.65): hero standfirst in muted, max-width 34rem; section ledes at 1.08rem, max-width 38rem. Always `text-wrap: pretty`.
+- **Body** (400, 1rem, 1.6): default. Guide prose is capped at 42rem; footer at 44rem.
+- **Mono** (400, 0.86–0.88rem, 1.75): terminal and `pre` blocks, with `font-variant-numeric: tabular-nums` in the terminal. Inline code chips run at 0.88em of their context.
+
+### Named Rules
+**The Off-Grid Weight Rule.** Weights come from the variable axis at tuned steps — 480 (nav), 560 (emphasis/mono), 580 (buttons), 620/650/680 (titles up to display) — never the flat 400/700 defaults.
+**The Tighter-As-Bigger Rule.** Tracking tightens with size: -0.01em at title, -0.025em at headline, -0.032em at display; body stays normal.
+**The Purple Period Rule.** A display or closing headline may end its sentence with an accent-colored full stop (`.dot`) — the accent's only appearance inside running display type.
+
+## Layout
+
+A single centered column, max-width 72rem (`--wrap`) with 1.5rem side gutters; prose pages narrow to 50rem (`.wrap.narrow`). Sections stack at a 5.5rem vertical rhythm (band 6rem, closing CTA 7rem), separated not by rules but by alternating backgrounds: plain, tint (with top+bottom hairlines), band. Two-column grids do the feature work — hero at `1.05fr / 1fr` with 4.5rem gap, quiet-grid at `0.92fr / 1.08fr` — always with `minmax(0, …)` to protect the terminal's overflow. Steps run three-across.
+
+Two breakpoints, both max-width: **62rem** collapses every grid to one column and compresses section padding to 4rem; **44rem** rewraps the sticky header (nav drops to a full-width second row). The header itself is 4rem tall, sticky, background at 82% opacity over 12px backdrop blur, and earns its bottom hairline only after 8px of scroll.
+
+## Elevation & Depth
+
+Essentially flat: hairline borders and background-tint shifts convey nearly all structure. Shadows exist in exactly two places — under framed dark media (terminal, dashboard frame) and under the primary button — and both are soft, large-radius, purple-cast ambient washes, never hard offsets.
+
+### Shadow Vocabulary
+- **Frame shadow** (`--shadow`: `0 1px 2px rgba(20,12,40,0.05), 0 12px 32px -12px rgba(20,12,40,0.18)` light; `0 1px 2px rgba(0,0,0,0.4), 0 16px 40px -16px rgba(0,0,0,0.55)` dark): terminal and dashboard frames only.
+- **Button shadow** (`0 1px 2px rgba(20,12,40,0.2), 0 6px 16px -6px color-mix(in srgb, var(--btn-bg) 55%, transparent)`): primary buttons on page surfaces; removed inside the band.
+
+### Named Rules
+**The Two-Shadows Rule.** Only framed dark media and the primary button cast shadows. Cards, sections, the header, and everything else are flat, defined by hairlines and tint shifts.
+
+## Shapes
+
+Soft-but-businesslike radii in three sizes: **0.3em** for inline chips, **~0.55–0.7rem** for controls (buttons 0.65rem, header CTA 0.55rem, `pre` 0.7rem), **0.85rem** for the big framed objects (terminal, dashboard). Step numerals, terminal traffic-light dots, and icon-tile contents use circles; icon tiles themselves are 2.4rem squares at 0.6rem radius. Borders are always 1px hairlines — there are no 2px+ borders and no hard-cornered rectangles anywhere. The brand mark is a rounded square (rx 14/64) holding an open circular stroke — the same geometry family as the page.
+
+## Components
+
+### Buttons
+- **Shape:** softly rounded (0.65rem), inline-flex, 0.72rem × 1.4rem padding, weight 580.
+- **Primary:** Carloku Purple fill (#5b3e96 light / #6a48c8 dark), white text, purple-cast shadow. Hover deepens the fill (#4c3380 / #7d5cdb). Inside the band it inverts: band-ink fill, band-bg text, no shadow, hover to pure white.
+- **Quiet:** transparent with a strong-hairline border and ink text; hover turns border and text accent-purple over an accent-soft wash.
+- **Active:** `translateY(1px)` press. **Focus:** 2px accent outline, 3px offset (global rule for all links and buttons).
+- **Header CTA:** a smaller quiet-button variant (0.42rem × 0.95rem, 0.55rem radius) in accent color.
+
+### Chips (inline code)
+- **Style:** Geist Mono at 0.88em on an accent-soft wash, accent-ink text, 0.3em radius, 0.12em × 0.4em padding. This is how the three commands appear in prose.
+
+### Cards / Containers
+There are no boxed cards. "Cards" are borderless grid cells (steps, features) separated by hairline rules between siblings (`li + li { border-top }`) and by whitespace. Framed containers exist only for dark media (see Terminal).
+
+### Navigation
+- Sticky translucent header (82% bg over 12px blur), 4rem tall; brand at weight 640 with the SVG mark; links in muted at 0.95rem/weight 480, hover to ink (color transition only, 120ms). Scroll past 8px adds the bottom hairline. Below 44rem the nav wraps to a second full-width row.
+
+### Terminal (signature component)
+Terminal Black frame, 0.85rem radius, hairline `rgba(255,255,255,0.08)` border, frame shadow. Chrome bar (#1b1626) holds three 0.68rem dots drawn in title-color at 28% opacity plus a mono-ish title (#8d84a6, 0.78rem). Body is Geist Mono 0.86rem at 1.75 line-height, tabular numerals, min-height 17.5rem. Line grammar: commands in terminal-ink with a `$` prompt in lifted purple (weight 560, injected via `::before`), output in terminal-out, comments in title-color, success in OK green, and a blinking lifted-purple caret (1.1s steps). On load the session types itself once (14ms/char, staged line delays, vanilla JS) — skipped entirely under reduced motion. The same frame + chrome bar wraps the dashboard screenshot (`.dash-frame`), which swaps light/dark captures via `<picture>` while the frame stays dark.
+
+### Feature list items
+Two-column grid rows (icon auto / text 1fr, 1.15rem gap), 1.35rem vertical padding, hairline rules between rows. Icons are inline 24px-viewBox stroke SVGs (stroke-width 2, round caps/joins — vendored Lucide-style, never icon fonts or CDNs) at 1.25rem inside 2.4rem accent-soft tiles.
+
+### Guide article (get-started)
+Hairline-separated sections at 2.4rem padding; `pre` blocks in the terminal's clothes (Terminal Black, terminal ink, 0.7rem radius); asides as `.note` — a single left hairline-strong rule, faint text, no background.
+
+## Do's and Don'ts
+
+### Do:
+- **Do** keep both color schemes complete: every new token needs a light value on `:root` and a dark value in the `prefers-color-scheme: dark` block, plus a `theme-color` meta if it changes page chrome.
+- **Do** draw structure with hairlines and tint shifts (`--line`, `--bg-tint`), reserving shadows for framed dark media and the primary button.
+- **Do** put anything terminal- or console-shaped on the fixed `--term-*` palette in both schemes.
+- **Do** use tuned variable weights (480/560/580/620/650/680) and tighter tracking as type grows.
+- **Do** keep motion at 120–160ms ease-out, and gate *all* animation and smooth scroll behind `prefers-reduced-motion` (the global kill switch already exists — don't bypass it).
+- **Do** stay dependency-free: no build step, no external requests, self-hosted fonts only, inline SVG icons, vanilla JS.
+
+### Don't:
+- **Don't** introduce a second accent hue on page surfaces; the terminal's OK green never leaves the terminal.
+- **Don't** use pure #000 or #fff for ink/dark backgrounds — near-blacks are purple-cast (#1d1927, #0e0c13, #120f1a). (The one white exception: text *on* purple button fills.)
+- **Don't** box content in bordered cards; siblings separate with single hairline rules or whitespace.
+- **Don't** add a second full-bleed purple band to a page — one per page is the ceiling.
+- **Don't** use hard-offset shadows, borders heavier than 1px, or icon fonts/CDN icons.
+- **Don't** let dark-scheme buttons use the lifted accent (#b394f2) as a fill; buttons keep their own deeper fills (#6a48c8/#7d5cdb) so white text stays legible.
diff --git a/fonts/Geist-Variable.woff2 b/fonts/Geist-Variable.woff2
new file mode 100644
index 0000000..5c5999d
Binary files /dev/null and b/fonts/Geist-Variable.woff2 differ
diff --git a/fonts/GeistMono-Variable.woff2 b/fonts/GeistMono-Variable.woff2
new file mode 100644
index 0000000..efaf95d
Binary files /dev/null and b/fonts/GeistMono-Variable.woff2 differ
diff --git a/fonts/LICENSE-OFL.txt b/fonts/LICENSE-OFL.txt
new file mode 100644
index 0000000..8d003fe
--- /dev/null
+++ b/fonts/LICENSE-OFL.txt
@@ -0,0 +1,92 @@
+Copyright (c) 2023 Vercel, in collaboration with basement.studio
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION AND CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/get-started/index.html b/get-started/index.html
index d3e22ef..441ae33 100644
--- a/get-started/index.html
+++ b/get-started/index.html
@@ -10,19 +10,35 @@
<meta property="og:type" content="website">
<meta property="og:url" content="https://carloku.com/get-started/">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
-<meta name="theme-color" content="#16141c" media="(prefers-color-scheme: dark)">
+<meta name="theme-color" content="#0e0c13" media="(prefers-color-scheme: dark)">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
+<link rel="preload" href="/fonts/Geist-Variable.woff2" as="font" type="font/woff2" crossorigin>
+<link rel="preload" href="/fonts/GeistMono-Variable.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/site.css">
</head>
<body>
-<header class="hero">
- <p class="eyebrow"><a href="/">🤖 CARLOKU</a></p>
- <h1>Get started<span class="dot">.</span></h1>
- <p class="lede">This is the head half; the heart half is that you'll be live
- before it feels like work. Four steps, one small tool.</p>
+<a class="skip" href="#main">Skip to content</a>
+<header class="site-head">
+ <div class="wrap">
+ <a class="brand" href="/"><svg class="mark" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="14" fill="#5b3e96"/><circle cx="30" cy="32" r="17" fill="none" stroke="#ffffff" stroke-width="8" stroke-linecap="round" stroke-dasharray="88.8 18" stroke-dashoffset="-9"/><circle cx="51" cy="44" r="5" fill="#c9b2f7"/></svg>Carloku</a>
+ <nav aria-label="Site">
+ <a href="/#workflow">How it works</a>
+ <a href="/#open">Open source</a>
+ <a href="/get-started/">Get started</a>
+ </nav>
+ <a class="head-cta" href="https://dashboard.carloku.com">Dashboard</a>
+ </div>
</header>
-<main>
- <article>
+<main id="main">
+ <div class="page-hero">
+ <div class="wrap narrow">
+ <h1>Get started<span class="dot">.</span></h1>
+ <p class="lede">This is the head half; the heart half is that you'll be live
+ before it feels like work. Four steps, one small tool.</p>
+ </div>
+ </div>
+ <div class="wrap narrow">
+ <article class="guide">
<section>
<h2>1 · Install the CLI</h2>
<pre><code>brew install carlosframework/tap/carlos</code></pre>
@@ -53,7 +69,15 @@ carlos ship -app hello -kind static -version v1 ./public
<pre><code>carlos promote -app hello v1 stable</code></pre>
<p>Promotion is the "go" button: the release you just looked at starts serving
for real. To roll back, promote the previous version: it's the same command
- pointed at an older name.</p>
+ pointed at an older name. One guard-rail: <code>stable</code> is protected by
+ your passkey, so the CLI hands you to the console to approve it — earlier
+ channels promote straight from the terminal.</p>
+ <p>Once you're comfortable, the two steps collapse into one motion:</p>
+ <pre><code>carlos deploy -app hello ./hello-linux-arm64</code></pre>
+ <p>It ships, promotes, and waits until your URL serves the new build —
+ when it prints <code>live</code>, it is. The session on the
+ <a href="/">homepage</a> is a real capture of exactly that, and the app it
+ deployed is <a href="https://hello.bdf.oncarlos.com">really running</a>.</p>
<p class="note">Carloku is young and moving quickly. If anything on this page
surprises you, the <a href="https://dashboard.carloku.com">dashboard</a> is the
source of truth, and the platform underneath is open source.</p>
@@ -76,12 +100,16 @@ carlos ship -app hello -kind static -version v1 ./public
<a href="https://carlosframework.com">carlosframework.com</a>.</p>
</section>
</article>
+ </div>
</main>
<footer>
- <p>🤖 Written by an LLM (Claude), on the ideas, instruction, and editing of humans.
- AI-written text here is always marked and always disclosed; see
- <a href="https://11factor.org/#x">factor X</a>.</p>
- <p><a href="/">← carloku.com</a> · <a href="https://github.com/carlosframework/carloku">Source on GitHub</a>.</p>
+ <div class="wrap">
+ <p>🤖 Written by an LLM (Claude), on the ideas, instruction, and editing of humans.
+ AI-written text here is always marked and always disclosed; see
+ <a href="https://11factor.org/#x">factor X</a>.</p>
+ <p><a href="/">← carloku.com</a> · <a href="https://github.com/carlosframework/carloku">Source on GitHub</a>.</p>
+ </div>
</footer>
+<script src="/site.js" defer></script>
</body>
</html>
diff --git a/img/dashboard-dark.png b/img/dashboard-dark.png
new file mode 100644
index 0000000..4a83bb3
Binary files /dev/null and b/img/dashboard-dark.png differ
diff --git a/img/dashboard-light.png b/img/dashboard-light.png
new file mode 100644
index 0000000..eb541e7
Binary files /dev/null and b/img/dashboard-light.png differ
diff --git a/index.html b/index.html
index a270228..f830fe1 100644
--- a/index.html
+++ b/index.html
@@ -3,65 +3,194 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
-<title>Carloku — the hosted CARLOS platform</title>
-<meta name="description" content="🤖 Hosting small enough to hold in your head. Ship with one small command, put it live with another, and stop carrying the rest. Carloku is the hosted home of the CARLOS platform.">
+<title>Carloku — the uncompromising app platform</title>
+<meta name="description" content="🤖 Carloku is an app hosting platform that accepts no tradeoffs. Deploy in seconds. Scale to billions. Simple commands, massive power. Cloud, hybrid, and on-prem: all first-class. Welcome to the new standard. Get ready to ship.">
<meta property="og:title" content="Carloku">
-<meta property="og:description" content="Hosting small enough to hold in your head. Ship with one small command, put it live with another, and stop carrying the rest.">
+<meta property="og:description" content="Carloku is an app hosting platform that accepts no tradeoffs. Deploy in seconds. Scale to billions. Simple commands, massive power. Cloud, hybrid, and on-prem: all first-class. Welcome to the new standard. Get ready to ship.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://carloku.com/">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
-<meta name="theme-color" content="#16141c" media="(prefers-color-scheme: dark)">
+<meta name="theme-color" content="#0e0c13" media="(prefers-color-scheme: dark)">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
+<link rel="preload" href="/fonts/Geist-Variable.woff2" as="font" type="font/woff2" crossorigin>
+<link rel="preload" href="/fonts/GeistMono-Variable.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/site.css">
</head>
<body>
-<header class="hero">
- <p class="eyebrow">🤖 CARLOKU</p>
- <h1>Ship it. Promote it. Sleep<span class="dot">.</span></h1>
- <p class="lede">Carloku is the hosted CARLOS platform: hosting small enough to
- hold in your head. You ship, you look at it, you say go. The rest — certificates,
- passwords, servers idling through the night — stops being your job.</p>
- <div class="cta">
- <a class="primary" href="/get-started/">Get started</a>
- <a class="secondary" href="https://dashboard.carloku.com">Open the dashboard</a>
+<!--
+THESIS: The category-standard app-platform page, played straight at Heroku/Fly/Render/Railway
+craft — chosen by the user as the standing exit over the assigned direction. It refuses
+nothing structurally; it must out-finish, not out-weird.
+OWN-WORLD: Near-black-with-purple dark scheme / white-with-purple light scheme, one accent
+(Carloku purple), Geist + Geist Mono, hairline borders, one dark terminal that stays dark in
+both schemes, one deep-purple full-bleed band.
+STORY: A developer sees the three-command workflow proven in a real terminal, believes
+hosting can be held in one head, and clicks Get started.
+FIRST VIEWPORT: Sticky nav; left, "Ship it. Promote it. Sleep." + lede + Get started primary;
+right, a terminal typing the real ship→promote→rollback session.
+FORM: Canon standing exit, user-chosen over grounded candidate 5; seed key 5f64f4cf.
+FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review,
+the verdict, and DESIGN.md.
+-->
+<a class="skip" href="#main">Skip to content</a>
+<header class="site-head">
+ <div class="wrap">
+ <a class="brand" href="/"><svg class="mark" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="14" fill="#5b3e96"/><circle cx="30" cy="32" r="17" fill="none" stroke="#ffffff" stroke-width="8" stroke-linecap="round" stroke-dasharray="88.8 18" stroke-dashoffset="-9"/><circle cx="51" cy="44" r="5" fill="#c9b2f7"/></svg>Carloku</a>
+ <nav aria-label="Site">
+ <a href="#workflow">How it works</a>
+ <a href="#open">Open source</a>
+ <a href="/get-started/">Get started</a>
+ </nav>
+ <a class="head-cta" href="https://dashboard.carloku.com">Dashboard</a>
</div>
</header>
-<main>
- <section>
- <h2>The whole workflow</h2>
- <ol class="ship" role="list">
- <li><strong><code>carlos ship</code></strong>. Your app goes up, and you get a
- private link to the real, running thing. Look at it. Poke it. It's live, just
- not public yet.</li>
- <li><strong><code>carlos promote</code></strong>. The exact thing you just
- looked at becomes the public thing. Nothing is rebuilt behind your back; what
- you approved is what people get.</li>
- <li><strong>Changed your mind?</strong> Point at yesterday's version and promote
- that instead. Going back is the same small motion as going forward.</li>
- </ol>
+<main id="main">
+ <section class="hero">
+ <div class="wrap">
+ <div>
+ <h1>The uncompromising app platform<span class="dot">.</span></h1>
+ <p class="lede">Carloku is an app hosting platform that accepts no tradeoffs.
+ Deploy in seconds. Scale to billions. Simple commands, massive power. Cloud,
+ hybrid, and on-prem: all first-class. Welcome to the new standard. Get ready
+ to ship.</p>
+ <div class="cta">
+ <a class="btn btn-primary" href="/get-started/">Get started</a>
+ <a class="btn btn-quiet" href="https://dashboard.carloku.com">Open the dashboard</a>
+ </div>
+ <p class="hero-note">Open source underneath — hosted here, never locked here.</p>
+ </div>
+ <div>
+ <div class="term" role="img" aria-label="A real terminal session: carlos deploy ships the app, promotes it, and waits until the URL serves the new build; it ends with the app's live URL.">
+ <div class="term-bar" aria-hidden="true"><i></i><i></i><i></i><span>you@laptop — carlos</span></div>
+ <div class="term-body" aria-hidden="true">
+ <span class="line cmd" data-type>carlos deploy -app hello ./hello-linux-arm64</span>
+ <span class="line" data-out>using https://console.carloku.com as you@keymail.dev</span>
+ <span class="line" data-out>deploying https://hello.bdf.oncarlos.com</span>
+ <span class="line ok" data-out>shipped hello a30b802</span>
+ <span class="line ok" data-out>promoted hello a30b802 -&gt; edge</span>
+ <span class="line ok" data-out>live https://hello.bdf.oncarlos.com</span>
+ <span class="line"><span class="term-caret"></span></span>
+ </div>
+ </div>
+ <p class="hero-note">That session is real, and so is the app —
+ <a href="https://hello.bdf.oncarlos.com">open it</a> ·
+ <a href="https://github.com/carlosframework/hello">source</a>.</p>
+ </div>
+ </div>
</section>
- <section>
- <h2>What you stop thinking about</h2>
- <ul class="features" role="list">
- <li><strong>Certificates</strong> <span>point your domain at Carloku and the
- padlock appears. Renewals happen without you.</span></li>
- <li><strong>Passwords</strong> <span>sign in with a passkey, on the dashboard
- and in the terminal. There's nothing to invent, reuse, or leak.</span></li>
- <li><strong>Idle servers</strong> <span>apps doze when nobody's around and wake
- the instant someone shows up. A quiet app is just quiet.</span></li>
- <li><strong>What's really running</strong> <span>the platform under Carloku is
- open source. You can read every moving part — or run it yourself. Hosted here,
- never locked here.</span></li>
- </ul>
+ <section id="workflow">
+ <div class="wrap">
+ <h2>The whole workflow</h2>
+ <p class="section-lede">One command — or three small motions. No pipelines to
+ babysit, no containers to wrap your app in — you ship the thing itself.</p>
+ <ol class="steps">
+ <li>
+ <h3><code>carlos deploy</code></h3>
+ <p>One command: it ships your app, promotes it, and waits until the URL
+ serves the new build. When it prints <code>live</code>, it is — seconds,
+ not pipelines.</p>
+ </li>
+ <li>
+ <h3>Want to look first?</h3>
+ <p>Take it in two motions: <code>carlos ship</code> gives you a private
+ link to the real, running thing; <code>carlos promote</code> makes the
+ exact thing you approved the public thing. Nothing is rebuilt behind
+ your back.</p>
+ </li>
+ <li>
+ <h3>Changed your mind?</h3>
+ <p>Point at yesterday's version and promote that instead. Going back is
+ the same small motion as going forward.</p>
+ </li>
+ </ol>
+ </div>
+ </section>
+ <section class="tint">
+ <div class="wrap">
+ <h2>What you stop thinking about</h2>
+ <div class="quiet-grid">
+ <ul class="features">
+ <li>
+ <div class="icon" aria-hidden="true">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
+ </div>
+ <div>
+ <h3>Certificates</h3>
+ <p>Point your domain at Carloku and the padlock appears. Renewals happen
+ without you.</p>
+ </div>
+ </li>
+ <li>
+ <div class="icon" aria-hidden="true">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/></svg>
+ </div>
+ <div>
+ <h3>Passwords</h3>
+ <p>Sign in with a passkey, on the dashboard and in the terminal. There's
+ nothing to invent, reuse, or leak.</p>
+ </div>
+ </li>
+ <li>
+ <div class="icon" aria-hidden="true">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
+ </div>
+ <div>
+ <h3>Idle servers</h3>
+ <p>Apps doze when nobody's around and wake the instant someone shows up.
+ A quiet app is just quiet.</p>
+ </div>
+ </li>
+ </ul>
+ <figure class="dash">
+ <div class="dash-frame">
+ <div class="term-bar" aria-hidden="true"><i></i><i></i><i></i><span>dashboard.carloku.com</span></div>
+ <picture>
+ <source srcset="/img/dashboard-dark.png" media="(prefers-color-scheme: dark)">
+ <img src="/img/dashboard-light.png" width="736" height="336" alt="The Carloku dashboard: an account's apps listed with their hosts, running instances, status, and the version each one serves.">
+ </picture>
+ </div>
+ <figcaption>The dashboard, as it really looks — demo data.</figcaption>
+ </figure>
+ </div>
+ </div>
+ </section>
+ <section class="band" id="open">
+ <div class="wrap">
+ <h2>Hosted here, never locked here.</h2>
+ <p>The platform under Carloku is open source: CARLOS. You can read every moving
+ part — or run the whole thing yourself, on your own machines, and your apps
+ wouldn't know the difference.</p>
+ <p>And what you ship is your app as itself — a folder of files, a single binary —
+ not a container wrapped around it.</p>
+ <div class="cta">
+ <a class="btn btn-primary" href="https://carlosframework.com">Read the design</a>
+ <a class="btn btn-quiet" href="https://github.com/carlosframework">CARLOS on GitHub</a>
+ </div>
+ <p class="proof">Proof it works: this site is itself a CARLOS app, shipped and
+ promoted the same way you just read about. <a href="https://github.com/carlosframework/carloku">Here's its source.</a></p>
+ </div>
+ </section>
+ <section class="close-cta">
+ <div class="wrap">
+ <h2>Be live before it feels like work<span class="dot">.</span></h2>
+ <p>One small tool, four steps, and your app is on the internet.</p>
+ <div class="cta">
+ <a class="btn btn-primary" href="/get-started/">Get started</a>
+ <a class="btn btn-quiet" href="https://dashboard.carloku.com">Open the dashboard</a>
+ </div>
+ </div>
</section>
</main>
<footer>
- <p>🤖 Written by an LLM (Claude), on the ideas, instruction, and editing of humans.
- AI-written text here is always marked and always disclosed; see
- <a href="https://11factor.org/#x">factor X</a>.</p>
- <p>Carloku runs on <a href="https://carlosframework.com">CARLOS</a>: the framework
- is open, and this site is itself a CARLOS app, shipped with <code>carlos ship</code>.</p>
- <p><a href="https://github.com/carlosframework/carloku">Source for this site on GitHub</a>.</p>
+ <div class="wrap">
+ <p>🤖 Written by an LLM (Claude), on the ideas, instruction, and editing of humans.
+ AI-written text here is always marked and always disclosed; see
+ <a href="https://11factor.org/#x">factor X</a>.</p>
+ <p>Carloku runs on <a href="https://carlosframework.com">CARLOS</a>: the framework
+ is open, and this site is itself a CARLOS app, shipped with <code>carlos ship</code>.</p>
+ <p><a href="https://github.com/carlosframework/carloku">Source for this site on GitHub</a>.</p>
+ </div>
</footer>
+<script src="/site.js" defer></script>
</body>
</html>
diff --git a/site.css b/site.css
index 8bb4206..84d40c2 100644
--- a/site.css
+++ b/site.css
@@ -1,77 +1,380 @@
-/* 🤖 Carloku shared stylesheet — both pages, zero dependencies. */
+/* 🤖 Carloku shared stylesheet — both pages, zero dependencies, no build step.
+ Fonts are Geist / Geist Mono (SIL OFL, see /fonts/LICENSE-OFL.txt), self-hosted. */
+
+@font-face {
+ font-family: "Geist";
+ src: url("/fonts/Geist-Variable.woff2") format("woff2");
+ font-weight: 100 900;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Geist Mono";
+ src: url("/fonts/GeistMono-Variable.woff2") format("woff2");
+ font-weight: 100 900;
+ font-display: swap;
+}
+
:root {
--bg: #ffffff;
- --ink: #201d29;
- --muted: #6f6480;
+ --bg-tint: #f8f7fb;
+ --ink: #1d1927;
+ --muted: #5d5570;
+ --faint: #6e6880;
--accent: #5b3e96;
- --accent-strong: #4c3380;
- --accent-soft: rgba(91, 62, 150, 0.09);
- --rule: #eae7f0;
- --max: 42rem;
- --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
- --sans: ui-sans-serif, system-ui, -apple-system, sans-serif;
+ --accent-ink: #4c3380;
+ --accent-soft: rgba(91, 62, 150, 0.08);
+ --btn-bg: #5b3e96;
+ --btn-bg-hover: #4c3380;
+ --btn-ink: #ffffff;
+ --line: rgba(29, 25, 39, 0.1);
+ --line-strong: rgba(29, 25, 39, 0.16);
+ --band-bg: #221a35;
+ --band-ink: #f2eef9;
+ --band-muted: #b9aed6;
+ --band-line: rgba(242, 238, 249, 0.14);
+ --term-bg: #120f1a;
+ --term-chrome: #1b1626;
+ --term-line: rgba(255, 255, 255, 0.08);
+ --term-title: #8d84a6;
+ --term-ink: #ede9f7;
+ --term-out: #9d93b8;
+ --term-ok: #7ee2a8;
+ --term-prompt: #b394f2;
+ --shadow: 0 1px 2px rgba(20, 12, 40, 0.05), 0 12px 32px -12px rgba(20, 12, 40, 0.18);
+ --sans: "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
+ --mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ --wrap: 72rem;
+ color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root {
- --bg: #16141c;
- --ink: #f0eef6;
- --muted: #a99ebd;
+ --bg: #0e0c13;
+ --bg-tint: #14111c;
+ --ink: #f2eff8;
+ --muted: #a9a0c2;
+ --faint: #928aab;
--accent: #b394f2;
- --accent-strong: #c9b2f7;
- --accent-soft: rgba(179, 148, 242, 0.12);
- --rule: #2c2836;
+ --accent-ink: #c9b2f7;
+ --accent-soft: rgba(179, 148, 242, 0.1);
+ --btn-bg: #6a48c8;
+ --btn-bg-hover: #7d5cdb;
+ --btn-ink: #ffffff;
+ --line: rgba(242, 239, 248, 0.09);
+ --line-strong: rgba(242, 239, 248, 0.16);
+ --band-bg: #261b44;
+ --band-ink: #f2eef9;
+ --band-muted: #b3a8d2;
+ --band-line: rgba(242, 238, 249, 0.12);
+ --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 16px 40px -16px rgba(0, 0, 0, 0.55);
+ color-scheme: dark;
}
}
+
* { box-sizing: border-box; margin: 0; }
+html { scroll-behavior: smooth; }
body {
- background: var(--bg); color: var(--ink);
- font-family: var(--sans); line-height: 1.6;
- padding: 0 1.25rem;
-}
-main, footer { max-width: var(--max); margin: 0 auto; }
-header.hero { max-width: var(--max); margin: 0 auto; padding: 5rem 0 3rem; }
-.eyebrow { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); letter-spacing: 0.08em; }
-.eyebrow a { color: var(--muted); text-decoration: none; }
-.eyebrow a:hover { color: var(--accent); }
-h1 { font-size: clamp(2rem, 7vw, 2.6rem); line-height: 1.15; margin: 0.5rem 0 1rem; }
-h1 .dot { color: var(--accent); }
-.lede { font-size: 1.15rem; color: var(--muted); max-width: 36rem; }
-.cta { margin: 2rem 0 0; display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; }
-.cta a.primary {
- background: var(--accent); color: var(--bg); text-decoration: none;
- padding: 0.65rem 1.2rem; border-radius: 0.4rem; font-weight: 600;
-}
-.cta a.primary:hover { background: var(--accent-strong); }
-.cta a.secondary { color: var(--accent); text-decoration: none; padding: 0.65rem 0; font-weight: 600; }
-.cta a.secondary:hover, footer a:hover { text-decoration: underline; }
-a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0.2em; }
+ background: var(--bg);
+ color: var(--ink);
+ font-family: var(--sans);
+ font-size: 1rem;
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
::selection { background: var(--accent); color: var(--bg); }
-section { border-top: 1px solid var(--rule); padding: 2.5rem 0; }
-h2 { font-size: 1.3rem; margin-bottom: 1.25rem; }
-ol.ship { list-style: none; counter-reset: step; display: grid; gap: 1.25rem; }
-ol.ship li { counter-increment: step; padding-left: 2.4rem; position: relative; }
-ol.ship li::before {
- content: counter(step); position: absolute; left: 0; top: 0.1rem;
- width: 1.6rem; height: 1.6rem; border-radius: 50%;
- background: var(--accent-soft); color: var(--accent);
- font-family: var(--mono); font-size: 0.85rem;
+a:focus-visible, button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 3px;
+ border-radius: 0.25em;
+}
+html { scrollbar-color: var(--line-strong) transparent; }
+
+.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 1.5rem; }
+.wrap.narrow { max-width: 50rem; }
+
+.skip {
+ position: absolute; left: 1rem; top: -3rem;
+ background: var(--btn-bg); color: var(--btn-ink);
+ padding: 0.5rem 1rem; border-radius: 0.5rem; text-decoration: none;
+ transition: top 120ms ease-out; z-index: 20;
+}
+.skip:focus-visible { top: 1rem; }
+
+/* ---- Header ---- */
+.site-head {
+ position: sticky; top: 0; z-index: 10;
+ background: color-mix(in srgb, var(--bg) 82%, transparent);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border-bottom: 1px solid transparent;
+ transition: border-color 160ms ease-out;
+}
+.site-head.scrolled { border-bottom-color: var(--line); }
+.site-head .wrap {
+ display: flex; align-items: center; gap: 2rem;
+ height: 4rem;
+}
+.brand {
+ font-weight: 640; font-size: 1.05rem; letter-spacing: -0.01em;
+ color: var(--ink); text-decoration: none;
+ display: flex; align-items: center; gap: 0.5rem;
+}
+.brand svg.mark { width: 1.4rem; height: 1.4rem; display: block; }
+.site-head nav {
+ display: flex; gap: 1.75rem; margin-left: auto; align-items: center;
+}
+.site-head nav a {
+ color: var(--muted); text-decoration: none; font-size: 0.95rem; font-weight: 480;
+ white-space: nowrap;
+ transition: color 120ms ease-out;
+}
+.site-head nav a:hover { color: var(--ink); }
+.site-head .head-cta {
+ color: var(--accent); font-weight: 560; font-size: 0.95rem;
+ text-decoration: none; white-space: nowrap;
+ border: 1px solid var(--line-strong); border-radius: 0.55rem;
+ padding: 0.42rem 0.95rem;
+ transition: border-color 120ms ease-out, background 120ms ease-out, color 120ms ease-out;
+}
+.site-head .head-cta:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
+@media (max-width: 44rem) {
+ .site-head .wrap { flex-wrap: wrap; height: auto; padding-top: 0.7rem; padding-bottom: 0.7rem; row-gap: 0.35rem; }
+ .site-head nav { flex-basis: 100%; order: 3; gap: 1.25rem; margin-left: 0; }
+ .site-head nav a { font-size: 0.9rem; }
+ .site-head .head-cta { margin-left: auto; padding: 0.3rem 0.8rem; font-size: 0.9rem; }
+}
+
+/* ---- Hero ---- */
+.hero { padding: 5.5rem 0 5rem; }
+.nobr { white-space: nowrap; }
+.hero .wrap {
+ display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
+ gap: 4.5rem; align-items: center;
+}
+h1 {
+ font-size: clamp(2.6rem, 5.4vw, 4.1rem);
+ line-height: 1.04; letter-spacing: -0.032em; font-weight: 680;
+ text-wrap: balance;
+ margin-bottom: 1.35rem;
+}
+h1 .dot { color: var(--accent); }
+.lede {
+ font-size: 1.17rem; line-height: 1.65; color: var(--muted);
+ max-width: 34rem; text-wrap: pretty;
+}
+.cta { margin-top: 2.25rem; display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; }
+.btn {
+ display: inline-flex; align-items: center; gap: 0.5rem;
+ text-decoration: none; font-weight: 580; font-size: 1rem;
+ border-radius: 0.65rem; padding: 0.72rem 1.4rem;
+ transition: background 130ms ease-out, border-color 130ms ease-out, transform 130ms ease-out;
+}
+.btn:active { transform: translateY(1px); }
+.btn-primary {
+ background: var(--btn-bg); color: var(--btn-ink);
+ box-shadow: 0 1px 2px rgba(20, 12, 40, 0.2), 0 6px 16px -6px color-mix(in srgb, var(--btn-bg) 55%, transparent);
+}
+.btn-primary:hover { background: var(--btn-bg-hover); }
+.btn-quiet {
+ color: var(--ink); border: 1px solid var(--line-strong);
+}
+.btn-quiet:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
+.hero-note {
+ margin-top: 1.4rem; font-size: 0.92rem; color: var(--faint);
+}
+.hero-note a { color: var(--faint); }
+.hero-note a:hover { color: var(--accent); }
+
+/* ---- Terminal ---- */
+.term {
+ background: var(--term-bg);
+ border: 1px solid var(--term-line);
+ border-radius: 0.85rem;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ font-family: var(--mono);
+ font-size: 0.86rem;
+ font-variant-numeric: tabular-nums;
+ line-height: 1.75;
+}
+.term-bar {
+ display: flex; align-items: center; gap: 0.45rem;
+ background: var(--term-chrome);
+ border-bottom: 1px solid var(--term-line);
+ padding: 0.6rem 0.9rem;
+}
+.term-bar i {
+ width: 0.68rem; height: 0.68rem; border-radius: 50%;
+ background: currentColor; opacity: 0.28; color: var(--term-title);
+}
+.term-bar span {
+ margin-left: 0.4rem; color: var(--term-title);
+ font-size: 0.78rem; letter-spacing: 0.01em;
+}
+.term-body { padding: 1.05rem 1.15rem 1.2rem; min-height: 17.5rem; }
+.term-body .line { display: block; white-space: pre-wrap; word-break: break-word; color: var(--term-out); }
+.term-body .cmd { color: var(--term-ink); }
+.term-body .cmd::before { content: "$ "; color: var(--term-prompt); font-weight: 560; }
+.term-body .cmt { color: var(--term-title); }
+.term-body .cmt::before { content: "$ "; color: var(--term-prompt); font-weight: 560; }
+.term-body .ok { color: var(--term-ok); }
+.term-caret {
+ display: inline-block; width: 0.55em; height: 1.05em;
+ background: var(--term-prompt); vertical-align: text-bottom;
+ animation: caret 1.1s steps(1) infinite;
+}
+@keyframes caret { 50% { opacity: 0; } }
+
+/* ---- Sections ---- */
+section { padding: 5.5rem 0; }
+section.tint { background: var(--bg-tint); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
+h2 {
+ font-size: clamp(1.7rem, 3vw, 2.3rem);
+ letter-spacing: -0.025em; line-height: 1.15; font-weight: 650;
+ text-wrap: balance;
+ margin-bottom: 0.9rem;
+}
+.section-lede { color: var(--muted); font-size: 1.08rem; max-width: 38rem; text-wrap: pretty; }
+
+.steps {
+ list-style: none; padding: 0;
+ margin-top: 3rem;
+ display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 2.75rem;
+}
+.steps li { position: relative; padding-top: 1.15rem; }
+.steps li::before {
+ content: counter(step); counter-increment: step;
+ position: absolute; top: -0.9rem; left: 0;
+ font-family: var(--mono); font-weight: 560; font-size: 0.82rem;
+ color: var(--accent);
+ background: var(--accent-soft);
+ width: 1.85rem; height: 1.85rem; border-radius: 50%;
+ display: grid; place-items: center;
+}
+.steps { counter-reset: step; }
+.steps h3 {
+ font-size: 1.12rem; font-weight: 620; letter-spacing: -0.01em;
+ margin: 0.9rem 0 0.45rem;
+}
+.steps p { color: var(--muted); font-size: 0.98rem; }
+.steps code {
+ font-family: var(--mono); font-size: 0.88em;
+ background: var(--accent-soft); color: var(--accent-ink);
+ padding: 0.12em 0.4em; border-radius: 0.3em;
+}
+
+.quiet-grid {
+ margin-top: 2.75rem;
+ display: grid; grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
+ gap: 4rem; align-items: center;
+}
+.features { list-style: none; padding: 0; }
+.features li {
+ display: grid; grid-template-columns: auto 1fr;
+ gap: 1.15rem; align-items: start;
+ padding: 1.35rem 0;
+}
+.features li + li { border-top: 1px solid var(--line); }
+.features .icon {
+ width: 2.4rem; height: 2.4rem; border-radius: 0.6rem;
display: grid; place-items: center;
+ background: var(--accent-soft); color: var(--accent);
+ margin-top: 0.15rem;
+}
+.features .icon svg { width: 1.25rem; height: 1.25rem; }
+.features h3 { font-size: 1.08rem; font-weight: 620; letter-spacing: -0.01em; margin-bottom: 0.3rem; }
+.features p { color: var(--muted); font-size: 0.97rem; }
+.dash-frame {
+ background: var(--term-bg);
+ border: 1px solid var(--term-line);
+ border-radius: 0.85rem;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+}
+.dash-frame img { display: block; width: 100%; height: auto; }
+.dash figcaption {
+ margin-top: 0.8rem; font-size: 0.88rem; color: var(--faint);
+}
+
+/* ---- Open-source band ---- */
+.band { background: var(--band-bg); color: var(--band-ink); padding: 6rem 0; }
+.band h2 { color: var(--band-ink); }
+.band p { color: var(--band-muted); font-size: 1.08rem; max-width: 40rem; text-wrap: pretty; }
+.band p + p { margin-top: 1rem; }
+.band .cta { margin-top: 2.2rem; }
+.band .btn-primary { background: var(--band-ink); color: var(--band-bg); box-shadow: none; }
+.band .btn-primary:hover { background: #ffffff; }
+.band .btn-quiet { color: var(--band-ink); border-color: var(--band-line); }
+.band .btn-quiet:hover { border-color: var(--band-ink); color: var(--band-ink); background: rgba(255, 255, 255, 0.06); }
+.band .proof {
+ margin-top: 2.4rem; padding-top: 1.6rem; border-top: 1px solid var(--band-line);
+ font-size: 0.95rem; color: var(--band-muted);
+}
+.band .proof a { color: var(--band-ink); }
+.band ::selection { background: var(--band-ink); color: var(--band-bg); }
+
+/* ---- Closing CTA ---- */
+.close-cta { padding: 7rem 0; text-align: center; }
+.close-cta h2 { font-size: clamp(2rem, 4vw, 3rem); }
+.close-cta p { color: var(--muted); font-size: 1.1rem; }
+.close-cta .cta { justify-content: center; }
+
+/* ---- Footer ---- */
+footer {
+ border-top: 1px solid var(--line);
+ padding: 3rem 0 3.5rem;
+ font-size: 0.92rem; color: var(--muted);
}
-code { font-family: var(--mono); font-size: 0.9em; background: var(--accent-soft); padding: 0.1em 0.35em; border-radius: 0.25em; }
-pre {
- background: var(--accent-soft); padding: 1rem 1.25rem; border-radius: 0.5rem;
- overflow-x: auto; font-family: var(--mono); font-size: 0.9rem; line-height: 1.5;
- margin: 1rem 0;
-}
-pre code { background: none; padding: 0; font-size: inherit; }
-ul.features { list-style: none; display: grid; gap: 0.9rem; }
-ul.features li strong { display: block; }
-ul.features li span { color: var(--muted); }
-article p, article ul { margin-bottom: 1rem; max-width: var(--max); }
-article h2 { margin-top: 0; }
-article a { color: var(--accent); text-decoration: none; }
-article a:hover { text-decoration: underline; }
-.note { border-left: 1px solid var(--rule); padding-left: 1rem; color: var(--muted); font-size: 0.95rem; }
-footer { border-top: 1px solid var(--rule); padding: 2rem 0 3rem; font-size: 0.9rem; color: var(--muted); }
-footer p { margin-bottom: 0.6rem; }
+footer p { max-width: 44rem; }
+footer p + p { margin-top: 0.7rem; }
footer a { color: var(--accent); text-decoration: none; }
+footer a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
+footer code { font-family: var(--mono); font-size: 0.9em; }
+
+/* ---- Get-started article ---- */
+.page-hero { padding: 4.5rem 0 1rem; }
+.page-hero .lede { margin-top: 0; }
+article.guide { padding: 1.5rem 0 4rem; }
+article.guide section { padding: 2.4rem 0; border-top: 1px solid var(--line); }
+article.guide section:first-child { border-top: 0; }
+article.guide h2 { font-size: 1.45rem; margin-bottom: 1rem; }
+article.guide p, article.guide ul { color: var(--muted); max-width: 42rem; margin-bottom: 1rem; }
+article.guide ul { padding-left: 1.2rem; }
+article.guide li + li { margin-top: 0.5rem; }
+article.guide a { color: var(--accent); text-decoration: none; }
+article.guide a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
+article.guide pre {
+ background: var(--term-bg); color: var(--term-ink);
+ border: 1px solid var(--term-line);
+ padding: 1rem 1.2rem; border-radius: 0.7rem;
+ overflow-x: auto;
+ font-family: var(--mono); font-size: 0.88rem; line-height: 1.7;
+ margin: 1.1rem 0 1.3rem;
+}
+article.guide pre code { background: none; padding: 0; color: inherit; font-size: inherit; }
+article.guide code {
+ font-family: var(--mono); font-size: 0.88em;
+ background: var(--accent-soft); color: var(--accent-ink);
+ padding: 0.12em 0.4em; border-radius: 0.3em;
+}
+article.guide .note {
+ border-left: 1px solid var(--line-strong);
+ padding-left: 1.1rem; color: var(--faint); font-size: 0.95rem;
+}
+
+/* ---- Responsive ---- */
+@media (max-width: 62rem) {
+ .hero { padding: 3.5rem 0 4.5rem; }
+ .hero .wrap { grid-template-columns: 1fr; gap: 3rem; }
+ .lede { max-width: 38rem; }
+ .steps { grid-template-columns: 1fr; gap: 2.25rem; }
+ .quiet-grid { grid-template-columns: 1fr; gap: 2.5rem; }
+ section, .band { padding: 4rem 0; }
+ .close-cta { padding: 5rem 0; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+ *, *::before, *::after { animation: none !important; transition: none !important; }
+}
diff --git a/site.js b/site.js
new file mode 100644
index 0000000..b4694ea
--- /dev/null
+++ b/site.js
@@ -0,0 +1,56 @@
+// 🤖 Carloku — hand-written, dependency-free. Two jobs only:
+// 1. the hero terminal types its session once (skipped under reduced motion),
+// 2. the sticky header grows its hairline once the page scrolls.
+(function () {
+ "use strict";
+
+ var head = document.querySelector(".site-head");
+ if (head) {
+ var onScroll = function () {
+ head.classList.toggle("scrolled", window.scrollY > 8);
+ };
+ window.addEventListener("scroll", onScroll, { passive: true });
+ onScroll();
+ }
+
+ var body = document.querySelector(".term-body");
+ if (!body) return;
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
+
+ var lines = Array.prototype.slice.call(body.querySelectorAll(".line"));
+ var caretLine = lines.pop(); // trailing caret stays put until the end
+ var texts = lines.map(function (el) { return el.textContent; });
+ lines.forEach(function (el) {
+ el.textContent = "";
+ el.style.display = "none";
+ });
+ if (caretLine) caretLine.style.display = "none";
+
+ var li = 0;
+ function nextLine() {
+ if (li >= lines.length) {
+ if (caretLine) caretLine.style.display = "";
+ return;
+ }
+ var el = lines[li];
+ var text = texts[li];
+ li++;
+ el.style.display = "";
+ if (el.hasAttribute("data-type")) {
+ var ci = 0;
+ (function typeChar() {
+ if (ci <= text.length) {
+ el.textContent = text.slice(0, ci);
+ ci++;
+ setTimeout(typeChar, 14);
+ } else {
+ setTimeout(nextLine, 160);
+ }
+ })();
+ } else {
+ el.textContent = text;
+ setTimeout(nextLine, el.classList.contains("ok") ? 260 : 120);
+ }
+ }
+ setTimeout(nextLine, 450);
+})();