// Hotel Walks — shared UI primitives. // Use sparingly; layout is mostly bespoke per screen. const UI = (() => { const C = { ink: "var(--ink)", bone: "var(--bone)", bone2: "var(--bone-2)", rule: "var(--rule)", ruleStrong: "var(--rule-strong)", muted: "var(--muted)", warn: "var(--warn)", good: "var(--good)", }; function Caps({ children, color, style, className }) { return ( {children} ); } function SerifI({ children, size = 24, style }) { return ( {children} ); } // Primary button — Ink filled on Bone, or Bone filled on Ink. function Btn({ children, onClick, kind = "primary", size = "md", disabled, style, type, full, as = "button", href }) { const sizes = { sm: { padding: "9px 14px", fontSize: 12, letterSpacing: "0.08em" }, md: { padding: "13px 22px", fontSize: 12, letterSpacing: "0.12em" }, lg: { padding: "18px 28px", fontSize: 13, letterSpacing: "0.14em" }, }; const base = { ...sizes[size], fontFamily: "'JetBrains Mono', monospace", textTransform: "uppercase", border: "1px solid " + C.ink, borderRadius: 0, cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.4 : 1, transition: "background 0.12s, color 0.12s, border-color 0.12s", width: full ? "100%" : undefined, display: "inline-block", textAlign: "center", textDecoration: "none", whiteSpace: "nowrap", }; const kinds = { primary: { background: C.ink, color: C.bone }, ghost: { background: "transparent", color: C.ink }, danger: { background: "transparent", color: C.warn, borderColor: C.warn }, onInk: { background: C.bone, color: C.ink, borderColor: C.bone }, onInkGhost: { background: "transparent", color: C.bone, borderColor: C.bone }, }; const Tag = as; return ( {children} ); } // Field — label sits as a caps row above the underlined input. function Field({ label, suffix, children, hint, style }) { return ( ); } function Input({ value, onChange, placeholder, type = "text", mono, style }) { return ( ); } // Numeric stepper — for room counts, rates, days function Stepper({ value, onChange, min = 0, max = 99, step = 1, prefix, mono = true, width = 140 }) { const v = value; const set = (n) => onChange(Math.max(min, Math.min(max, n))); return (
{prefix}{v}
); } // Small stroke icons paired with status pills — non-color-only signals. function PillIcon({ tone }) { const sw = 1.6; const common = { width: 11, height: 11, viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: sw, strokeLinecap: "square", strokeLinejoin: "round" }; if (tone === "warn") return ( ); if (tone === "good") return ( ); return null; } // Pill — for status badges. Filled (solid) for warn/good/filled; outline for neutral/muted. // Auto-prepends a small icon for warn/good tones. function Pill({ children, tone = "neutral", style, icon = "auto" }) { const tones = { neutral: { color: C.ink, border: "1px solid " + C.ruleStrong, background: "transparent" }, filled: { color: C.bone, border: "1px solid " + C.ink, background: C.ink }, warn: { color: C.bone, border: "1px solid " + C.warn, background: C.warn }, good: { color: C.bone, border: "1px solid " + C.good, background: C.good }, muted: { color: C.muted, border: "1px solid " + C.rule, background: "transparent" }, }; const showIcon = icon === "auto" ? (tone === "warn" || tone === "good") : !!icon; return ( {showIcon && } {children} ); } // Toggle — for no-show, urgent etc. function Toggle({ on, onChange, label }) { return ( ); } // Plain horizontal rule function Hr({ tone = "default", style }) { return
; } // Section heading — caps eyebrow + italic title function SectionHead({ eyebrow, title, sub, right, style }) { return (
{eyebrow &&
{eyebrow}
}
{title}
{sub &&
{sub}
}
{right}
); } // Money helpers — integer dollars look natural; fractions get 2 decimals. const money = (n) => { const v = Number(n); const hasFrac = Math.round(v * 100) !== Math.round(v) * 100; return "$" + v.toLocaleString(undefined, { minimumFractionDigits: hasFrac ? 2 : 0, maximumFractionDigits: 2, }); }; return { C, Caps, SerifI, Btn, Field, Input, Stepper, Pill, Toggle, Hr, SectionHead, money }; })(); window.UI = UI;