1273 lines
42 KiB
HTML
1273 lines
42 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Latch — Feature flags that don't get in the way</title>
|
||
<meta name="description" content="Latch is a feature flag system for product teams who ship every day. Toggle, target, and roll back in under 50ms — without the YAML.">
|
||
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
|
||
<style>
|
||
/* ============================================================
|
||
LATCH — feature flags for product teams
|
||
Style: Refined Minimal, dark, Linear-style
|
||
Palette: dark surface + warm-tinted white ink + green accent
|
||
============================================================ */
|
||
|
||
:root {
|
||
--surface: #0A0A0B;
|
||
--surface-1: #131316;
|
||
--surface-2: #1C1C20;
|
||
--surface-3: #26262C;
|
||
--surface-sunken: #050507;
|
||
|
||
--ink: #F4F4F5;
|
||
--ink-muted: #A1A1AA;
|
||
--ink-subtle: #71717A;
|
||
|
||
--hairline: #1F1F23;
|
||
--hairline-strong: #2E2E33;
|
||
|
||
--accent: #6EE7B7; /* Tailwind emerald-300 — refined mint */
|
||
--accent-strong: #34D399;
|
||
--accent-soft: rgba(110, 231, 183, 0.12);
|
||
|
||
--good: #34D399;
|
||
--warn: #FBBF24;
|
||
--bad: #F87171;
|
||
--info: #60A5FA;
|
||
|
||
--font-text: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||
|
||
--text-xs: 0.75rem;
|
||
--text-sm: 0.8125rem;
|
||
--text-base: 0.9375rem;
|
||
--text-md: 1.0625rem;
|
||
--text-lg: 1.25rem;
|
||
--text-xl: 1.5rem;
|
||
--text-2xl: 1.875rem;
|
||
--text-3xl: 2.25rem;
|
||
--text-4xl: 3rem;
|
||
--text-5xl: 3.75rem;
|
||
|
||
--lead-tight: 1.05;
|
||
--lead-snug: 1.2;
|
||
--lead-normal: 1.5;
|
||
--lead-loose: 1.65;
|
||
|
||
--track-tightest: -0.035em;
|
||
--track-tight: -0.02em;
|
||
--track-wide: 0.04em;
|
||
--track-widest: 0.14em;
|
||
|
||
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
|
||
--sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
|
||
--sp-9: 96px; --sp-10: 128px;
|
||
|
||
--r-sm: 4px;
|
||
--r-md: 6px;
|
||
--r-lg: 8px;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
|
||
|
||
body {
|
||
margin: 0;
|
||
font-family: var(--font-text);
|
||
font-size: var(--text-base);
|
||
line-height: var(--lead-normal);
|
||
color: var(--ink);
|
||
background: var(--surface);
|
||
font-feature-settings: 'kern' 1, 'cv11' 1, 'ss01' 1;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
text-rendering: optimizeLegibility;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
img, svg { display: block; max-width: 100%; }
|
||
a {
|
||
color: inherit;
|
||
text-decoration: none;
|
||
border-bottom: 1px solid var(--hairline-strong);
|
||
padding-bottom: 1px;
|
||
transition: border-color 140ms ease, color 140ms ease;
|
||
}
|
||
a:hover { border-color: var(--accent); color: var(--accent); }
|
||
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
|
||
|
||
button, input { font: inherit; color: inherit; }
|
||
button { background: none; border: 0; padding: 0; cursor: pointer; }
|
||
input { background: transparent; border: 0; }
|
||
|
||
::selection { background: var(--accent); color: var(--surface); }
|
||
|
||
.mono { font-family: var(--font-mono); }
|
||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
||
|
||
/* ----- Nav ------------------------------------------------------- */
|
||
|
||
.nav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
background: color-mix(in oklab, var(--surface) 88%, transparent);
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border-bottom: 1px solid var(--hairline);
|
||
}
|
||
.nav__inner {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 0 clamp(20px, 4vw, 48px);
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.nav__brand {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--sp-2);
|
||
font-weight: 600;
|
||
font-size: var(--text-md);
|
||
letter-spacing: var(--track-tight);
|
||
color: var(--ink);
|
||
border-bottom: 0;
|
||
}
|
||
.nav__brand-mark { color: var(--accent); }
|
||
.nav__links {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--sp-6);
|
||
font-size: var(--text-sm);
|
||
}
|
||
.nav__links a {
|
||
color: var(--ink-muted);
|
||
border-bottom: 0;
|
||
}
|
||
.nav__links a:hover { color: var(--ink); }
|
||
.nav__cta {
|
||
padding: 7px 13px;
|
||
border: 1px solid var(--hairline-strong);
|
||
border-radius: var(--r-sm);
|
||
color: var(--ink) !important;
|
||
transition: background 120ms ease, border-color 120ms ease;
|
||
}
|
||
.nav__cta:hover { background: var(--surface-1); border-color: var(--accent); }
|
||
|
||
@media (max-width: 720px) {
|
||
.nav__links a:not(.nav__cta) { display: none; }
|
||
}
|
||
|
||
/* ----- Hero ------------------------------------------------------ */
|
||
|
||
.hero {
|
||
position: relative;
|
||
padding: clamp(60px, 10vw, 120px) clamp(20px, 4vw, 48px);
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
overflow: hidden;
|
||
}
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(
|
||
ellipse 70% 50% at 50% 0%,
|
||
var(--accent-soft) 0%,
|
||
transparent 60%
|
||
);
|
||
pointer-events: none;
|
||
}
|
||
.hero__inner {
|
||
position: relative;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
|
||
gap: clamp(32px, 6vw, 64px);
|
||
align-items: center;
|
||
}
|
||
|
||
.hero__kicker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--sp-2);
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
letter-spacing: var(--track-widest);
|
||
text-transform: uppercase;
|
||
color: var(--ink-muted);
|
||
margin-bottom: var(--sp-6);
|
||
}
|
||
.hero__kicker-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--accent);
|
||
}
|
||
|
||
.hero__title {
|
||
font-size: clamp(2.5rem, 6vw, 5rem);
|
||
font-weight: 600;
|
||
letter-spacing: var(--track-tightest);
|
||
line-height: 1.02;
|
||
margin: 0 0 var(--sp-7) 0;
|
||
}
|
||
|
||
.hero__lede {
|
||
font-size: var(--text-md);
|
||
line-height: 1.55;
|
||
color: var(--ink-muted);
|
||
max-width: 44ch;
|
||
margin: 0 0 var(--sp-7) 0;
|
||
}
|
||
|
||
.hero__actions {
|
||
display: flex;
|
||
gap: var(--sp-3);
|
||
flex-wrap: wrap;
|
||
margin-bottom: var(--sp-4);
|
||
}
|
||
|
||
.hero__fineprint {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
letter-spacing: var(--track-wide);
|
||
color: var(--ink-subtle);
|
||
margin: 0;
|
||
}
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 40px;
|
||
padding: 0 16px;
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
border-radius: var(--r-md);
|
||
border: 1px solid transparent;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, transform 80ms ease;
|
||
white-space: nowrap;
|
||
border-bottom-width: 1px;
|
||
}
|
||
.btn:active { transform: translateY(1px); }
|
||
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||
|
||
.btn--primary {
|
||
background: var(--accent);
|
||
color: var(--surface);
|
||
border-color: var(--accent);
|
||
font-weight: 600;
|
||
}
|
||
.btn--primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
|
||
|
||
.btn--ghost {
|
||
background: transparent;
|
||
color: var(--ink);
|
||
border-color: var(--hairline-strong);
|
||
}
|
||
.btn--ghost:hover { border-color: var(--ink-muted); background: var(--surface-1); }
|
||
|
||
@media (max-width: 560px) {
|
||
.btn { width: 100%; }
|
||
}
|
||
|
||
/* ----- Hero panel: feature flag list ------------------------- */
|
||
|
||
.panel {
|
||
background: var(--surface-1);
|
||
border: 1px solid var(--hairline-strong);
|
||
border-radius: var(--r-lg);
|
||
overflow: hidden;
|
||
box-shadow: 0 24px 48px rgba(0,0,0,0.4), 0 4px 16px rgba(0,0,0,0.3);
|
||
}
|
||
.panel__chrome {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--sp-2);
|
||
padding: var(--sp-3) var(--sp-4);
|
||
background: var(--surface-2);
|
||
border-bottom: 1px solid var(--hairline);
|
||
}
|
||
.panel__dot { width: 10px; height: 10px; border-radius: 50%; background: var(--hairline-strong); }
|
||
.panel__dot:nth-child(1) { background: #FF5F57; }
|
||
.panel__dot:nth-child(2) { background: #FEBC2E; }
|
||
.panel__dot:nth-child(3) { background: #28C840; }
|
||
.panel__url {
|
||
margin-left: var(--sp-4);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--ink-subtle);
|
||
}
|
||
|
||
.panel__body { padding: 0; }
|
||
|
||
.panel__head {
|
||
padding: var(--sp-4);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid var(--hairline);
|
||
}
|
||
.panel__title {
|
||
font-size: var(--text-md);
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
.panel__seg {
|
||
display: inline-flex;
|
||
background: var(--surface-2);
|
||
border: 1px solid var(--hairline);
|
||
border-radius: var(--r-sm);
|
||
padding: 2px;
|
||
}
|
||
.panel__seg-btn {
|
||
padding: 4px 10px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--ink-muted);
|
||
border-radius: 3px;
|
||
letter-spacing: var(--track-wide);
|
||
}
|
||
.panel__seg-btn--active {
|
||
background: var(--surface);
|
||
color: var(--ink);
|
||
}
|
||
|
||
/* Flag rows */
|
||
.flag-list { list-style: none; margin: 0; padding: 0; }
|
||
.flag {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||
gap: var(--sp-3);
|
||
align-items: center;
|
||
padding: var(--sp-3) var(--sp-4);
|
||
border-bottom: 1px solid var(--hairline);
|
||
font-size: var(--text-sm);
|
||
transition: background 100ms ease;
|
||
}
|
||
.flag:hover { background: var(--surface-2); }
|
||
.flag:last-child { border-bottom: 0; }
|
||
|
||
.flag__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||
.flag__key {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-sm);
|
||
color: var(--ink);
|
||
font-weight: 500;
|
||
}
|
||
.flag__desc {
|
||
font-size: 12px;
|
||
color: var(--ink-muted);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.flag__env {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
padding: 2px 8px;
|
||
border-radius: 3px;
|
||
letter-spacing: var(--track-wide);
|
||
}
|
||
.flag__env--prod { background: rgba(248, 113, 113, 0.12); color: var(--bad); }
|
||
.flag__env--stg { background: rgba(251, 191, 36, 0.12); color: var(--warn); }
|
||
.flag__env--dev { background: rgba(96, 165, 250, 0.12); color: var(--info); }
|
||
|
||
/* Toggle */
|
||
.toggle {
|
||
width: 32px;
|
||
height: 18px;
|
||
background: var(--surface-3);
|
||
border-radius: 9999px;
|
||
position: relative;
|
||
transition: background 140ms ease;
|
||
flex-shrink: 0;
|
||
}
|
||
.toggle__knob {
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 2px;
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
background: var(--ink-muted);
|
||
transition: transform 140ms ease, background 140ms ease;
|
||
}
|
||
.toggle--on { background: var(--accent); }
|
||
.toggle--on .toggle__knob { transform: translateX(14px); background: var(--surface); }
|
||
|
||
/* ----- Trusted-by (compact) -------------------------------- */
|
||
|
||
.trusted {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: var(--sp-7) clamp(20px, 4vw, 48px);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--sp-7);
|
||
flex-wrap: wrap;
|
||
border-top: 1px solid var(--hairline);
|
||
border-bottom: 1px solid var(--hairline);
|
||
background: var(--surface-1);
|
||
}
|
||
.trusted__label {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
letter-spacing: var(--track-widest);
|
||
text-transform: uppercase;
|
||
color: var(--ink-subtle);
|
||
margin: 0;
|
||
}
|
||
.trusted__list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
gap: var(--sp-7);
|
||
flex-wrap: wrap;
|
||
}
|
||
.trusted__item {
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: var(--sp-2);
|
||
font-size: var(--text-md);
|
||
font-weight: 500;
|
||
letter-spacing: var(--track-tight);
|
||
color: var(--ink-muted);
|
||
}
|
||
.trusted__item strong { color: var(--ink); font-weight: 500; }
|
||
|
||
/* ----- Features section -------------------------------------- */
|
||
|
||
.features {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: clamp(80px, 12vw, 160px) clamp(20px, 4vw, 48px);
|
||
}
|
||
|
||
.section-head { margin-bottom: var(--sp-8); }
|
||
.section-head__kicker {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
letter-spacing: var(--track-widest);
|
||
text-transform: uppercase;
|
||
color: var(--ink-muted);
|
||
margin: 0 0 var(--sp-5) 0;
|
||
}
|
||
.section-head__title {
|
||
font-size: clamp(1.875rem, 4vw, 3rem);
|
||
font-weight: 600;
|
||
letter-spacing: var(--track-tight);
|
||
line-height: 1.1;
|
||
margin: 0;
|
||
max-width: 22ch;
|
||
}
|
||
|
||
/* Feature 1 — code block */
|
||
.feature {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
|
||
gap: clamp(32px, 6vw, 64px);
|
||
align-items: center;
|
||
padding-block: clamp(48px, 7vw, 80px);
|
||
border-top: 1px solid var(--hairline);
|
||
}
|
||
.feature:first-of-type { border-top: 0; padding-top: 0; }
|
||
|
||
.feature__body .kicker {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
letter-spacing: var(--track-widest);
|
||
text-transform: uppercase;
|
||
color: var(--ink-muted);
|
||
margin: 0 0 var(--sp-5) 0;
|
||
}
|
||
.feature__body h3 {
|
||
font-size: clamp(1.375rem, 2.4vw, 2rem);
|
||
font-weight: 600;
|
||
letter-spacing: var(--track-tight);
|
||
line-height: 1.2;
|
||
margin: 0 0 var(--sp-5) 0;
|
||
}
|
||
.feature__body p {
|
||
color: var(--ink-muted);
|
||
font-size: var(--text-md);
|
||
line-height: 1.55;
|
||
max-width: 44ch;
|
||
margin: 0 0 var(--sp-5) 0;
|
||
}
|
||
.feature__list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--sp-2);
|
||
font-size: var(--text-sm);
|
||
color: var(--ink-muted);
|
||
}
|
||
.feature__list li {
|
||
padding-left: var(--sp-5);
|
||
position: relative;
|
||
}
|
||
.feature__list li::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0.6em;
|
||
width: 12px;
|
||
height: 1px;
|
||
background: var(--accent);
|
||
}
|
||
|
||
/* Code block */
|
||
.codeblock {
|
||
background: var(--surface-sunken);
|
||
border: 1px solid var(--hairline-strong);
|
||
border-radius: var(--r-lg);
|
||
overflow: hidden;
|
||
}
|
||
.codeblock__head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: var(--sp-3) var(--sp-4);
|
||
border-bottom: 1px solid var(--hairline);
|
||
background: var(--surface-1);
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
color: var(--ink-muted);
|
||
letter-spacing: var(--track-wide);
|
||
}
|
||
.codeblock__file { color: var(--ink); }
|
||
.codeblock__body {
|
||
margin: 0;
|
||
padding: var(--sp-5);
|
||
overflow-x: auto;
|
||
font-family: var(--font-mono);
|
||
font-size: 13px;
|
||
line-height: 1.65;
|
||
color: var(--ink);
|
||
}
|
||
.tok-cmt { color: var(--ink-subtle); font-style: italic; }
|
||
.tok-key { color: #C792EA; }
|
||
.tok-str { color: #C3E88D; }
|
||
.tok-fn { color: #82AAFF; }
|
||
.tok-var { color: #F78C6C; }
|
||
.tok-num { color: #F78C6C; }
|
||
|
||
/* Feature 2 — flags visualization */
|
||
.viz-flags {
|
||
background: var(--surface-1);
|
||
border: 1px solid var(--hairline-strong);
|
||
border-radius: var(--r-lg);
|
||
padding: var(--sp-5);
|
||
}
|
||
.viz-flags__head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--sp-4);
|
||
}
|
||
.viz-flags__head h4 {
|
||
font-size: var(--text-sm);
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
.viz-flags__count {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
letter-spacing: var(--track-wide);
|
||
color: var(--ink-muted);
|
||
}
|
||
.viz-flags__row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 60px 60px 60px;
|
||
gap: var(--sp-3);
|
||
padding: var(--sp-3) 0;
|
||
border-top: 1px solid var(--hairline);
|
||
align-items: center;
|
||
font-size: var(--text-sm);
|
||
}
|
||
.viz-flags__row:first-of-type { border-top: 0; }
|
||
.viz-flags__name {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
color: var(--ink);
|
||
}
|
||
.viz-flags__pct {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
color: var(--ink-muted);
|
||
text-align: right;
|
||
}
|
||
.viz-flags__bar {
|
||
height: 6px;
|
||
background: var(--surface-3);
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
.viz-flags__bar-fill {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: var(--pct);
|
||
background: var(--accent);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
@media (max-width: 880px) {
|
||
.hero__inner,
|
||
.feature {
|
||
grid-template-columns: 1fr;
|
||
gap: var(--sp-7);
|
||
}
|
||
.viz-flags__row { grid-template-columns: 1fr 50px 50px 50px; }
|
||
}
|
||
|
||
/* ----- Pricing ----------------------------------------------- */
|
||
|
||
.pricing {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: clamp(80px, 12vw, 160px) clamp(20px, 4vw, 48px);
|
||
border-top: 1px solid var(--hairline);
|
||
}
|
||
.pricing__grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: var(--sp-5);
|
||
max-width: 880px;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.pricing__grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.plan {
|
||
background: var(--surface-1);
|
||
border: 1px solid var(--hairline-strong);
|
||
border-radius: var(--r-lg);
|
||
padding: var(--sp-7);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--sp-5);
|
||
}
|
||
.plan--featured {
|
||
border-color: var(--accent);
|
||
background: linear-gradient(180deg, var(--accent-soft) 0%, var(--surface-1) 40%);
|
||
}
|
||
.plan__head { padding-bottom: var(--sp-5); border-bottom: 1px solid var(--hairline); }
|
||
.plan__name {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
letter-spacing: var(--track-widest);
|
||
text-transform: uppercase;
|
||
color: var(--ink-muted);
|
||
margin: 0 0 var(--sp-4) 0;
|
||
}
|
||
.plan--featured .plan__name { color: var(--accent); }
|
||
.plan__price {
|
||
font-size: var(--text-4xl);
|
||
font-weight: 600;
|
||
letter-spacing: var(--track-tight);
|
||
margin: 0;
|
||
line-height: 1;
|
||
}
|
||
.plan__period {
|
||
font-size: var(--text-md);
|
||
color: var(--ink-muted);
|
||
font-weight: 500;
|
||
}
|
||
.plan__sub {
|
||
margin: var(--sp-3) 0 0 0;
|
||
font-size: var(--text-sm);
|
||
color: var(--ink-muted);
|
||
}
|
||
.plan__list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--sp-3);
|
||
font-size: var(--text-sm);
|
||
flex: 1;
|
||
}
|
||
.plan__list li {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: var(--sp-3);
|
||
}
|
||
.plan__list li::before {
|
||
content: '✓';
|
||
color: var(--accent);
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
font-family: var(--font-mono);
|
||
}
|
||
|
||
/* ----- CTA --------------------------------------------------- */
|
||
|
||
.cta {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: clamp(80px, 12vw, 160px) clamp(20px, 4vw, 48px);
|
||
border-top: 1px solid var(--hairline);
|
||
text-align: center;
|
||
}
|
||
.cta__title {
|
||
font-size: clamp(2rem, 4.5vw, 3.5rem);
|
||
font-weight: 600;
|
||
letter-spacing: var(--track-tight);
|
||
line-height: 1.1;
|
||
margin: 0;
|
||
}
|
||
.cta__sub {
|
||
color: var(--ink-muted);
|
||
font-size: var(--text-md);
|
||
max-width: 50ch;
|
||
margin: var(--sp-5) auto 0;
|
||
}
|
||
.cta__form {
|
||
display: flex;
|
||
gap: var(--sp-2);
|
||
max-width: 460px;
|
||
margin: var(--sp-7) auto 0;
|
||
}
|
||
.cta__input {
|
||
flex: 1;
|
||
height: 44px;
|
||
padding: 0 var(--sp-4);
|
||
background: var(--surface-1);
|
||
border: 1px solid var(--hairline-strong);
|
||
border-radius: var(--r-md);
|
||
color: var(--ink);
|
||
font-size: var(--text-sm);
|
||
transition: border-color 140ms ease;
|
||
}
|
||
.cta__input::placeholder { color: var(--ink-subtle); }
|
||
.cta__input:focus { border-color: var(--accent); outline: none; }
|
||
.cta__input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||
@media (max-width: 560px) {
|
||
.cta__form { flex-direction: column; }
|
||
}
|
||
|
||
/* ----- Footer ----------------------------------------------- */
|
||
|
||
.footer {
|
||
padding: var(--sp-8) clamp(20px, 4vw, 48px);
|
||
border-top: 1px solid var(--hairline);
|
||
background: var(--surface-1);
|
||
}
|
||
.footer__inner {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
||
gap: var(--sp-8);
|
||
}
|
||
.footer__brand {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--sp-2);
|
||
font-size: var(--text-md);
|
||
font-weight: 600;
|
||
letter-spacing: var(--track-tight);
|
||
color: var(--ink);
|
||
}
|
||
.footer__brand .nav__brand-mark { color: var(--accent); }
|
||
.footer__nav {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: var(--sp-7);
|
||
}
|
||
.footer__nav .kicker {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
letter-spacing: var(--track-widest);
|
||
text-transform: uppercase;
|
||
color: var(--ink-muted);
|
||
margin: 0 0 var(--sp-3) 0;
|
||
}
|
||
.footer__nav a {
|
||
display: block;
|
||
color: var(--ink-muted);
|
||
font-size: var(--text-sm);
|
||
padding: 4px 0;
|
||
border-bottom: 0;
|
||
}
|
||
.footer__nav a:hover { color: var(--ink); }
|
||
.footer__bot {
|
||
max-width: 1200px;
|
||
margin: var(--sp-7) auto 0;
|
||
padding-top: var(--sp-5);
|
||
border-top: 1px solid var(--hairline);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: var(--sp-3);
|
||
}
|
||
.footer__meta { margin: 0; font-size: var(--text-xs); color: var(--ink-muted); }
|
||
.footer__build {
|
||
margin: 0;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--ink-subtle);
|
||
letter-spacing: var(--track-wide);
|
||
}
|
||
@media (max-width: 720px) {
|
||
.footer__inner, .footer__nav { grid-template-columns: 1fr; gap: var(--sp-7); }
|
||
.footer__nav { grid-template-columns: repeat(2, 1fr); }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation-duration: 0.01ms !important;
|
||
transition-duration: 0.01ms !important;
|
||
scroll-behavior: auto !important;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ====== NAV ============================================== -->
|
||
<header class="nav" role="banner">
|
||
<div class="nav__inner">
|
||
<a href="#" class="nav__brand" aria-label="Latch — home">
|
||
<svg class="nav__brand-mark" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true">
|
||
<path d="M4 5 H16 V8 H4 Z M4 12 H12 V15 H4 Z" fill="currentColor"/>
|
||
</svg>
|
||
<span>Latch</span>
|
||
</a>
|
||
<nav class="nav__links" aria-label="Primary">
|
||
<a href="#product">Product</a>
|
||
<a href="#pricing">Pricing</a>
|
||
<a href="#">Docs</a>
|
||
<a href="#">Sign in</a>
|
||
<a href="#cta" class="nav__cta">Start free →</a>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main id="main">
|
||
|
||
<!-- ====== HERO ============================================== -->
|
||
<section class="hero" aria-labelledby="hero-title">
|
||
<div class="hero__inner">
|
||
<div>
|
||
<p class="hero__kicker">
|
||
<span class="hero__kicker-dot" aria-hidden="true"></span>
|
||
v3.2 — Now with local evaluation, 0ms overhead
|
||
</p>
|
||
<h1 id="hero-title" class="hero__title">
|
||
Feature flags<br>
|
||
that don't<br>
|
||
get in the way.
|
||
</h1>
|
||
<p class="hero__lede">
|
||
Toggle, target, and roll back in under 50ms — without
|
||
the YAML, without the SDK lock-in, without the
|
||
dashboard that takes ten clicks to flip one switch.
|
||
</p>
|
||
<div class="hero__actions">
|
||
<a href="#cta" class="btn btn--primary">Start free — no card</a>
|
||
<a href="#install" class="btn btn--ghost">See the install</a>
|
||
</div>
|
||
<p class="hero__fineprint">Free up to 100k evaluations / month · No credit card · 5-minute setup</p>
|
||
</div>
|
||
|
||
<!-- Flag panel mockup -->
|
||
<figure aria-label="Latch dashboard — flags list">
|
||
<div class="panel">
|
||
<div class="panel__chrome">
|
||
<span class="panel__dot"></span>
|
||
<span class="panel__dot"></span>
|
||
<span class="panel__dot"></span>
|
||
<span class="panel__url">latch.run / flags / acme-prod</span>
|
||
</div>
|
||
<div class="panel__body">
|
||
<div class="panel__head">
|
||
<h3 class="panel__title">Flags</h3>
|
||
<div class="panel__seg" role="tablist" aria-label="Environment">
|
||
<button class="panel__seg-btn" role="tab">Dev</button>
|
||
<button class="panel__seg-btn" role="tab">Stg</button>
|
||
<button class="panel__seg-btn panel__seg-btn--active" role="tab" aria-selected="true">Prod</button>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="flag-list" role="list">
|
||
<li class="flag">
|
||
<div class="flag__main">
|
||
<span class="flag__key">checkout-v3-redesign</span>
|
||
<span class="flag__desc">New checkout flow with Apple Pay</span>
|
||
</div>
|
||
<span class="flag__env flag__env--prod">prod</span>
|
||
<span class="toggle toggle--on" role="img" aria-label="Enabled"><span class="toggle__knob"></span></span>
|
||
</li>
|
||
<li class="flag">
|
||
<div class="flag__main">
|
||
<span class="flag__key">ai-summarize-beta</span>
|
||
<span class="flag__desc">GPT-4 summary on doc pages</span>
|
||
</div>
|
||
<span class="flag__env flag__env--stg">stg</span>
|
||
<span class="toggle toggle--on" role="img" aria-label="Enabled"><span class="toggle__knob"></span></span>
|
||
</li>
|
||
<li class="flag">
|
||
<div class="flag__main">
|
||
<span class="flag__key">dark-mode-default</span>
|
||
<span class="flag__desc">Auto-dark for system pref users</span>
|
||
</div>
|
||
<span class="flag__env flag__env--prod">prod</span>
|
||
<span class="toggle toggle--on" role="img" aria-label="Enabled"><span class="toggle__knob"></span></span>
|
||
</li>
|
||
<li class="flag">
|
||
<div class="flag__main">
|
||
<span class="flag__key">referral-rewards-v2</span>
|
||
<span class="flag__desc">New tiered referral program</span>
|
||
</div>
|
||
<span class="flag__env flag__env--prod">prod</span>
|
||
<span class="toggle" role="img" aria-label="Disabled"><span class="toggle__knob"></span></span>
|
||
</li>
|
||
<li class="flag">
|
||
<div class="flag__main">
|
||
<span class="flag__key">edge-cache-rewrite</span>
|
||
<span class="flag__desc">New CDN invalidation logic</span>
|
||
</div>
|
||
<span class="flag__env flag__env--dev">dev</span>
|
||
<span class="toggle" role="img" aria-label="Disabled"><span class="toggle__knob"></span></span>
|
||
</li>
|
||
<li class="flag">
|
||
<div class="flag__main">
|
||
<span class="flag__key">homepage-experiment-q1</span>
|
||
<span class="flag__desc">50/50 split, 14 day window</span>
|
||
</div>
|
||
<span class="flag__env flag__env--prod">prod</span>
|
||
<span class="toggle toggle--on" role="img" aria-label="Enabled"><span class="toggle__knob"></span></span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</figure>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ====== TRUSTED-BY ======================================== -->
|
||
<section class="trusted" aria-label="Customers">
|
||
<p class="trusted__label">In production at</p>
|
||
<ul class="trusted__list" role="list">
|
||
<li class="trusted__item"><strong>Plover</strong> · payments</li>
|
||
<li class="trusted__item"><strong>Northwind</strong> · B2B SaaS</li>
|
||
<li class="trusted__item"><strong>Cobalt</strong> · dev tools</li>
|
||
<li class="trusted__item"><strong>Quill</strong> · editorial CMS</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- ====== FEATURES ========================================== -->
|
||
<section id="product" class="features" aria-labelledby="features-heading">
|
||
<header class="section-head">
|
||
<p class="section-head__kicker">§ 01 — The product</p>
|
||
<h2 id="features-heading" class="section-head__title">
|
||
Three things every flag<br>
|
||
system should do. Latch<br>
|
||
does all three well.
|
||
</h2>
|
||
</header>
|
||
|
||
<!-- Feature 1: install -->
|
||
<article id="install" class="feature">
|
||
<div class="feature__body">
|
||
<p class="kicker">01 · Install</p>
|
||
<h3>Four lines of code. Live in 4 minutes.</h3>
|
||
<p>
|
||
One env var, one import, one provider. Latch works with
|
||
Next.js, Remix, Rails, Django, Go, anything that can
|
||
read an environment variable. Local evaluation means
|
||
<strong>zero network round-trips</strong> in production.
|
||
</p>
|
||
<ul class="feature__list" role="list">
|
||
<li>~12 KB gzipped SDK, no dependencies</li>
|
||
<li>Works in edge runtimes (Vercel, Cloudflare)</li>
|
||
<li>Open-source SDKs in 7 languages</li>
|
||
<li>SOC 2 Type II, EU and US data residency</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<div class="codeblock">
|
||
<div class="codeblock__head">
|
||
<span class="codeblock__file">app/layout.tsx</span>
|
||
<span>TypeScript</span>
|
||
</div>
|
||
<pre class="codeblock__body"><code><span class="tok-cmt">// 1. Install</span>
|
||
<span class="tok-key">npm i</span> <span class="tok-str">@latch/sdk</span>
|
||
|
||
<span class="tok-cmt">// 2. Set your key</span>
|
||
<span class="tok-var">LATCH_KEY</span>=<span class="tok-str">lk_live_••••••••••••</span>
|
||
|
||
<span class="tok-cmt">// 3. Use it</span>
|
||
<span class="tok-key">import</span> { latch } <span class="tok-key">from</span> <span class="tok-str">'@latch/sdk'</span>
|
||
|
||
<span class="tok-key">if</span> (latch.<span class="tok-fn">isOn</span>(<span class="tok-str">'checkout-v3-redesign'</span>)) {
|
||
<span class="tok-key">return</span> <<span class="tok-fn">NewCheckout</span> />
|
||
}
|
||
<span class="tok-key">return</span> <<span class="tok-fn">LegacyCheckout</span> /></code></pre>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Feature 2: targeting -->
|
||
<article class="feature">
|
||
<div>
|
||
<div class="viz-flags">
|
||
<div class="viz-flags__head">
|
||
<h4>Rollout · homepage-experiment-q1</h4>
|
||
<span class="viz-flags__count">3 segments · 14 days</span>
|
||
</div>
|
||
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">Returning US</span>
|
||
<span class="viz-flags__pct mono">100%</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 100%"></span></span>
|
||
<span class="viz-flags__pct mono">142k</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">New EU</span>
|
||
<span class="viz-flags__pct mono">50%</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 50%"></span></span>
|
||
<span class="viz-flags__pct mono">28k</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">Mobile / iOS 17</span>
|
||
<span class="viz-flags__pct mono">25%</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 25%"></span></span>
|
||
<span class="viz-flags__pct mono">9.4k</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">Beta program</span>
|
||
<span class="viz-flags__pct mono">100%</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 100%"></span></span>
|
||
<span class="viz-flags__pct mono">2.1k</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">Everyone else</span>
|
||
<span class="viz-flags__pct mono">0%</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 0%"></span></span>
|
||
<span class="viz-flags__pct mono">—</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="feature__body">
|
||
<p class="kicker">02 · Targeting</p>
|
||
<h3>Target by anything. Roll out in real time.</h3>
|
||
<p>
|
||
Segment by user attributes, geography, plan, device,
|
||
cohort, or anything you send. Build segments in the UI,
|
||
or define them in code — Latch syncs both directions.
|
||
Roll back in one click. Pause a flag globally in <200ms.
|
||
</p>
|
||
<ul class="feature__list" role="list">
|
||
<li>Targeting by 20+ attributes out of the box</li>
|
||
<li>Custom attributes via SDK</li>
|
||
<li>A/B testing with built-in statistical significance</li>
|
||
<li>Audit log of every change, every change-maker</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
|
||
<!-- Feature 3: speed -->
|
||
<article class="feature">
|
||
<div class="feature__body">
|
||
<p class="kicker">03 · Speed</p>
|
||
<h3>Sub-50ms evaluations. Even at the edge.</h3>
|
||
<p>
|
||
Latch evaluates flags locally from a 2KB signed payload
|
||
that ships with your build. There's no round-trip to
|
||
a control plane, no network jitter, no cold-start penalty.
|
||
Your slowest flag check is faster than your fastest
|
||
database query.
|
||
</p>
|
||
<ul class="feature__list" role="list">
|
||
<li>p50: 0.4ms · p95: 2.1ms · p99: 8ms (cold)</li>
|
||
<li>Payload cached in CDN, refreshed every 30s</li>
|
||
<li>Works offline (last-known-good payload)</li>
|
||
<li>No runtime dependency on Latch being up</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<div class="viz-flags">
|
||
<div class="viz-flags__head">
|
||
<h4>Evaluation latency · last 24h</h4>
|
||
<span class="viz-flags__count">p95 = 2.1ms</span>
|
||
</div>
|
||
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">p50</span>
|
||
<span class="viz-flags__pct mono">0.4ms</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 4%"></span></span>
|
||
<span class="viz-flags__pct mono">~</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">p75</span>
|
||
<span class="viz-flags__pct mono">1.0ms</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 10%"></span></span>
|
||
<span class="viz-flags__pct mono">~</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">p95</span>
|
||
<span class="viz-flags__pct mono">2.1ms</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 21%"></span></span>
|
||
<span class="viz-flags__pct mono">~</span>
|
||
</div>
|
||
<div class="viz-flags__row">
|
||
<span class="viz-flags__name">p99</span>
|
||
<span class="viz-flags__pct mono">8.0ms</span>
|
||
<span class="viz-flags__bar"><span class="viz-flags__bar-fill" style="--pct: 80%"></span></span>
|
||
<span class="viz-flags__pct mono">~</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</section>
|
||
|
||
<!-- ====== PRICING =========================================== -->
|
||
<section id="pricing" class="pricing" aria-labelledby="pricing-heading">
|
||
<header class="section-head">
|
||
<p class="section-head__kicker">§ 02 — Pricing</p>
|
||
<h2 id="pricing-heading" class="section-head__title">Two plans. Both honest.</h2>
|
||
</header>
|
||
|
||
<div class="pricing__grid">
|
||
<article class="plan">
|
||
<header class="plan__head">
|
||
<p class="plan__name">Hobby</p>
|
||
<p class="plan__price">$0<span class="plan__period"> / month</span></p>
|
||
<p class="plan__sub">For side projects and prototypes. Free forever.</p>
|
||
</header>
|
||
<ul class="plan__list" role="list">
|
||
<li>100,000 evaluations / month</li>
|
||
<li>Unlimited flags</li>
|
||
<li>3 environments</li>
|
||
<li>3 team members</li>
|
||
<li>Community support</li>
|
||
</ul>
|
||
<a href="#cta" class="btn btn--ghost">Start free</a>
|
||
</article>
|
||
|
||
<article class="plan plan--featured">
|
||
<header class="plan__head">
|
||
<p class="plan__name">Production</p>
|
||
<p class="plan__price">$320<span class="plan__period"> / month</span></p>
|
||
<p class="plan__sub">For apps with real traffic. Includes everything in Hobby.</p>
|
||
</header>
|
||
<ul class="plan__list" role="list">
|
||
<li>5M evaluations / month, then $0.06 / 100k</li>
|
||
<li>Unlimited environments, unlimited members</li>
|
||
<li>A/B testing + statistical significance</li>
|
||
<li>Audit log, RBAC, SAML SSO</li>
|
||
<li>EU + US data residency</li>
|
||
<li>SOC 2 Type II report</li>
|
||
<li>4-hour response SLA, shared Slack channel</li>
|
||
</ul>
|
||
<a href="#cta" class="btn btn--primary">Start 14-day trial</a>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ====== CTA =============================================== -->
|
||
<section id="cta" class="cta" aria-labelledby="cta-title">
|
||
<h2 id="cta-title" class="cta__title">Ship a flag in the next 4 minutes.</h2>
|
||
<p class="cta__sub">Free up to 100k evaluations / month · No credit card · Cancel any time</p>
|
||
<form class="cta__form" action="#" method="post" novalidate>
|
||
<label for="email" class="sr-only">Work email</label>
|
||
<input id="email" type="email" required class="cta__input"
|
||
placeholder="you@company.com" autocomplete="email">
|
||
<button type="submit" class="btn btn--primary">Get started →</button>
|
||
</form>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<!-- ====== FOOTER ============================================ -->
|
||
<footer class="footer" role="contentinfo">
|
||
<div class="footer__inner">
|
||
<div>
|
||
<a href="#" class="footer__brand">
|
||
<svg class="nav__brand-mark" width="18" height="18" viewBox="0 0 20 20" aria-hidden="true">
|
||
<path d="M4 5 H16 V8 H4 Z M4 12 H12 V15 H4 Z" fill="currentColor"/>
|
||
</svg>
|
||
<span>Latch</span>
|
||
</a>
|
||
</div>
|
||
<nav class="footer__nav" aria-label="Footer">
|
||
<div>
|
||
<p class="kicker">Product</p>
|
||
<a href="#">Flags</a>
|
||
<a href="#">Targeting</a>
|
||
<a href="#">Experiments</a>
|
||
<a href="#">Pricing</a>
|
||
</div>
|
||
<div>
|
||
<p class="kicker">Resources</p>
|
||
<a href="#">Docs</a>
|
||
<a href="#">API</a>
|
||
<a href="#">Status</a>
|
||
<a href="#">Security</a>
|
||
</div>
|
||
<div>
|
||
<p class="kicker">Company</p>
|
||
<a href="#">About</a>
|
||
<a href="#">Customers</a>
|
||
<a href="#">Blog</a>
|
||
<a href="#">Careers</a>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
<div class="footer__bot">
|
||
<p class="footer__meta">© 2024–2026 Latch Labs, Inc. · Made in Berlin and Lisbon</p>
|
||
<p class="footer__build">v3.2.7 · build 8f4a12 · uptime 99.99% · region eu-west-1</p>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
// Segmented control — keyboard + click
|
||
document.querySelectorAll('.panel__seg').forEach(function (group) {
|
||
const btns = group.querySelectorAll('.panel__seg-btn');
|
||
btns.forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
btns.forEach(function (b) {
|
||
b.classList.remove('panel__seg-btn--active');
|
||
b.setAttribute('aria-selected', 'false');
|
||
});
|
||
btn.classList.add('panel__seg-btn--active');
|
||
btn.setAttribute('aria-selected', 'true');
|
||
});
|
||
});
|
||
});
|
||
|
||
// Toggle interaction (demo only)
|
||
document.querySelectorAll('.toggle').forEach(function (t) {
|
||
t.addEventListener('click', function () {
|
||
t.classList.toggle('toggle--on');
|
||
});
|
||
t.addEventListener('keydown', function (e) {
|
||
if (e.key === ' ' || e.key === 'Enter') {
|
||
e.preventDefault();
|
||
t.classList.toggle('toggle--on');
|
||
}
|
||
});
|
||
t.setAttribute('tabindex', '0');
|
||
t.setAttribute('role', 'switch');
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|