// buttons.jsx — 5 button styles. Pass `variant` to switch between them. // Each variant has a `primary` (filled / dominant) and `secondary` (ghost) // tone. All sharp-edged — no pills. Micro-motion on every clickable thing. // Now consumes --tap-min / --focus-ring from landing foundation tokens (shared.jsx). // All interactive meet ≥44px tap targets. Focus visible for a11y (iPad kb + desktop). (function () { const { PALETTE } = window; // Inject shared button keyframes once if (typeof document !== "undefined" && !document.getElementById("ol-button-styles")) { const s = document.createElement("style"); s.id = "ol-button-styles"; s.textContent = ` .ol-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 10px; padding: 14px 22px; font-family: "Inter", ui-sans-serif, system-ui, sans-serif; font-size: 14.5px; font-weight: 500; letter-spacing: 0.1px; text-decoration: none; cursor: pointer; border: none; background: none; transition: all .25s ease; white-space: nowrap; user-select: none; min-height: var(--tap-min); -webkit-tap-highlight-color: transparent; } .ol-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); } .ol-btn-arrow { display: inline-block; transition: transform .3s cubic-bezier(.2,.7,.3,1); } .ol-btn:hover .ol-btn-arrow { transform: translateX(4px); } /* A · BLOCK — sharp solid rectangle */ .ol-btn-block { background: ${PALETTE.ink}; color: ${PALETTE.bg}; padding: 16px 24px; } .ol-btn-block:hover { background: ${PALETTE.accent}; } .ol-btn-block:active { transform: scale(0.985); } .ol-btn-block.ol-btn-secondary { background: transparent; color: ${PALETTE.ink}; box-shadow: inset 0 0 0 1px ${PALETTE.hairline}; } .ol-btn-block.ol-btn-secondary:hover { background: ${PALETTE.ink}; color: ${PALETTE.bg}; box-shadow: inset 0 0 0 1px ${PALETTE.ink}; } /* B · OUTLINE — hairline rectangle, fills on hover */ .ol-btn-outline { background: transparent; color: ${PALETTE.ink}; padding: 15px 22px; box-shadow: inset 0 0 0 1px ${PALETTE.hairline}; text-transform: uppercase; font-size: 12px; letter-spacing: 1.6px; font-weight: 600; } .ol-btn-outline:hover { background: ${PALETTE.ink}; color: ${PALETTE.bg}; box-shadow: inset 0 0 0 1px ${PALETTE.ink}; } .ol-btn-outline:active { transform: scale(0.985); } .ol-btn-outline.ol-btn-secondary { box-shadow: none; padding-left: 0; padding-right: 0; } .ol-btn-outline.ol-btn-secondary:hover { background: transparent; color: ${PALETTE.accent}; } /* C · UNDERLINE — text + animated underline (tap target enforced) */ .ol-btn-underline { background: transparent; color: ${PALETTE.ink}; padding: 13px 0; font-size: 16px; font-weight: 500; position: relative; gap: 8px; min-height: var(--tap-min); } .ol-btn-underline::after { content: ""; position: absolute; left: 0; bottom: 0; height: 1.5px; width: 100%; background: ${PALETTE.ink}; transform: scaleX(1); transform-origin: left; transition: transform .4s cubic-bezier(.7,0,.2,1), background .25s ease; } .ol-btn-underline:hover::after { background: ${PALETTE.accent}; } .ol-btn-underline:focus-visible::after { background: ${PALETTE.accent}; } .ol-btn-underline.ol-btn-secondary::after { background: ${PALETTE.hairline}; transform: scaleX(0); } .ol-btn-underline.ol-btn-secondary:hover::after { transform: scaleX(1); background: ${PALETTE.ink}; } /* D · STRIPE — sharp rectangle with accent stripe on left that grows */ .ol-btn-stripe { background: ${PALETTE.ink}; color: ${PALETTE.bg}; padding: 16px 24px 16px 32px; position: relative; overflow: hidden; } .ol-btn-stripe::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 6px; background: ${PALETTE.accent}; transition: width .35s cubic-bezier(.2,.7,.3,1); } .ol-btn-stripe:hover::before { width: 14px; } .ol-btn-stripe:active { transform: scale(0.985); } .ol-btn-stripe.ol-btn-secondary { background: transparent; color: ${PALETTE.ink}; box-shadow: inset 0 0 0 1px ${PALETTE.hairline}; } .ol-btn-stripe.ol-btn-secondary::before { background: ${PALETTE.accent}; } .ol-btn-stripe.ol-btn-secondary:hover { background: ${PALETTE.soft}; } /* E · BRUTALIST — sharp rectangle with offset accent shadow */ .ol-btn-brutal { background: ${PALETTE.bg}; color: ${PALETTE.ink}; box-shadow: inset 0 0 0 1.5px ${PALETTE.ink}, 4px 4px 0 0 ${PALETTE.accent}; padding: 14px 22px; transform: translate(0, 0); transition: transform .2s ease, box-shadow .2s ease; } .ol-btn-brutal:hover { transform: translate(2px, 2px); box-shadow: inset 0 0 0 1.5px ${PALETTE.ink}, 2px 2px 0 0 ${PALETTE.accent}; } .ol-btn-brutal:active { transform: translate(1px, 1px) scale(0.985); } .ol-btn-brutal.ol-btn-secondary { box-shadow: inset 0 0 0 1.5px ${PALETTE.hairline}, 4px 4px 0 0 transparent; } .ol-btn-brutal.ol-btn-secondary:hover { box-shadow: inset 0 0 0 1.5px ${PALETTE.ink}, 2px 2px 0 0 ${PALETTE.accent}; } `; document.head.appendChild(s); } // OLButton — main button component. Variants: block, outline, underline, stripe, brutal. function OLButton({ variant = "block", tone = "primary", href, onClick, arrow = true, children, style }) { const className = `ol-btn ol-btn-${variant}${tone === "secondary" ? " ol-btn-secondary" : ""}`; const Tag = href ? "a" : "button"; return ( {children} {arrow ? : null} ); } // ButtonComparison — shows all 5 variants side-by-side for the user to pick. // Each row shows primary + secondary in that style. function ButtonComparison({ selected, onSelect }) { const variants = [ { key: "block", label: "A · Block", note: "Solid filled rectangle — direct, contemporary" }, { key: "outline", label: "B · Outline", note: "Hairline frame with all-caps — editorial" }, { key: "underline", label: "C · Underline", note: "Text + animated underline — minimal, magazine" }, { key: "stripe", label: "D · Stripe", note: "Solid with accent stripe — built, mechanical" }, { key: "brutal", label: "E · Brutalist", note: "Offset shadow — bold, crafted" }, ]; return (
{variants.map((v, i) => (
onSelect && onSelect(v.key)} style={{ display: "grid", gridTemplateColumns: "180px 1fr 1fr 140px", alignItems: "center", gap: 32, padding: "28px 32px", borderTop: i === 0 ? `1px solid ${PALETTE.line}` : "none", borderBottom: `1px solid ${PALETTE.line}`, background: selected === v.key ? PALETTE.soft : "transparent", cursor: "pointer", transition: "background .2s ease", }} >
{v.label}
{v.note}
Email Lucas
See how it works
{selected === v.key ? "Selected" : "Click to select"}
))}
); } Object.assign(window, { OLButton, ButtonComparison }); })();