- Time
- 2 minutes ago
- Client IP
- 203.0.113.42
- Path
- /to/quiet-sunset-4821
{\n \"event\": \"payment.succeeded\",\n \"amount\": 4200\n}diff --git a/.impeccable/config.json b/.impeccable/config.json new file mode 100644 index 0000000..91a2426 --- /dev/null +++ b/.impeccable/config.json @@ -0,0 +1,9 @@ +{ + "hook": { + "enabled": true, + "limits": { + "maxFindings": 5, + "maxChars": 8000 + } + } +} diff --git a/.impeccable/design.json b/.impeccable/design.json index 3fd676c..e3f430d 100644 --- a/.impeccable/design.json +++ b/.impeccable/design.json @@ -4,194 +4,136 @@ "title": "Design System: httphq", "extensions": { "colorMeta": { - "beacon-periwinkle": { + "mark": { "role": "primary", - "displayName": "Beacon Periwinkle", - "canonical": "oklch(63.2% 0.157 275.252)", - "tonalRamp": [ - "oklch(15% 0.157 275.252)", - "oklch(26% 0.157 275.252)", - "oklch(37% 0.157 275.252)", - "oklch(48% 0.157 275.252)", - "oklch(59% 0.157 275.252)", - "oklch(70% 0.157 275.252)", - "oklch(82% 0.157 275.252)", - "oklch(93% 0.157 275.252)" - ] + "displayName": "The Mark", + "canonical": "#707ee7", + "note": "The periwinkle diamond in the logo and favicon set. Origin of the brand ramp; too light to hold white text, so it is never a fill." }, - "signal-indigo": { + "brand-600": { "role": "primary", - "displayName": "Signal Indigo", - "canonical": "oklch(51.1% 0.262 276.966)", + "displayName": "Brand", + "canonical": "oklch(52% 0.157 275.25)", + "tonalRamp": [ + "oklch(33% 0.105 275.25)", + "oklch(39% 0.13 275.25)", + "oklch(46% 0.15 275.25)", + "oklch(52% 0.157 275.25)", + "oklch(57.5% 0.157 275.25)", + "oklch(70% 0.14 275.25)", + "oklch(87.5% 0.075 275.25)", + "oklch(96.5% 0.022 275.25)" + ], + "note": "The mark's hue and chroma, darkened until white text clears the contrast floor." + }, + "neutral-50": { + "role": "neutral", + "displayName": "Field", + "canonical": "oklch(98.4% 0.003 274)", "tonalRamp": [ - "oklch(25.7% 0.09 281.288)", - "oklch(35.9% 0.144 278.697)", - "oklch(39.8% 0.195 277.366)", - "oklch(45.7% 0.24 277.023)", - "oklch(51.1% 0.262 276.966)", - "oklch(67.3% 0.182 276.935)", - "oklch(87% 0.065 274.039)", - "oklch(96.2% 0.018 272.314)" + "oklch(12.8% 0.026 274)", + "oklch(20.6% 0.03 274)", + "oklch(27.8% 0.032 274)", + "oklch(37% 0.034 274)", + "oklch(44.4% 0.036 274)", + "oklch(70.2% 0.032 274)", + "oklch(92.8% 0.011 274)", + "oklch(98.4% 0.003 274)" ] }, - "ink-body": { + "neutral-800": { "role": "neutral", "displayName": "Body Ink", - "canonical": "oklch(27.9% 0.041 260.031)", + "canonical": "oklch(27.8% 0.032 274)", "tonalRamp": [ - "oklch(12.9% 0.042 264.695)", - "oklch(20.8% 0.042 265.755)", - "oklch(27.9% 0.041 260.031)", - "oklch(37.2% 0.044 257.287)", - "oklch(44.6% 0.043 257.281)", - "oklch(70.4% 0.04 256.788)", - "oklch(92.9% 0.013 255.508)", - "oklch(98.4% 0.003 247.858)" + "oklch(12.8% 0.026 274)", + "oklch(20.6% 0.03 274)", + "oklch(27.8% 0.032 274)", + "oklch(37% 0.034 274)", + "oklch(44.4% 0.036 274)", + "oklch(70.2% 0.032 274)", + "oklch(92.8% 0.011 274)", + "oklch(98.4% 0.003 274)" ] }, - "board-field": { - "role": "neutral", - "displayName": "Board Field", - "canonical": "oklch(98.4% 0.003 247.858)", + "danger-600": { + "role": "secondary", + "displayName": "Danger", + "canonical": "oklch(55% 0.185 19)", "tonalRamp": [ - "oklch(12.9% 0.042 264.695)", - "oklch(20.8% 0.042 265.755)", - "oklch(27.9% 0.041 260.031)", - "oklch(37.2% 0.044 257.287)", - "oklch(44.6% 0.043 257.281)", - "oklch(70.4% 0.04 256.788)", - "oklch(92.9% 0.013 255.508)", - "oklch(98.4% 0.003 247.858)" + "oklch(48% 0.165 19)", + "oklch(55% 0.185 19)", + "oklch(62% 0.19 19)", + "oklch(89% 0.07 19)", + "oklch(94% 0.04 19)", + "oklch(97% 0.02 19)" ] }, - "method-get-ink": { + "get-ink": { "role": "tertiary", - "displayName": "Instrument Blue", - "canonical": "oklch(48.8% 0.243 264.376)", - "tonalRamp": [ - "oklch(28.2% 0.091 267.935)", - "oklch(37.9% 0.146 265.522)", - "oklch(42.4% 0.199 265.638)", - "oklch(48.8% 0.243 264.376)", - "oklch(54.6% 0.245 262.881)", - "oklch(70.7% 0.165 254.624)", - "oklch(88.2% 0.059 254.128)", - "oklch(97% 0.014 254.604)" - ] + "displayName": "GET", + "canonical": "oklch(50% 0.155 255)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." }, - "method-post-ink": { + "post-ink": { "role": "tertiary", - "displayName": "Bench Emerald", - "canonical": "oklch(50.8% 0.118 165.612)", - "tonalRamp": [ - "oklch(26.2% 0.051 172.552)", - "oklch(37.8% 0.077 168.94)", - "oklch(43.2% 0.095 166.913)", - "oklch(50.8% 0.118 165.612)", - "oklch(59.6% 0.145 163.225)", - "oklch(76.5% 0.177 163.223)", - "oklch(90.5% 0.093 164.15)", - "oklch(97.9% 0.021 166.113)" - ] + "displayName": "POST", + "canonical": "oklch(50% 0.115 157)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." }, - "method-put-ink": { + "put-ink": { "role": "tertiary", - "displayName": "Signal Amber", - "canonical": "oklch(55.5% 0.163 48.998)", - "tonalRamp": [ - "oklch(27.9% 0.077 45.635)", - "oklch(41.4% 0.112 45.904)", - "oklch(47.3% 0.137 46.201)", - "oklch(55.5% 0.163 48.998)", - "oklch(66.6% 0.179 58.318)", - "oklch(82.8% 0.189 84.429)", - "oklch(92.4% 0.12 95.746)", - "oklch(98.7% 0.022 95.277)" - ] + "displayName": "PUT", + "canonical": "oklch(50% 0.135 62)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." }, - "method-patch-ink": { + "patch-ink": { "role": "tertiary", - "displayName": "Filament Violet", - "canonical": "oklch(49.1% 0.27 292.581)", - "tonalRamp": [ - "oklch(28.3% 0.141 291.089)", - "oklch(38% 0.189 293.745)", - "oklch(43.2% 0.232 292.759)", - "oklch(49.1% 0.27 292.581)", - "oklch(54.1% 0.281 293.009)", - "oklch(70.2% 0.183 293.541)", - "oklch(89.4% 0.057 293.283)", - "oklch(96.9% 0.016 293.756)" - ] + "displayName": "PATCH", + "canonical": "oklch(50% 0.165 308)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." }, - "method-delete-ink": { + "delete-ink": { "role": "tertiary", - "displayName": "Warning Rose", - "canonical": "oklch(51.4% 0.222 16.935)", - "tonalRamp": [ - "oklch(27.1% 0.105 12.094)", - "oklch(41% 0.159 10.272)", - "oklch(45.5% 0.188 13.697)", - "oklch(51.4% 0.222 16.935)", - "oklch(58.6% 0.253 17.585)", - "oklch(71.2% 0.194 13.428)", - "oklch(89.2% 0.058 10.001)", - "oklch(96.9% 0.015 12.422)" - ] + "displayName": "DELETE", + "canonical": "oklch(50% 0.17 19)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." }, - "method-options-ink": { + "options-ink": { "role": "tertiary", - "displayName": "Preflight Sky", - "canonical": "oklch(50% 0.134 242.749)", - "tonalRamp": [ - "oklch(29.3% 0.066 243.157)", - "oklch(39.1% 0.09 240.876)", - "oklch(44.3% 0.11 240.79)", - "oklch(50% 0.134 242.749)", - "oklch(58.8% 0.158 241.966)", - "oklch(74.6% 0.16 232.661)", - "oklch(90.1% 0.058 230.902)", - "oklch(97.7% 0.013 236.62)" - ] + "displayName": "OPTIONS", + "canonical": "oklch(50% 0.13 213)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." + }, + "head-ink": { + "role": "tertiary", + "displayName": "HEAD", + "canonical": "oklch(37% 0.034 274)", + "note": "Method badge ink, built at 50% lightness on a 97% wash of the same hue." }, "syntax-key": { "role": "tertiary", - "displayName": "Key Blue", + "displayName": "Syntax Key", "canonical": "#005cc5", - "note": "Vendored from the highlight.js GitHub light theme; no tonal ramp because the borrowed palette has no scale. Covers hljs-attr, hljs-number, hljs-literal, hljs-meta." + "note": "Vendored from the highlight.js GitHub theme, deliberately outside the OKLCH families." }, "syntax-string": { "role": "tertiary", - "displayName": "String Navy", + "displayName": "Syntax String", "canonical": "#032f62", - "note": "Vendored highlight.js theme colour for hljs-string. Not a system token; do not restyle in isolation." + "note": "Vendored from the highlight.js GitHub theme, deliberately outside the OKLCH families." }, "syntax-name": { "role": "tertiary", - "displayName": "Element Green", + "displayName": "Syntax Name", "canonical": "#22863a", - "note": "Vendored highlight.js theme colour for XML element names (hljs-name)." + "note": "Vendored from the highlight.js GitHub theme, deliberately outside the OKLCH families." }, "syntax-keyword": { "role": "tertiary", - "displayName": "Keyword Red", + "displayName": "Syntax Keyword", "canonical": "#d73a49", - "note": "Vendored highlight.js theme colour for hljs-keyword." - }, - "alert-rose": { - "role": "secondary", - "displayName": "Alert Rose", - "canonical": "oklch(58.6% 0.253 17.585)", - "tonalRamp": [ - "oklch(27.1% 0.105 12.094)", - "oklch(41% 0.159 10.272)", - "oklch(45.5% 0.188 13.697)", - "oklch(51.4% 0.222 16.935)", - "oklch(58.6% 0.253 17.585)", - "oklch(71.2% 0.194 13.428)", - "oklch(89.2% 0.058 10.001)", - "oklch(96.9% 0.015 12.422)" - ] + "note": "Vendored from the highlight.js GitHub theme, deliberately outside the OKLCH families." } }, "typographyMeta": { @@ -228,43 +170,27 @@ "purpose": "Every captured value: headers, paths, IPs, query strings, bodies, endpoint URL, request UUID." } }, - "shadows": [ - { - "name": "resting-whisper", - "value": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", - "purpose": "Every panel, card, disclosure and primary button at rest. Its only job is to stop white from looking pasted onto the slate field." - }, - { - "name": "hover-lift", - "value": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", - "purpose": "Defined but currently unused. Elevation change is reserved for surfaces that respond to the cursor; every card in the product is static content." - } - ], + "shadows": [], "motion": [ { "name": "default-transition", "value": "150ms cubic-bezier(0.4, 0, 0.2, 1)", - "purpose": "Every hover and focus state change: button fills, text-action colour shifts, card lift." + "purpose": "Hover and focus state changes." }, { "name": "disclosure-chevron", - "value": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", - "purpose": "The Send-a-test-request summary chevron rotates 180 degrees on open." + "value": "transform 150ms", + "purpose": "The send-panel chevron rotates on open." }, { "name": "waiting-ellipsis", "value": "1.2s steps(4, end) infinite", - "purpose": "The four-step CSS content animation in the waiting state. The only looping motion in the product." - }, - { - "name": "copy-confirm", - "value": "1500ms label swap", - "purpose": "Copy controls swap their label to Copied! and revert after 1.5s. Send status clears after 2s." + "purpose": "The only looping motion in the product." }, { "name": "reduced-motion", "value": "@media (prefers-reduced-motion: reduce)", - "purpose": "Transitions and the waiting ellipsis collapse to a resting state. All motion here is decorative, so removing it substitutes cleanly." + "purpose": "All motion is decorative, so removing it substitutes cleanly." } ], "breakpoints": [ @@ -278,127 +204,100 @@ { "name": "Primary Button", "kind": "button", - "refersTo": "button-primary", - "description": "The one indigo-filled action per view. Two sizes only: page action (1rem type) and in-panel (0.875rem).", - "html": "", - "css": ".ds-btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; background: oklch(51.1% 0.262 276.966); color: #fff; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 1rem; font-weight: 500; line-height: 1.5; padding: 0.75rem 1.5rem; border: none; border-radius: 0.375rem; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); cursor: pointer; transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1); } .ds-btn-primary:hover { background: oklch(58.5% 0.233 277.117); } .ds-btn-primary:focus-visible { outline: none; box-shadow: 0 0 0 2px #fff, 0 0 0 4px oklch(58.5% 0.233 277.117); } .ds-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }" + "refersTo": "btn-primary", + "description": "The one brand-filled action per view. Flat: no border, no shadow.", + "html": "", + "css": ".ds-btn { display:inline-flex; align-items:center; justify-content:center; gap:.375rem; border-radius:.375rem; font-family:ui-sans-serif,system-ui,sans-serif; font-size:.875rem; font-weight:500; padding:.375rem .75rem; border:1px solid transparent; cursor:pointer; transition:background-color 150ms cubic-bezier(.4,0,.2,1); } .ds-btn-primary { background:oklch(52% 0.157 275.25); color:#fff; padding:.5rem 1rem; } .ds-btn-primary:hover { background:oklch(57.5% 0.157 275.25); } .ds-btn-primary:focus-visible { outline:none; box-shadow:0 0 0 2px #fff, 0 0 0 4px oklch(57.5% 0.157 275.25); }" }, { "name": "Secondary Button", "kind": "button", - "refersTo": "button-secondary", - "description": "White fill with a control-edge stroke. Every non-primary action that still needs a button shape.", - "html": "", - "css": ".ds-btn-secondary { display: inline-flex; align-items: center; justify-content: center; gap: 0.375rem; background: #fff; color: oklch(37.2% 0.044 257.287); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 500; line-height: 1.429; padding: 0.5rem 0.75rem; border: 1px solid oklch(86.9% 0.022 252.894); border-radius: 0.375rem; cursor: pointer; transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1); } .ds-btn-secondary:hover { background: oklch(98.4% 0.003 247.858); } .ds-btn-secondary:focus-visible { outline: none; box-shadow: 0 0 0 2px oklch(58.5% 0.233 277.117); } .ds-btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }" + "refersTo": "btn-secondary", + "description": "White fill with a neutral seam. Hovers to neutral-100, never to the page field.", + "html": "", + "css": ".ds-btn { display:inline-flex; align-items:center; gap:.375rem; border-radius:.375rem; font-family:ui-sans-serif,system-ui,sans-serif; font-size:.875rem; font-weight:500; padding:.375rem .75rem; cursor:pointer; } .ds-btn-secondary { background:#fff; color:oklch(37% 0.034 274); border:1px solid oklch(86.6% 0.018 274); } .ds-btn-secondary:hover { background:oklch(96.6% 0.006 274); border-color:oklch(70.2% 0.032 274); }" }, { "name": "Destructive Button", "kind": "button", - "refersTo": "button-destructive", - "description": "Rose wash with a rose stroke. Reserved for actions that destroy captured data.", - "html": "", - "css": ".ds-btn-destructive { display: inline-flex; align-items: center; gap: 0.375rem; background: oklch(96.9% 0.015 12.422); color: oklch(51.4% 0.222 16.935); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 500; line-height: 1.429; padding: 0.375rem 0.75rem; border: 1px solid oklch(89.2% 0.058 10.001); border-radius: 0.375rem; cursor: pointer; transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1); } .ds-btn-destructive:hover { background: oklch(94.1% 0.03 12.58); } .ds-btn-destructive:focus-visible { outline: none; box-shadow: 0 0 0 2px oklch(64.5% 0.246 16.439); }" + "refersTo": "btn-danger", + "description": "One destructive variant, solid. A tinted one would share its fill with the danger surfaces it sits on.", + "html": "", + "css": ".ds-btn { display:inline-flex; align-items:center; gap:.375rem; border-radius:.375rem; font-family:ui-sans-serif,system-ui,sans-serif; font-size:.875rem; font-weight:500; padding:.375rem .75rem; cursor:pointer; } .ds-btn-danger { background:oklch(55% 0.185 19); color:#fff; border:1px solid transparent; } .ds-btn-danger:hover { background:oklch(48% 0.165 19); }" }, { - "name": "Text Action", - "kind": "button", - "refersTo": "button-secondary", - "description": "No fill, no border. Label ink resolving to signal indigo on hover: or alert rose when the action deletes.", - "html": "
", - "css": ".ds-action { display: inline-flex; align-items: center; gap: 0.25rem; background: none; border: none; padding: 0; color: oklch(55.4% 0.046 257.417); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; line-height: 1.429; cursor: pointer; transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1); } .ds-action:hover { color: oklch(51.1% 0.262 276.966); } .ds-action-danger:hover { color: oklch(58.6% 0.253 17.585); } .ds-action:focus-visible { outline: none; box-shadow: 0 0 0 2px oklch(58.5% 0.233 277.117); border-radius: 0.25rem; }" - }, - { - "name": "Labelled Input", - "kind": "input", - "refersTo": "input-field", - "description": "Uppercase field label above a white field with a control-edge stroke. Focus tightens to a 1px indigo ring plus a border shift.", - "html": "", - "css": ".ds-field { display: block; } .ds-field-label { display: block; margin-bottom: 0.25rem; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.75rem; font-weight: 500; line-height: 1.333; letter-spacing: 0.025em; text-transform: uppercase; color: oklch(55.4% 0.046 257.417); } .ds-input { display: block; width: 100%; box-sizing: border-box; background: #fff; color: oklch(27.9% 0.041 260.031); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; line-height: 1.429; padding: 0.5rem 0.75rem; border: 1px solid oklch(86.9% 0.022 252.894); border-radius: 0.375rem; } .ds-input-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; } .ds-input::placeholder { color: oklch(70.4% 0.04 256.788); } .ds-input:focus { outline: none; border-color: oklch(58.5% 0.233 277.117); box-shadow: 0 0 0 1px oklch(67.3% 0.182 276.935); }" - }, - { - "name": "Select", + "name": "Field", "kind": "input", - "refersTo": "input-field", - "description": "Native select with appearance stripped and a slate chevron inlined as a data-URI, 1.1em at 0.5rem from the right.", - "html": "", - "css": ".ds-select { appearance: none; -webkit-appearance: none; background-color: #fff; background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%2364748b'%3E%3Cpath fill-rule='evenodd' d='M5.23 7.21a.75.75 0 011.06.02L10 11.083l3.71-3.853a.75.75 0 111.08 1.04l-4.25 4.41a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z' clip-rule='evenodd'/%3E%3C/svg%3E\"); background-repeat: no-repeat; background-position: right 0.5rem center; background-size: 1.1em; color: oklch(27.9% 0.041 260.031); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; line-height: 1.429; padding: 0.5rem 2rem 0.5rem 0.75rem; border: 1px solid oklch(86.9% 0.022 252.894); border-radius: 0.375rem; cursor: pointer; } .ds-select:focus { outline: none; border-color: oklch(58.5% 0.233 277.117); box-shadow: 0 0 0 1px oklch(67.3% 0.182 276.935); }" + "refersTo": "field", + "description": "One input component. Fields taking HTTP syntax add a mono variant; nothing else differs between forms.", + "html": "", + "css": ".ds-field-label { display:block; font-family:ui-sans-serif,system-ui,sans-serif; font-size:.75rem; font-weight:500; letter-spacing:.025em; text-transform:uppercase; color:oklch(55.2% 0.038 274); margin-bottom:.5rem; } .ds-field { display:block; width:100%; box-sizing:border-box; background:#fff; color:oklch(27.8% 0.032 274); font-family:ui-sans-serif,system-ui,sans-serif; font-size:.875rem; padding:.5rem .75rem; border:1px solid oklch(86.6% 0.018 274); border-radius:.375rem; } .ds-field-mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; } .ds-field:focus-visible { outline:none; border-color:oklch(57.5% 0.157 275.25); box-shadow:0 0 0 1px oklch(70% 0.14 275.25); }" }, { "name": "Method Badge", "kind": "chip", - "refersTo": "method-badge", - "description": "The signature component. Seven wash-and-ink pairs; the only saturated colour in the request stream. Unknown methods fall back to the HEAD pair.", - "html": "{\n \"event\": \"payment.succeeded\",\n \"amount\": 4200\n}http://httphq.com/to/quiet-sunset-4821{\n \"type\": \"payment_intent.succeeded\",\n \"amount\": 4200,\n \"livemode\": false\n}
None
Waiting for requests
Requests sent to your URL will appear here in real time.
{\n \"type\": \"payment_intent.succeeded\"\n}", + "css": ".ds-code-block { margin:0; background:oklch(98.4% 0.003 274); border:1px solid oklch(92.8% 0.011 274); border-radius:.25rem; padding:.75rem; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.75rem; color:oklch(27.8% 0.032 274); overflow:auto; } .ds-punct { color:oklch(27.8% 0.032 274); } .ds-key { color:#005cc5; } .ds-string { color:#032f62; }" } ], "narrative": { "northStar": "The Arrivals Board", - "overview": "httphq is a board you watch, not a console you operate. Requests land at the top of the stream, get colour-coded by class, and expire. The whole visual system is built so a developer working in another window can glance over and read the board, status first, detail on approach, then go back to what they were doing. Everything that isn't the arriving traffic is chassis: a slate field, white panels, hairline seams, and exactly one indigo control per view.\n\nThe register is precise, quiet and fast. Restraint here is not minimalism as a style choice; it is what makes the board readable. The interface never claims more than it can prove, never decorates a value it captured, and never puts a second thing in colour next to a method badge. Type is system-native and loads instantly, there is no webfont anywhere, and that is a feature of a tool whose entire promise is being usable within seconds of arrival.\n\nFour looks are rejected outright, and all four are confirmed prohibitions rather than taste: marketing-SaaS gloss (gradient meshes, floating mockups, testimonial carousels), the dense enterprise console (dark chrome, packed toolbars, panels nested in panels), terminal cosplay (green-on-black, ASCII framing, faux-CRT effects), and playful dev-tool mascotry (cartoon characters, blob illustrations, jokey empty states). Monospace appears throughout, but as evidence handling, never as costume.", + "overview": "httphq is a board you watch, not a console you operate. Requests land at the\ntop of the stream, get colour-coded by class, and expire. The whole visual\nsystem is built so a developer working in another window can glance over and\nread the board, status first, detail on approach, then go back to what they\nwere doing. Everything that isn't the arriving traffic is chassis: a slate\nfield, white panels, hairline seams, and exactly one indigo control per view.\n\nThe register is precise, quiet and fast. Restraint here is not minimalism as a\nstyle choice; it is what makes the board readable. The interface never claims\nmore than it can prove, never decorates a value it captured, and never puts a\nsecond thing in colour next to a method badge. Type is system-native and\nloads instantly: there is no webfont anywhere, and that is a feature of a tool\nwhose entire promise is being usable within seconds of arrival.\n\nFour looks are rejected outright, and all four are confirmed prohibitions\nrather than taste: marketing-SaaS gloss (gradient meshes, floating mockups,\ntestimonial carousels), the dense enterprise console (dark chrome, packed\ntoolbars, panels nested in panels), terminal cosplay (green-on-black, ASCII\nframing, faux-CRT effects), and playful dev-tool mascotry (cartoon characters,\nblob illustrations, jokey empty states). Monospace appears throughout, but as\nevidence handling: never as costume.", "keyCharacteristics": [ - "A slate-50 field with white panels; hairline seams, not shadows, make the edges", + "A neutral-50 field with white panels; hairline seams and no shadows anywhere make the edges", "One indigo action per view; every other control is a slate outline or bare text", "Full-spectrum colour is spent entirely on the seven HTTP-method badges", "Everything the client sent is monospace; everything httphq says is sans", "A single breakpoint (40rem) governs the entire responsive system", - "Liveness is signalled in the browser chrome, tab title count, favicon dot, not by animating the page" + "Liveness is stated, never performed: a connection indicator on the page, a count in the tab title, a dot on the favicon. The page body never flashes, animates or auto-scrolls to claim attention" ], "rules": [ { - "name": "The Borrowed Palette Rule", - "body": "The four syntax colours are a vendored theme, not system tokens. Never restyle individual hljs-* classes to bring them closer to the slate-and-indigo world: either swap the whole theme or leave it alone. A half-retinted syntax palette reads as a bug in the highlighter.", + "name": "The Chroma Rule", + "body": "The accent carries the mark's chroma of 0.157, not more. A higher-chroma violet reads as a generic framework purple and fights the neutral chassis; the softness is the identity, and lightness is the only axis that moves when contrast demands it.", "section": "colors" }, { "name": "The Board Rule", - "body": "Full-spectrum colour belongs to the method badge and nothing else. If a new element wants blue, emerald, amber, violet or sky, the answer is no: that vocabulary means \"this is the class of traffic that arrived\", and every additional user dilutes the only colour-coding on the page.", + "body": "Full-spectrum colour belongs to the method badge and nothing else. If a new element wants blue, green, amber or violet, the answer is no: that vocabulary means \"this is the class of traffic that arrived\", and every additional user dilutes the only colour-coding on the page.", "section": "colors" }, { - "name": "The One Indigo Rule", - "body": "At most one indigo-filled control exists per view. On the home page it is Create endpoint; on the endpoint page it is Send; on contact it is Send message. Everything else that is actionable is a slate outline or bare label ink that turns indigo on hover.", + "name": "The One Accent Rule", + "body": "At most one brand-filled control exists per view. Everything else that is actionable is a neutral outline or bare text that turns brand on hover.", "section": "colors" }, { "name": "The Wash-and-Ink Rule", - "body": "Coloured chips are always a ~97%-lightness wash carrying a ~50%-lightness ink of the same hue. There are no saturated fills with white text anywhere except the single primary button.", + "body": "Coloured chips are a 97%-lightness wash carrying a 50%-lightness ink of the same hue. There are no saturated fills with white text anywhere except the single primary button.", + "section": "colors" + }, + { + "name": "The Danger Is Removal Rule", + "body": "Danger means something is about to be destroyed: the delete controls and the DELETE badge, which share the hue deliberately. Its one sanctioned exception is the unread dot on the favicon. It is never used for form validation or generic error text.", "section": "colors" }, { - "name": "The Rose Is Removal Rule", - "body": "Rose means something is about to be destroyed: the Delete all button, the per-request delete hover, the DELETE badge. Its one sanctioned exception is the unread dot painted onto the favicon, where it means \"traffic landed while you were away\". Rose is never used for form validation or generic error text.", + "name": "The Borrowed Palette Rule", + "body": "The four syntax-highlighting colours are a vendored theme, not system tokens. Never restyle individual `hljs-*` classes to bring them closer to this palette: either swap the whole theme or leave it alone. A half-retinted syntax palette reads as a bug in the highlighter.", "section": "colors" }, { @@ -412,13 +311,18 @@ "section": "typography" }, { - "name": "The Hairline-First Rule", - "body": "If a surface needs to be distinguished, give it a hairline seam before you give it a shadow. A panel with no border and a heavier shadow is wrong in this system even when it looks fine in isolation.", + "name": "The One Edge Rule", + "body": "A surface declares its edge once, with a seam. If a new component seems to need a shadow, the real problem is that it is not distinct from what surrounds it, and the fix is tone or spacing.", + "section": "elevation" + }, + { + "name": "The No-Lift Rule", + "body": "Nothing changes elevation on hover, because nothing has elevation to change. Hover is expressed in colour: a fill steps lighter, a border steps darker, bare text turns brand.", "section": "elevation" }, { - "name": "The Shadow-Is-A-Response Rule", - "body": "Nothing above the resting whisper exists at rest. shadow-md appears only as a hover response; there is no shadow-lg or above anywhere, and adding one would break the flatness the board depends on.", + "name": "The Never-Hover-To-The-Field Rule", + "body": "A white surface must never hover to neutral-50, because that is the page it sits on: the control dissolves into the background at the exact moment it should respond. White surfaces hover to neutral-100 and darken their seam to neutral-400. With no shadow to fall back on, the colour step is the whole signal.", "section": "elevation" }, { @@ -433,27 +337,27 @@ } ], "dos": [ - "Do put every new surface on the board field with a panel-white fill, a 1px hairline seam, and the resting whisper shadow. That trio is the system's default surface.", - "Do set anything the client sent in monospace at 0.75rem, and anything httphq says in sans.", - "Do give a new region an uppercase 0.75rem/500/+0.025em label ink heading, and separate its rows with hairline-faint rules instead of nesting another bordered box.", - "Do pair outline: none with a visible focus-visible ring every single time: 2px signal-indigo-lit plus a white offset ring on buttons, 1px focus-indigo plus a border shift on fields.", - "Do take the radius from the category: 0.25rem for data, 0.375rem for controls, 0.5rem for panels.", - "Do design to the 40rem breakpoint alone, stacking below it and going horizontal above it.", - "Do keep icons at 24×24 viewBox, stroke-width 2, fill none, aria-hidden, beside a text label.", - "Do signal background activity in the tab title and favicon rather than in the page." + "**Do** put every new surface on the neutral-50 field with a white fill and a 1px neutral-200 seam. That pair is the system's default surface, and it carries no shadow.", + "**Do** set anything the client sent in monospace at 0.75rem, and anything httphq says in sans.", + "**Do** give a new region an uppercase 0.75rem/500/+0.025em neutral-500 heading, and separate its rows with neutral-100 rules instead of nesting another bordered box.", + "**Do** pair `outline: none` with a visible `focus-visible` ring every single time: 2px brand-500 plus a white offset ring on buttons, 1px brand-400 plus a border shift on fields.", + "**Do** take the radius from the category: 0.25rem for data, 0.375rem for controls, 0.5rem for panels.", + "**Do** design to the 40rem breakpoint alone, stacking below it and going horizontal above it.", + "**Do** keep icons at 24×24 viewBox, `stroke-width=\"2\"`, `fill=\"none\"`, `aria-hidden`, beside a text label.", + "**Do** state connection and background activity plainly: an indicator on the page, a count in the tab title, a dot on the favicon. Never by animating the body." ], "donts": [ - "Don't spend blue, emerald, amber, violet or sky on anything but a method badge. That spectrum is the traffic classification and nothing else.", - "Don't add a second indigo-filled button to a view. One primary action per screen, every other control outlined or bare.", - "Don't introduce a saturated fill with white text; coloured chips are a ~97% wash with a ~50% ink of the same hue.", - "Don't use rose for validation errors or generic failure text: rose means removal, plus the unread favicon dot.", - "Don't reach past the hover lift for elevation. There is no shadow-lg in this system, and a borderless card with a bigger shadow is wrong here even when it looks fine alone.", - "Don't add a webfont. The system stack is a performance commitment on a tool whose promise is a working URL in seconds.", - "Don't add a second breakpoint. If a layout can't resolve with sm alone, simplify the layout.", - "Don't uppercase anything that isn't a field label or a method badge.", - "Don't borrow the dashed border for disabled, errored, or drop-target surfaces; it means \"correctly empty, expecting content\".", - "Don't animate the page to announce arrivals, auto-scroll the stream, or add a second looping animation beside the waiting ellipsis.", - "Don't half-migrate the accent toward the mark's periwinkle. Until that convergence is done deliberately across the mark, favicons and UI together, signal-indigo remains the interface accent everywhere." + "**Don't** spend a method hue on anything but a method badge. The seven wash-and-ink pairs are the traffic classification and nothing else.", + "**Don't** add a second brand-filled button to a view. One primary action per screen; everything else is an outlined, bare, or destructive control.", + "**Don't** introduce a saturated fill with white text beyond the two that exist: the brand primary and the destructive button. Coloured chips are a 97% wash carrying a 50% ink of the same hue, never a fill.", + "**Don't** use danger for validation errors or generic failure text: it means removal, plus the unread favicon dot.", + "**Don't** add a shadow. There are none in this system, and a border under a shadow is two mechanisms declaring one edge.", + "**Don't** add a webfont. The system stack is a performance commitment on a tool whose promise is a working URL in seconds.", + "**Don't** add a second breakpoint. If a layout can't resolve with `sm` alone, simplify the layout.", + "**Don't** uppercase anything that isn't a field label or a method badge.", + "**Don't** borrow the dashed border for disabled, errored, or drop-target surfaces; it means \"correctly empty, expecting content\".", + "**Don't** animate the page to announce arrivals, auto-scroll the stream, or add a second looping animation beside the waiting ellipsis.", + "**Don't** reach for a stock framework colour. Tailwind's defaults are cleared in `theme.css`, so a stock utility silently renders nothing; every colour comes from the authored ramps." ] } } diff --git a/DESIGN.md b/DESIGN.md index dd7de42..a7102d2 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,45 +1,57 @@ --- name: httphq -description: "A live arrivals board for HTTP requests. Neutral chassis, one indigo control, color reserved for classifying traffic." +description: "A live arrivals board for HTTP requests. Neutral chassis, one accent drawn from the mark, colour reserved for classifying traffic." colors: - beacon-periwinkle: "#707ee7" - signal-indigo: "oklch(51.1% 0.262 276.966)" - signal-indigo-lit: "oklch(58.5% 0.233 277.117)" - signal-indigo-deep: "oklch(45.7% 0.24 277.023)" - signal-indigo-wash: "oklch(96.2% 0.018 272.314)" - focus-indigo: "oklch(67.3% 0.182 276.935)" - panel-white: "#ffffff" - board-field: "oklch(98.4% 0.003 247.858)" - hairline-faint: "oklch(96.8% 0.007 247.896)" - hairline: "oklch(92.9% 0.013 255.508)" - edge-control: "oklch(86.9% 0.022 252.894)" - ink-quiet: "oklch(70.4% 0.04 256.788)" - ink-label: "oklch(55.4% 0.046 257.417)" - ink-prose: "oklch(44.6% 0.043 257.281)" - ink-control: "oklch(37.2% 0.044 257.287)" - ink-body: "oklch(27.9% 0.041 260.031)" - ink-strong: "oklch(20.8% 0.042 265.755)" - method-get-ink: "oklch(48.8% 0.243 264.376)" - method-get-wash: "oklch(97% 0.014 254.604)" - method-post-ink: "oklch(50.8% 0.118 165.612)" - method-post-wash: "oklch(97.9% 0.021 166.113)" - method-put-ink: "oklch(55.5% 0.163 48.998)" - method-put-wash: "oklch(98.7% 0.022 95.277)" - method-patch-ink: "oklch(49.1% 0.27 292.581)" - method-patch-wash: "oklch(96.9% 0.016 293.756)" - method-delete-ink: "oklch(51.4% 0.222 16.935)" - method-delete-wash: "oklch(96.9% 0.015 12.422)" - method-options-ink: "oklch(50% 0.134 242.749)" - method-options-wash: "oklch(97.7% 0.013 236.62)" - alert-rose: "oklch(58.6% 0.253 17.585)" - alert-rose-deep: "oklch(51.4% 0.222 16.935)" - alert-rose-edge: "oklch(89.2% 0.058 10.001)" - alert-rose-wash: "oklch(96.9% 0.015 12.422)" - alert-rose-wash-hover: "oklch(94.1% 0.03 12.58)" + white: "#ffffff" + black: "#000000" + mark: "#707ee7" + brand-50: "oklch(96.5% 0.022 275.25)" + brand-100: "oklch(93.2% 0.042 275.25)" + brand-200: "oklch(87.5% 0.075 275.25)" + brand-300: "oklch(79.5% 0.11 275.25)" + brand-400: "oklch(70% 0.14 275.25)" + brand-500: "oklch(57.5% 0.157 275.25)" + brand-600: "oklch(52% 0.157 275.25)" + brand-700: "oklch(46% 0.15 275.25)" + brand-800: "oklch(39% 0.13 275.25)" + brand-900: "oklch(33% 0.105 275.25)" + neutral-50: "oklch(98.4% 0.003 274)" + neutral-100: "oklch(96.6% 0.006 274)" + neutral-200: "oklch(92.8% 0.011 274)" + neutral-300: "oklch(86.6% 0.018 274)" + neutral-400: "oklch(70.2% 0.032 274)" + neutral-500: "oklch(55.2% 0.038 274)" + neutral-600: "oklch(44.4% 0.036 274)" + neutral-700: "oklch(37% 0.034 274)" + neutral-800: "oklch(27.8% 0.032 274)" + neutral-900: "oklch(20.6% 0.03 274)" + neutral-950: "oklch(12.8% 0.026 274)" + get-ink: "oklch(50% 0.155 255)" + get-wash: "oklch(97% 0.025 255)" + post-ink: "oklch(50% 0.115 157)" + post-wash: "oklch(97% 0.018 157)" + put-ink: "oklch(50% 0.135 62)" + put-wash: "oklch(97% 0.022 62)" + patch-ink: "oklch(50% 0.165 308)" + patch-wash: "oklch(97% 0.026 308)" + delete-ink: "oklch(50% 0.17 19)" + delete-wash: "oklch(97% 0.027 19)" + options-ink: "oklch(50% 0.13 213)" + options-wash: "oklch(97% 0.021 213)" + head-ink: "oklch(37% 0.034 274)" + head-wash: "oklch(96.6% 0.006 274)" + danger-50: "oklch(97% 0.02 19)" + danger-100: "oklch(94% 0.04 19)" + danger-200: "oklch(89% 0.07 19)" + danger-500: "oklch(62% 0.19 19)" + danger-600: "oklch(55% 0.185 19)" + danger-700: "oklch(48% 0.165 19)" syntax-key: "#005cc5" syntax-string: "#032f62" syntax-name: "#22863a" syntax-keyword: "#d73a49" + live: "oklch(62% 0.145 157)" + pending: "oklch(68% 0.145 62)" typography: display: fontFamily: "ui-sans-serif, system-ui, sans-serif" @@ -98,66 +110,73 @@ spacing: "8": "2rem" "12": "3rem" components: - button-primary: - backgroundColor: "{colors.signal-indigo}" - textColor: "{colors.panel-white}" + btn: + backgroundColor: "{colors.white}" + textColor: "{colors.neutral-700}" typography: "{typography.body}" rounded: "{rounded.md}" - padding: "0.75rem 1.5rem" - button-primary-hover: - backgroundColor: "{colors.signal-indigo-lit}" - button-primary-compact: - backgroundColor: "{colors.signal-indigo}" - textColor: "{colors.panel-white}" + padding: "0.375rem 0.75rem" + btn-primary: + backgroundColor: "{colors.brand-600}" + textColor: "{colors.white}" typography: "{typography.body}" rounded: "{rounded.md}" padding: "0.5rem 1rem" - button-secondary: - backgroundColor: "{colors.panel-white}" - textColor: "{colors.ink-control}" + btn-primary-hover: + backgroundColor: "{colors.brand-500}" + btn-lg: + backgroundColor: "{colors.brand-600}" + textColor: "{colors.white}" + typography: "{typography.lede}" + rounded: "{rounded.md}" + padding: "0.75rem 1.5rem" + btn-secondary: + backgroundColor: "{colors.white}" + textColor: "{colors.neutral-700}" typography: "{typography.body}" rounded: "{rounded.md}" - padding: "0.5rem 0.75rem" - button-secondary-hover: - backgroundColor: "{colors.board-field}" - button-destructive: - backgroundColor: "{colors.alert-rose-wash}" - textColor: "{colors.alert-rose-deep}" + padding: "0.375rem 0.75rem" + btn-secondary-hover: + backgroundColor: "{colors.neutral-100}" + btn-danger: + backgroundColor: "{colors.danger-600}" + textColor: "{colors.white}" typography: "{typography.body}" rounded: "{rounded.md}" padding: "0.375rem 0.75rem" - button-destructive-hover: - backgroundColor: "{colors.alert-rose-wash-hover}" - input-field: - backgroundColor: "{colors.panel-white}" - textColor: "{colors.ink-body}" + btn-danger-hover: + backgroundColor: "{colors.danger-700}" + btn-inline: + textColor: "{colors.neutral-500}" + typography: "{typography.body}" + rounded: "{rounded.md}" + padding: "0.25rem 0.25rem" + field: + backgroundColor: "{colors.white}" + textColor: "{colors.neutral-800}" typography: "{typography.body}" rounded: "{rounded.md}" padding: "0.5rem 0.75rem" + field-label: + textColor: "{colors.neutral-500}" + typography: "{typography.label}" panel: - backgroundColor: "{colors.panel-white}" + backgroundColor: "{colors.white}" rounded: "{rounded.lg}" padding: "1.25rem" - code-well: - backgroundColor: "{colors.board-field}" - textColor: "{colors.ink-body}" + code-block: + backgroundColor: "{colors.neutral-50}" + textColor: "{colors.neutral-800}" typography: "{typography.mono}" rounded: "{rounded.sm}" padding: "0.75rem" - method-badge: + badge: typography: "{typography.badge}" rounded: "{rounded.sm}" padding: "0.125rem 0.5rem" - icon-tile: - backgroundColor: "{colors.signal-indigo-wash}" - textColor: "{colors.signal-indigo}" - rounded: "{rounded.md}" - width: "2.25rem" - height: "2.25rem" - empty-state: - backgroundColor: "{colors.panel-white}" - textColor: "{colors.ink-label}" - rounded: "{rounded.lg}" + empty-value: + textColor: "{colors.neutral-500}" + typography: "{typography.mono}" padding: "3rem 1rem" --- @@ -191,99 +210,101 @@ evidence handling: never as costume. **Key Characteristics:** -- A slate-50 field with white panels; hairline seams, not shadows, make the edges +- A neutral-50 field with white panels; hairline seams and no shadows anywhere make the edges - One indigo action per view; every other control is a slate outline or bare text - Full-spectrum colour is spent entirely on the seven HTTP-method badges - Everything the client sent is monospace; everything httphq says is sans - A single breakpoint (40rem) governs the entire responsive system -- Liveness is signalled in the browser chrome, tab title count, favicon dot, not by animating the page +- Liveness is stated, never performed: a connection indicator on the page, a count in the tab title, a dot on the favicon. The page body never flashes, animates or auto-scrolls to claim attention ## Colors -A near-neutral chassis of eleven slate steps, one indigo accent that carries -every action, and a seven-hue spectrum spent exclusively on classifying -traffic. +Authored in OKLCH, not taken from a framework's defaults. Three families, built +so they read as one system: a neutral chassis, one accent drawn from the mark, +and a seven-hue spectrum spent only on classifying traffic. ### Primary -- **Beacon Periwinkle** (`#707ee7`): the brand primary, carried by the diamond - mark in `public/logo.svg` and the favicon set. It is lighter and considerably - softer than the interface accent below. **Recorded intent:** this is the true - brand primary and the interface accent should converge toward it in a future - pass. Until that happens the two coexist and neither moves: do not - half-migrate individual components. -- **Signal Indigo** (`oklch(51.1% 0.262 276.966)`): the current interface - accent. It fills the one primary button per view, sets the endpoint URL in - its deeper step, tints the use-case icon tiles in its lightest wash, and is - the hover colour every quiet control resolves to. Nothing else is indigo. +- **The Mark** (`#707ee7`): the periwinkle diamond in `public/logo.svg` and the favicon set. It is the origin of the whole accent family and appears unchanged wherever the logo appears. +- **Brand 600** (`oklch(52% 0.157 275.25)`): the accent that fills the one primary control per view. It is the mark's own hue and chroma, darkened until white text clears the contrast floor: the mark itself measures 3.6:1 against white and cannot hold a label. Brand 500 is the hover step and the focus ring; brand 700 sets the endpoint URL; brand 50 tints the icon tiles; brand 400 is the quieter ring on form fields. + +**The Chroma Rule.** The accent carries the mark's chroma of 0.157, not more. A +higher-chroma violet reads as a generic framework purple and fights the neutral +chassis; the softness is the identity, and lightness is the only axis that moves +when contrast demands it. ### Tertiary -The method spectrum. Seven hues, each existing only as a wash-and-ink pair on a -badge, and defined in `public/endpoint.js` rather than in markup: +The method spectrum. Conventional meanings, because GET-blue and POST-green and +DELETE-red are near-universal in HTTP tooling, but built to one construction +rather than inherited: **every ink sits at 50% lightness and every wash at 97%**, +with chroma tuned per hue so the seven carry equal visual weight. -- **GET: Instrument Blue** (ink `oklch(48.8% 0.243 264.376)` on wash `oklch(97% 0.014 254.604)`): reads, the default arrival. -- **POST: Bench Emerald** (ink `oklch(50.8% 0.118 165.612)` on wash `oklch(97.9% 0.021 166.113)`): creates, and the most common arrival on a webhook endpoint. -- **PUT: Signal Amber** (ink `oklch(55.5% 0.163 48.998)` on wash `oklch(98.7% 0.022 95.277)`): replaces. -- **PATCH: Filament Violet** (ink `oklch(49.1% 0.27 292.581)` on wash `oklch(96.9% 0.016 293.756)`): modifies. -- **DELETE: Warning Rose** (ink `oklch(51.4% 0.222 16.935)` on wash `oklch(96.9% 0.015 12.422)`): removes; shares its hue with the destructive controls, deliberately. -- **HEAD: Neutral Slate** (ink `oklch(37.2% 0.044 257.287)` on wash `oklch(96.8% 0.007 247.896)`): metadata only, so it gets no hue at all. -- **OPTIONS: Preflight Sky** (ink `oklch(50% 0.134 242.749)` on wash `oklch(97.7% 0.013 236.62)`): negotiation, usually a browser's preflight. +- **GET** ink `oklch(50% 0.155 255)` on wash `oklch(97% 0.025 255)` +- **POST** ink `oklch(50% 0.115 157)` on wash `oklch(97% 0.018 157)` +- **PUT** ink `oklch(50% 0.135 62)` on wash `oklch(97% 0.022 62)` +- **PATCH** ink `oklch(50% 0.165 308)` on wash `oklch(97% 0.026 308)` +- **DELETE** ink `oklch(50% 0.17 19)` on wash `oklch(97% 0.027 19)` +- **OPTIONS** ink `oklch(50% 0.13 213)` on wash `oklch(97% 0.021 213)` +- **HEAD** carries no hue at all: it is metadata, so it takes the neutral ink and wash. -Any method outside this set falls back to the HEAD pair. +PATCH sits at 308 and GET at 255 deliberately, pushed away from the accent's 275 +so a badge is never mistaken for an action. Every pair measures between 4.97:1 +and 9.48:1. ### Neutral -- **Board Field** (`oklch(98.4% 0.003 247.858)`): the page ground the panels sit on, and, reused deliberately, the fill of every code well and the endpoint URL chip, so raw data reads as recessed into the board. -- **Panel White** (`#ffffff`): every card, disclosure, input and empty state. -- **Hairline** (`oklch(92.9% 0.013 255.508)`): the 1px seam around panels, code wells and the sticky filter rail. The primary edge in the system. -- **Hairline Faint** (`oklch(96.8% 0.007 247.896)`): the internal rules between header rows and detail rows, one step quieter than a panel edge so nested structure never out-shouts the container. -- **Control Edge** (`oklch(86.9% 0.022 252.894)`): the visible stroke on inputs, selects, and secondary buttons; also the dashed edge of the waiting state. -- **Quiet Ink** (`oklch(70.4% 0.04 256.788)`): input placeholders only. It is deliberately not used for an absent value: the italic "None" that marks one measured 2.63:1 on white in this step, so it carries Label Ink instead. -- **Label Ink** (`oklch(55.4% 0.046 257.417)`): field labels, meta rows, timestamps, and the resting colour of every text-only control. -- **Prose Ink** (`oklch(44.6% 0.043 257.281)`): descriptive sentences on the home and contact pages. -- **Control Ink** (`oklch(37.2% 0.044 257.287)`): secondary button labels, the disclosure summary, and the wordmark. -- **Body Ink** (`oklch(27.9% 0.041 260.031)`): default document text. -- **Strong Ink** (`oklch(20.8% 0.042 265.755)`): headings and card titles. +The chassis sits on the mark's hue at very low chroma, which is why the greys +read as belonging to the brand rather than as a generic blue-grey. + +- **Neutral 50** (`oklch(98.4% 0.003 274)`): the page field, and the fill of every code well. +- **White** (`#ffffff`): every card, disclosure and input. +- **Neutral 200** (`oklch(92.8% 0.011 274)`): the 1px seam around panels and wells. The primary edge in the system. +- **Neutral 100**: internal rules between rows, one step quieter than a panel edge. +- **Neutral 300**: the stroke on inputs and secondary buttons, and the dashed edge of the waiting state. +- **Neutral 400**: input placeholders only. +- **Neutral 500**: field labels, meta rows, and the italic "None" for an absent value. +- **Neutral 600**: descriptive prose. +- **Neutral 700**: secondary button labels, the disclosure summary, and the wordmark. +- **Neutral 800**: default document text. +- **Neutral 900**: headings and card titles. -### Syntax Highlighting +### Named Rules -The one part of the palette httphq does not author. Captured JSON, XML and -multipart bodies are highlighted by the pinned highlight.js GitHub light theme -(`@highlightjs/cdn-assets@11.10.0/styles/github.min.css`), which ships plain -sRGB hex outside the OKLCH system above. Verified against rendered captures: +**The Board Rule.** Full-spectrum colour belongs to the method badge and nothing +else. If a new element wants blue, green, amber or violet, the answer is no: +that vocabulary means "this is the class of traffic that arrived", and every +additional user dilutes the only colour-coding on the page. -- **Key Blue** (`#005cc5`): object keys, numbers, booleans and the XML prolog: `hljs-attr`, `hljs-number`, `hljs-literal`, `hljs-meta`. -- **String Navy** (`#032f62`): every quoted string value: `hljs-string`. -- **Element Green** (`#22863a`): XML element names: `hljs-name`. -- **Keyword Red** (`#d73a49`): language keywords: `hljs-keyword`. -- Punctuation, braces and tag brackets carry no colour of their own; `hljs-punctuation` and `hljs-tag` inherit Body Ink, which is what keeps a highlighted payload from turning into confetti. +**The One Accent Rule.** At most one brand-filled control exists per view. +Everything else that is actionable is a neutral outline or bare text that turns +brand on hover. -### Named Rules +**The Wash-and-Ink Rule.** Coloured chips are a 97%-lightness wash carrying a +50%-lightness ink of the same hue. There are no saturated fills with white text +anywhere except the single primary button. -**The Borrowed Palette Rule.** The four syntax colours are a vendored theme, not -system tokens. Never restyle individual `hljs-*` classes to bring them closer to -the slate-and-indigo world: either swap the whole theme or leave it alone. A -half-retinted syntax palette reads as a bug in the highlighter. +**The Danger Is Removal Rule.** Danger means something is about to be destroyed: +the delete controls and the DELETE badge, which share the hue deliberately. Its +one sanctioned exception is the unread dot on the favicon. It is never used for +form validation or generic error text. -**The Board Rule.** Full-spectrum colour belongs to the method badge and -nothing else. If a new element wants blue, emerald, amber, violet or sky, the -answer is no: that vocabulary means "this is the class of traffic that -arrived", and every additional user dilutes the only colour-coding on the page. +**The Borrowed Palette Rule.** The four syntax-highlighting colours are a +vendored theme, not system tokens. Never restyle individual `hljs-*` classes to +bring them closer to this palette: either swap the whole theme or leave it +alone. A half-retinted syntax palette reads as a bug in the highlighter. -**The One Indigo Rule.** At most one indigo-filled control exists per view. On -the home page it is _Create endpoint_; on the endpoint page it is _Send_; -on contact it is _Send message_. Everything else that is actionable is a slate -outline or bare label ink that turns indigo on hover. +### Syntax Highlighting -**The Wash-and-Ink Rule.** Coloured chips are always a ~97%-lightness wash -carrying a ~50%-lightness ink of the same hue. There are no saturated fills -with white text anywhere except the single primary button. +The one part of the palette httphq does not author. Captured JSON, XML and +multipart bodies are highlighted by the pinned highlight.js GitHub light theme, +which ships plain sRGB hex outside the system above: -**The Rose Is Removal Rule.** Rose means something is about to be destroyed: the _Delete all_ button, the per-request delete hover, the DELETE badge. Its one -sanctioned exception is the unread dot painted onto the favicon, where it means -"traffic landed while you were away". Rose is never used for form validation or -generic error text. +- **Key Blue** (`#005cc5`): object keys, numbers, booleans and the XML prolog. +- **String Navy** (`#032f62`): quoted string values. +- **Element Green** (`#22863a`): XML element names. +- **Keyword Red** (`#d73a49`): language keywords. +- Punctuation and tag brackets inherit body ink, which is what keeps a highlighted payload from turning into confetti. ## Typography @@ -302,7 +323,7 @@ httphq speaks in sans, the captured traffic speaks in mono. - **Display** (600, 3rem desktop / 1.875rem below 40rem, line-height 1, -0.025em): the home hero headline. Appears once, on one page. - **Headline** (600, 1.875rem / 1.5rem below 40rem, line-height 1.2): page titles outside the hero, e.g. _Get in touch_. -- **Title** (500, 1rem): card headings and the disclosure summary. Medium weight, never semibold, because titles mark a region rather than competing with the display. +- **Title** (500, 1rem): card headings. Medium weight, never semibold, because titles mark a region rather than competing with the display. The disclosure summary is deliberately not a Title: it is a control, so it takes the 0.875rem control size even though it is marked up as a heading. - **Lede** (400, 1.125rem, line-height ~1.56): the one paragraph under the hero, capped at 36rem. - **Body** (400, 0.875rem, line-height ~1.43): the working size of the entire application chrome. Most of the product is set at 14px, not 16px. - **Label** (500, 0.75rem, +0.025em, uppercase): field labels: TIME, CLIENT IP, PATH, HEADERS, QUERY STRING, BODY, YOUR UNIQUE URL. @@ -339,6 +360,12 @@ filter bar become horizontal, and secondary button labels appear next to their icons. Design new surfaces to the same discipline: if a layout needs a second breakpoint, it is probably too complex for this product. +A label belongs to its field, so the gap above a field is smaller than the gap +below it: 0.5rem from label to control, 0.75rem from control to the note that +follows. Equal gaps make a label read as floating between two things rather than +naming one of them. Do not reach for `space-y-*` where that distinction matters; +it distributes one gap evenly and cannot express the grouping. + Vertical rhythm runs on the 0.25rem base scale, using 0.5 / 0.75 / 1 / 1.25 / 1.5 / 2 / 3rem steps. Panels carry 1rem of internal padding below the breakpoint and 1.25rem above it; the contact form is the one exception at 1.5 → @@ -355,7 +382,7 @@ breakpoint the label holds a fixed 10rem column with a 0.75rem gap and the value takes the remainder; below it the label stacks above its value and both run the full row width, so a long path or header value gets the whole screen instead of competing with a label column that a phone cannot afford. Each row is its own -element, so its hairline-faint divider spans the entire row rather than sitting +element, so its neutral-100 divider spans the entire row rather than sitting over one column. Header lists scroll internally at 16rem, bodies at 24rem: long payloads never @@ -363,25 +390,33 @@ push the next request off the board. ## Elevation & Depth -Hairline-first. The 1px hairline seam _is_ the edge of a surface; the shadow -underneath it is a whisper whose only job is to stop a white panel from looking -pasted onto the slate field. Depth in this system is carried by the tonal step -between board field and panel white, reinforced by a seam: not by lift. +There are no shadows. Not "shadows used sparingly": the stylesheet contains +none, and `box-shadow` on a panel computes to `none`. -### Shadow Vocabulary +Depth is carried by two things only. The tonal step between the neutral-50 page +field and the white panel that sits on it, and a 1px neutral-200 seam around +that panel. A border and a shadow together is the ghost card: two mechanisms +declaring the same edge, each weakening the other. Picking one is what makes a +dense page of stacked panels stay calm. -- **Resting whisper** (`box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)`): every panel, card, disclosure and primary button at rest. Barely perceptible by design. -- **Hover lift** (`box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)`): defined but **currently unused**. It was on the home page's use-case cards, which are not interactive: a surface that lifts under the cursor and then does nothing reads as a broken link. Available for a genuinely interactive card if one ever exists; not to be reintroduced on static content. +The primary button is a flat brand fill with no border and no shadow. Nothing on +the page floats. ### Named Rules -**The Hairline-First Rule.** If a surface needs to be distinguished, give it a -hairline seam before you give it a shadow. A panel with no border and a heavier -shadow is wrong in this system even when it looks fine in isolation. +**The One Edge Rule.** A surface declares its edge once, with a seam. If a new +component seems to need a shadow, the real problem is that it is not distinct +from what surrounds it, and the fix is tone or spacing. + +**The No-Lift Rule.** Nothing changes elevation on hover, because nothing has +elevation to change. Hover is expressed in colour: a fill steps lighter, a +border steps darker, bare text turns brand. -**The Shadow-Is-A-Response Rule.** Nothing above the resting whisper exists at -rest. `shadow-md` appears only as a hover response; there is no `shadow-lg` or -above anywhere, and adding one would break the flatness the board depends on. +**The Never-Hover-To-The-Field Rule.** A white surface must never hover to +neutral-50, because that is the page it sits on: the control dissolves into the +background at the exact moment it should respond. White surfaces hover to +neutral-100 and darken their seam to neutral-400. With no shadow to fall back +on, the colour step is the whole signal. ## Shapes @@ -403,7 +438,7 @@ label rather than replacing one. The family has no exceptions: there is no emoji anywhere in the product. The retention notice carries a stroked alert triangle from the same set, inheriting -label ink like every other icon. Keep it that way: an emoji brings its own +neutral-500 like every other icon. Keep it that way: an emoji brings its own colour and its own per-platform rendering, and one is enough to break the uniformity that makes this icon set read as a system. @@ -422,8 +457,9 @@ must never be borrowed for a disabled, errored, or drop-target surface. The system is implemented, not only described. `src/styles/components.css` carries the classes these entries specify (`.btn` and its variants, `.field`, `.field-label`, `.panel`, `.kv-row`, `.icon`, `.badge`, `.code-block`, -`.empty-value`), and templates compose them rather than repeating utility -strings. Utilities stay the default for one-off composition; anything whose +`.empty-value`, `.btn-lg`), and templates compose them rather +than repeating utility strings. Every page uses them: a template that re-spells +a component as a utility string is the bug, not a shortcut. Utilities stay the default for one-off composition; anything whose tokens must not drift between call sites belongs in that file. Before adding a variant, check whether an existing class should absorb it: eight spellings of one button is how two nominally identical controls ended up 4px apart. @@ -432,28 +468,31 @@ button is how two nominally identical controls ended up 4px apart. - **Character:** calm instruments. Sized for accuracy rather than presence; nothing asks to be admired. - **Shape:** control radius (0.375rem) on every variant. -- **Primary:** signal indigo fill, white label, medium weight, resting whisper shadow. Two sizes only: 0.75rem/1.5rem padding at 1rem type for the page's main action, and 0.5rem/1rem at 0.875rem inside panels. -- **Secondary:** white fill, control-edge stroke, control-ink label at 0.875rem medium, 0.5rem/0.75rem padding. Hovers to the board field. -- **Destructive:** rose wash fill, rose edge stroke, deep rose label, 0.375rem/0.75rem padding. Hovers one wash step darker. -- **Text-only:** no fill, no border, label ink at 0.875rem, resolving to signal indigo on hover, or to alert rose when the action deletes. Used for _Copy_, _Copy request_, and per-request _Delete_. It carries its own padding so the hit area clears the 24px target minimum, and the same authored focus ring as every other control: inside a card that repeats N times, falling back to the browser default multiplies the inconsistency by N. -- **Hover / Focus:** fills shift one step lighter on primary, one step darker on destructive. Focus is never suppressed: `outline: none` is always paired with a 2px `focus-visible` ring in **signal indigo lit** (the lighter step, not the fill colour), with a 2px white offset ring on filled buttons, so the ring reads against the indigo it sits on. Destructive controls ring in alert rose instead. +- **Primary:** brand-600 fill, white label, medium weight, no border and no shadow. +- **Three sizes, each with a job:** `.btn` at 0.375rem/0.75rem carries in-panel secondary and destructive controls; `.btn-primary` at 0.5rem/1rem carries the primary action inside a panel; `.btn-lg` at 0.75rem/1.5rem with 1rem type is reserved for the landing page's single call to action, where the button is the reason the page exists. +- **Secondary:** white fill, neutral-300 stroke, control-ink label at 0.875rem medium, 0.5rem/0.75rem padding. Hovers to the neutral-50 field. +- **Destructive:** one variant only, a solid danger-600 fill with a white label, hovering to danger-700. A tinted destructive button shares its fill with the danger surfaces it sits on, so it stops reading as a button exactly where the stakes are highest. It marks the action that actually destroys, never the one that asks: a control that opens a confirmation is an ordinary secondary button, because colouring it red spends the alarm before anything is at stake and leaves nothing louder for the step that matters. The per-request Delete is not this button either: it is a bare text action, because a solid red fill repeated once per card would shout over the stream it sits in. +- **Text-only:** no fill, no border, neutral-500 at 0.875rem, resolving to brand-600 on hover, or to danger-600 when the action deletes. Used for _Copy_, _Copy request_, and per-request _Delete_. It carries its own padding so the hit area clears the 24px target minimum, and the same authored focus ring as every other control: inside a card that repeats N times, falling back to the browser default multiplies the inconsistency by N. +- **Hover / Focus:** fills shift one step lighter on primary, one step darker on destructive. Focus is never suppressed: `outline: none` is always paired with a 2px `focus-visible` ring in **brand-500** (the lighter step, not the fill colour), with a 2px white offset ring on filled buttons, so the ring reads against the brand fill it sits on. Destructive controls ring in danger-500 instead. - **Disabled:** 50% opacity and `not-allowed` cursor; used on _Copy all_ when the stream is empty. ### Cards / Containers - **Corner Style:** panel radius (0.5rem). -- **Background:** panel white on the board field. -- **Shadow Strategy:** resting whisper only. No surface in the product lifts on hover: elevation change is reserved for something that responds to the cursor, and every card here is static content. +- **Background:** white on the neutral-50 field. +- **Shadow Strategy:** none. The seam is the edge; see Elevation & Depth. - **Border:** 1px hairline, always. - **Internal Padding:** 1rem below 40rem, 1.25rem above. - **Composition:** a request article is a header rule-separated from its body, with the method badge and the requested path on the left and the copy/delete actions on the right. The path carries the query string inline, because to the person reading it they are one thing: the URL the client addressed; the body is a stack of labelled regions rather than a nested set of boxes. Its key/value rows follow the flex behaviour described in Layout: 10rem label column above the breakpoint, label stacked above value below it. ### Inputs / Fields -- **Style:** white fill, 1px control-edge stroke, control radius, 0.5rem/0.75rem padding, 0.875rem type. Textareas and header/body fields use the mono stack with `spellcheck="false"`; ordinary text fields use sans. -- **Focus:** `outline: none` paired with a 1px **focus indigo** ring and a **signal indigo lit** border. That is a tighter, quieter treatment than the 2px ring on buttons, because a focused field is already unambiguous. Note the three-way split: fields ring in indigo-400, buttons ring in indigo-500, and only fills use indigo-600. -- **Labels:** the uppercase label style, 0.25–0.375rem above the field. The contact form is the one place labels are sentence-case medium body text, because it is a public form rather than an instrument panel. -- **Select:** native `appearance: none` with a slate chevron inlined as a data-URI background, 1.1em, positioned 0.5rem from the right with 2rem of padding reserved. Note this chevron is the single literal hex in the system (`#64748b`) and is the slate-500 equivalent. +- **Label spacing:** 0.5rem from label to field, 1rem between field groups. Four pixels is a collision, not a relationship. + +- **Style:** white fill, 1px neutral-300 stroke, control radius, 0.5rem/0.75rem padding, 0.875rem type. Textareas and header/body fields use the mono stack with `spellcheck="false"`; ordinary text fields use sans. +- **Focus:** `outline: none` paired with a 1px **brand-400** ring and a **brand-500** border. That is a tighter, quieter treatment than the 2px ring on buttons, because a focused field is already unambiguous. Note the three-way split: fields ring in brand-400, buttons ring in brand-500, and only fills use brand-600. Every control uses `:focus-visible`, so a mouse click never paints a ring on a button. +- **Labels:** one label component everywhere, uppercase at 0.75rem, 0.5rem above its field, with 1rem between field groups. The label belongs to its field, so it sits closer to it than the group does to the next one. There is no second label style for public forms: a contact field and a header field are the same control doing the same job, and two spellings of one control is drift, not intent. +- **Select:** native `appearance: none` with a slate chevron inlined as a data-URI background, 1.1em, positioned 0.5rem from the right with 2rem of padding reserved. Note this chevron is the single literal hex in the system (`#64748b`), because a data URI cannot read a CSS variable; it approximates neutral-500 and must be kept in step with it. ### Navigation @@ -461,8 +500,8 @@ There is none, and that is deliberate: the header is the periwinkle mark beside a centred wordmark linking home, at 1.875rem bold with tight tracking in control ink. The link wraps only the mark and wordmark, never the full header band. Every other route is reachable from the footer: feedback, the GitHub repository -with an inline brand glyph, and the Formspark credit: set at 0.875rem label ink, -centred, hovering to signal indigo. +with an inline brand glyph, and the Formspark credit: set at 0.875rem neutral-500, +centred, hovering to brand-600. Every link carries its own padding and a 2.75rem minimum height. As bare inline text their hit area collapses to the text box, which lands under the 24px target @@ -491,7 +530,7 @@ container never signals which path ran; only the content does. - **XML**: highlighted in place, unformatted; the payload keeps whatever whitespace it arrived with. - **Anything else**: HTML-escaped raw text, no highlighting, no reformatting. -An absent body or query string renders as an italic _None_ in label ink rather +An absent body or query string renders as an italic _None_ in neutral-500 rather than an empty well, so a card with nothing in it still reads as a complete record. Escaping happens on every path: a captured body is attacker-controlled text and is never trusted as markup. @@ -500,7 +539,7 @@ text and is never trusted as markup. The parts the product does not draw still carry its palette, set once in the base layer: text selection is indigo wash behind indigo ink rather than the -browser's blue, and `accent-color` is signal indigo so native checkboxes and the +browser's blue, and `accent-color` is brand-600 so native checkboxes and the search field's clear affordance match the one accent. `color-scheme` is declared light, so form controls and scrollbars render in the world the system actually commits to rather than inverting under a dark OS preference. @@ -509,9 +548,17 @@ Motion respects `prefers-reduced-motion`: transitions and the waiting ellipsis collapse to a resting state. Every animation in this product is decorative, none carries meaning that is lost when it is removed, so reducing it is a clean substitution, not a degradation. +### Render Window + +The stream renders 25 cards and reveals another 25 per press of a Show more +control, rather than rendering everything the store holds. Each card is roughly +104 DOM nodes, so a full page of them is a five-figure node count and a visible +stall on every filter change. The store still holds every captured request; only +the DOM is bounded. + ### Waiting State -Panel-white, dashed control-edge border, 3rem/1rem padding, centred label ink: +White fill, dashed neutral-300 border, 3rem/1rem padding, centred neutral-500: a 1rem line reading _Waiting for requests_ followed by an animated ellipsis, and a 0.875rem line explaining that requests will appear in real time. The ellipsis is a CSS `content` animation on four steps over 1.2s: the only looping motion @@ -520,8 +567,8 @@ in the product. ### Liveness Indicator When a request arrives while the tab is hidden, the count is prefixed to the -document title and the favicon is repainted on a canvas: a slate-800 disc, a -white lowercase _h_, and a rose-600 dot at the upper right. Restoring +document title and the favicon is repainted on a canvas: a neutral-800 disc, a +white lowercase _h_, and a danger-600 dot at the upper right. Restoring visibility clears both. This is where the board announces itself: the page body never flashes, animates, or auto-scrolls to claim attention. @@ -529,25 +576,25 @@ body never flashes, animates, or auto-scrolls to claim attention. ### Do: -- **Do** put every new surface on the board field with a panel-white fill, a 1px hairline seam, and the resting whisper shadow. That trio is the system's default surface. +- **Do** put every new surface on the neutral-50 field with a white fill and a 1px neutral-200 seam. That pair is the system's default surface, and it carries no shadow. - **Do** set anything the client sent in monospace at 0.75rem, and anything httphq says in sans. -- **Do** give a new region an uppercase 0.75rem/500/+0.025em label ink heading, and separate its rows with hairline-faint rules instead of nesting another bordered box. -- **Do** pair `outline: none` with a visible `focus-visible` ring every single time: 2px signal-indigo-lit plus a white offset ring on buttons, 1px focus-indigo plus a border shift on fields. +- **Do** give a new region an uppercase 0.75rem/500/+0.025em neutral-500 heading, and separate its rows with neutral-100 rules instead of nesting another bordered box. +- **Do** pair `outline: none` with a visible `focus-visible` ring every single time: 2px brand-500 plus a white offset ring on buttons, 1px brand-400 plus a border shift on fields. - **Do** take the radius from the category: 0.25rem for data, 0.375rem for controls, 0.5rem for panels. - **Do** design to the 40rem breakpoint alone, stacking below it and going horizontal above it. - **Do** keep icons at 24×24 viewBox, `stroke-width="2"`, `fill="none"`, `aria-hidden`, beside a text label. -- **Do** signal background activity in the tab title and favicon rather than in the page. +- **Do** state connection and background activity plainly: an indicator on the page, a count in the tab title, a dot on the favicon. Never by animating the body. ### Don't: -- **Don't** spend blue, emerald, amber, violet or sky on anything but a method badge. That spectrum is the traffic classification and nothing else. -- **Don't** add a second indigo-filled button to a view. One primary action per screen, every other control outlined or bare. -- **Don't** introduce a saturated fill with white text; coloured chips are a ~97% wash with a ~50% ink of the same hue. -- **Don't** use rose for validation errors or generic failure text: rose means removal, plus the unread favicon dot. -- **Don't** reach past the hover lift for elevation. There is no `shadow-lg` in this system, and a borderless card with a bigger shadow is wrong here even when it looks fine alone. +- **Don't** spend a method hue on anything but a method badge. The seven wash-and-ink pairs are the traffic classification and nothing else. +- **Don't** add a second brand-filled button to a view. One primary action per screen; everything else is an outlined, bare, or destructive control. +- **Don't** introduce a saturated fill with white text beyond the two that exist: the brand primary and the destructive button. Coloured chips are a 97% wash carrying a 50% ink of the same hue, never a fill. +- **Don't** use danger for validation errors or generic failure text: it means removal, plus the unread favicon dot. +- **Don't** add a shadow. There are none in this system, and a border under a shadow is two mechanisms declaring one edge. - **Don't** add a webfont. The system stack is a performance commitment on a tool whose promise is a working URL in seconds. - **Don't** add a second breakpoint. If a layout can't resolve with `sm` alone, simplify the layout. - **Don't** uppercase anything that isn't a field label or a method badge. - **Don't** borrow the dashed border for disabled, errored, or drop-target surfaces; it means "correctly empty, expecting content". - **Don't** animate the page to announce arrivals, auto-scroll the stream, or add a second looping animation beside the waiting ellipsis. -- **Don't** half-migrate the accent toward the mark's periwinkle. Until that convergence is done deliberately across the mark, favicons and UI together, `signal-indigo` remains the interface accent everywhere. +- **Don't** reach for a stock framework colour. Tailwind's defaults are cleared in `theme.css`, so a stock utility silently renders nothing; every colour comes from the authored ramps. diff --git a/public/app.css b/public/app.css index f0eda88..0dd40f4 100644 --- a/public/app.css +++ b/public/app.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-amber-50:oklch(98.7% .022 95.277);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-700:oklch(55.5% .163 48.998);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-700:oklch(50.8% .118 165.612);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-700:oklch(48.8% .243 264.376);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-700:oklch(49.1% .27 292.581);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-xl:36rem;--container-2xl:42rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}button:not(:disabled),summary,[role=button]:not(:disabled){cursor:pointer}::selection{background-color:var(--color-indigo-100);color:var(--color-indigo-900)}:root{accent-color:var(--color-indigo-600);color-scheme:light}}@layer components{.app-select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%2364748b'%3E%3Cpath fill-rule='evenodd' d='M5.23 7.21a.75.75 0 011.06.02L10 11.083l3.71-3.853a.75.75 0 111.08 1.04l-4.25 4.41a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z' clip-rule='evenodd'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.1em;padding-right:2rem}.loading-dots:after{content:"";text-align:left;width:1.5ch;animation:1.2s steps(4,end) infinite httphq-dots;display:inline-block}.focus-ring:focus{--tw-outline-style:none;outline-style:none}.focus-ring:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--color-indigo-500)}.btn{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}.btn:focus{--tw-outline-style:none;outline-style:none}.btn:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--color-indigo-500)}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-secondary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}@media (hover:hover){.btn-secondary:hover{background-color:var(--color-slate-50)}}.btn-danger{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-rose-200);background-color:var(--color-rose-50);color:var(--color-rose-700)}@media (hover:hover){.btn-danger:hover{background-color:var(--color-rose-100)}}.btn-danger:focus-visible{--tw-ring-color:var(--color-rose-500)}.btn-primary{background-color:var(--color-indigo-600);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}@media (hover:hover){.btn-primary:hover{background-color:var(--color-indigo-500)}}.btn-primary:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.btn-inline{align-items:center;gap:calc(var(--spacing) * 1);border-radius:var(--radius-sm);padding-inline:calc(var(--spacing) * 1);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-slate-500);display:inline-flex}@media (hover:hover){.btn-inline:hover{color:var(--color-indigo-600)}}.btn-inline:focus{--tw-outline-style:none;outline-style:none}.btn-inline:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--color-indigo-500)}@media (hover:hover){.btn-inline-danger:hover{color:var(--color-rose-600)}}.btn-inline-danger:focus-visible{--tw-ring-color:var(--color-rose-500)}.field{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:block}.field:focus{--tw-outline-style:none;outline-style:none}.field:focus-visible{border-color:var(--color-indigo-500);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--color-indigo-400)}.field-mono{font-family:var(--font-mono)}.field-label{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);color:var(--color-slate-500);text-transform:uppercase;display:block}.region-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);color:var(--color-slate-500);text-transform:uppercase}.panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-200);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.panel-flat{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-200);background-color:var(--color-white)}.kv-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-100);padding-block:calc(var(--spacing) * 1.5);flex-direction:column;display:flex}.kv-row:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (min-width:40rem){.kv-row{gap:calc(var(--spacing) * 3);flex-direction:row}}.kv-row-tight{padding-block:calc(var(--spacing) * 1)}.kv-key{color:var(--color-slate-500)}@media (min-width:40rem){.kv-key{width:calc(var(--spacing) * 40);flex-shrink:0}}.kv-value{min-width:calc(var(--spacing) * 0)}.icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);flex-shrink:0}.badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);text-transform:uppercase;border-radius:.25rem;flex-shrink:0;justify-content:center;align-items:center;display:inline-flex}.code-block{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-200);background-color:var(--color-slate-50);padding:calc(var(--spacing) * 3);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-radius:.25rem;overflow:auto}.empty-value{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-500);font-style:italic}}@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-4{margin-inline:calc(var(--spacing) * -4)}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-auto{margin-top:auto}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-9{height:calc(var(--spacing) * 9)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-96{max-height:calc(var(--spacing) * 96)}.min-h-11{min-height:calc(var(--spacing) * 11)}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing) * 2)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-9{width:calc(var(--spacing) * 9)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.scroll-mt-24{scroll-margin-top:calc(var(--spacing) * 24)}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-rose-200{border-color:var(--color-rose-200)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-slate-300{border-color:var(--color-slate-300)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-500{background-color:var(--color-rose-500)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-50\/95{background-color:#f8fafcf2}@supports (color:color-mix(in lab, red, red)){.bg-slate-50\/95{background-color:color-mix(in oklab, var(--color-slate-50) 95%, transparent)}}.bg-slate-100{background-color:var(--color-slate-100)}.bg-violet-50{background-color:var(--color-violet-50)}.bg-white{background-color:var(--color-white)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-10{padding-bottom:calc(var(--spacing) * 10)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-pretty{text-wrap:pretty}.break-all{word-break:break-all}.text-\[\#005cc5\]{color:#005cc5}.text-\[\#032f62\]{color:#032f62}.text-amber-700{color:var(--color-amber-700)}.text-blue-700{color:var(--color-blue-700)}.text-emerald-700{color:var(--color-emerald-700)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-rose-700{color:var(--color-rose-700)}.text-sky-700{color:var(--color-sky-700)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-violet-700{color:var(--color-violet-700)}.text-white{color:var(--color-white)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-all{-webkit-user-select:all;user-select:all}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}.placeholder\:text-slate-400::placeholder{color:var(--color-slate-400)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-indigo-400:focus{--tw-ring-color:var(--color-indigo-400)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-indigo-500:focus-visible{--tw-ring-color:var(--color-indigo-500)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}@media (min-width:40rem){.sm\:-mx-6{margin-inline:calc(var(--spacing) * -6)}.sm\:inline{display:inline}.sm\:w-40{width:calc(var(--spacing) * 40)}.sm\:w-auto{width:auto}.sm\:max-w-\[10rem\]{max-width:10rem}.sm\:shrink-0{flex-shrink:0}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:p-8{padding:calc(var(--spacing) * 8)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:py-8{padding-block:calc(var(--spacing) * 8)}.sm\:py-12{padding-block:calc(var(--spacing) * 12)}.sm\:pt-16{padding-top:calc(var(--spacing) * 16)}.sm\:pb-5{padding-bottom:calc(var(--spacing) * 5)}.sm\:pb-8{padding-bottom:calc(var(--spacing) * 8)}.sm\:pb-12{padding-bottom:calc(var(--spacing) * 12)}.sm\:pb-14{padding-bottom:calc(var(--spacing) * 14)}.sm\:text-right{text-align:right}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}}@keyframes httphq-dots{0%{content:""}25%{content:"."}50%{content:".."}75%,to{content:"..."}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.loading-dots:after{content:"...";animation:none}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"
+
Found a bug, have an idea, or want to say hi? We read every message.