`: screen readers announce the name twice. Use `aria-labelledby` or drop one. -- Heading order is clean (h1 → h2 → 6× h3, no skips), `lang="en"` present, all 5 interactive elements have accessible names, all 8 SVGs correctly hidden, `rel="noopener"` present. Zero console messages and 10/10 successful requests on load. -- Only the CTA has an authored focus style; the four links fall back to Chrome's UA default blue, which is engine-specific and unrelated to the indigo system. The CTA's ring also *fades in* via `transition` rather than appearing instantly. -- LCP element is the hero lede `
`, not the `
`: the largest painted element is the supporting copy, not the headline.
-- No `prefers-reduced-motion` rule exists in any loaded stylesheet while 7 elements carry `.transition`.
-- Parallelism breaks on card four: five titles are verb-first (Test, Inspect, Debug, Verify, Share), and "Mobile & IoT traffic" names a channel instead of a job.
-- Footer says *"A community project by Formspark"*; PRODUCT.md records Formspark as **sponsor**. Worth aligning.
-- Measured performance is excellent where localhost can measure it: LCP 128ms, CLS 0, TTFB 2ms. The payload issue above is specifically the cold-cache, real-network case localhost cannot reproduce.
-
-## Questions to Consider
-
-1. The North Star is "The Arrivals Board." What is the argument for describing a live stream in six paragraphs instead of rendering one honest static capture beneath the button, from components that already exist and cost nothing to place?
-2. Three claims sit in PRODUCT.md as the things no neighbouring tool could truthfully copy, and none of the three appear on this page. What is the six-card grid buying that those three sentences aren't?
-3. The CTA creates a public, unauthenticated, guessable-in-principle URL that dies in four hours, and says none of that before the click. Which of those six facts would you be uncomfortable stating above the button: and what does that discomfort tell you?
-4. Half this page's height exists to persuade a first-timer who "will not read documentation to get started", while the regular has read those cards fifty times. If neither audience reads the grid, who is it for?
diff --git a/DESIGN.md b/DESIGN.md
index d013d3a..dd7de42 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -1,6 +1,6 @@
---
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 indigo control, color reserved for classifying traffic."
colors:
beacon-periwinkle: "#707ee7"
signal-indigo: "oklch(51.1% 0.262 276.966)"
@@ -239,7 +239,7 @@ Any method outside this set falls back to the HEAD pair.
- **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)`): placeholders and the italic "None" for an absent query string or body.
+- **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.
@@ -419,6 +419,15 @@ must never be borrowed for a disabled, errored, or drop-target surface.
## Components
+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
+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.
+
### Buttons
- **Character:** calm instruments. Sized for accuracy rather than presence; nothing asks to be admired.
@@ -426,7 +435,7 @@ must never be borrowed for a disabled, errored, or drop-target surface.
- **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.75–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_.
+- **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.
- **Disabled:** 50% opacity and `not-allowed` cursor; used on _Copy all_ when the stream is empty.
@@ -437,7 +446,7 @@ must never be borrowed for a disabled, errored, or drop-target surface.
- **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.
- **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 UUID on the left and the copy/delete actions on the right; 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.
+- **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
@@ -482,7 +491,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 quiet ink rather
+An absent body or query string renders as an italic _None_ in label ink 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.
diff --git a/e2e/tests/endpoint-screen.spec.ts b/e2e/tests/endpoint-screen.spec.ts
index 1c214c4..6ad121b 100644
--- a/e2e/tests/endpoint-screen.spec.ts
+++ b/e2e/tests/endpoint-screen.spec.ts
@@ -339,7 +339,7 @@ test.describe("Endpoint screen", () => {
await page.locator('[data-test="method-filter"]').selectOption("PUT");
await expect(
page.locator('[data-test="copy-all-har-label"]'),
- ).toContainText("Copy all (1)");
+ ).toContainText("Copy shown (1)");
await page.locator('[data-test="copy-all-har"]').click();
const clipboard = await page.evaluate(() =>
@@ -374,7 +374,10 @@ test.describe("Endpoint screen", () => {
await search.fill("Nothing-matches-this");
await expect(results).toContainText("0 results");
- await expect(requests).toContainText("Waiting for requests");
+ // A stream hidden by a filter is not a stream that never arrived, so it
+ // must not claim to be still waiting.
+ await expect(requests).toContainText("No requests match this filter");
+ await expect(requests).not.toContainText("Waiting for requests");
});
test("filters by header key/value via the search box", async ({
@@ -428,15 +431,60 @@ test.describe("Endpoint screen", () => {
);
});
- test("delete-all clears every request", async ({ page, request }) => {
+ test("delete-all asks before clearing every request", async ({
+ page,
+ request,
+ }) => {
await post(request, endpointUrl, { data: "x" });
await post(request, endpointUrl, { data: "y" });
+
await page.locator('[data-test="delete-requests"]').click();
+ // Destructive and unrecoverable, so it confirms first; the requests are
+ // still there until the confirmation is accepted.
+ await expect(page.locator('[data-test="delete-confirm"]')).toBeVisible();
+ await expect(page.locator('[data-test="request"]')).toHaveCount(2);
+
+ await page.locator('[data-test="delete-cancel"]').click();
+ await expect(page.locator('[data-test="request"]')).toHaveCount(2);
+
+ await page.locator('[data-test="delete-requests"]').click();
+ await page.locator('[data-test="delete-confirm-button"]').click();
await expect(page.locator('[data-test="requests"]')).toContainText(
"Waiting for requests",
);
});
+ test("a filtered-empty stream is not reported as waiting", async ({
+ page,
+ request,
+ }) => {
+ await post(request, endpointUrl, { data: "x" });
+ await expect(page.locator('[data-test="request"]')).toHaveCount(1);
+
+ await page
+ .locator('[data-test="search-input"]')
+ .fill("nothing-matches-this-term");
+ await expect(page.locator('[data-test="empty-filtered"]')).toBeVisible();
+ await expect(page.locator('[data-test="empty-waiting"]')).toHaveCount(0);
+
+ await page.locator('[data-test="clear-filters"]').click();
+ await expect(page.locator('[data-test="request"]')).toHaveCount(1);
+ });
+
+ test("a malformed header line is reported instead of dropped", async ({
+ page,
+ }) => {
+ await page.locator('[data-test="send-toggle"]').click();
+ await page
+ .locator('[data-test="send-headers"]')
+ .fill("Authorization Bearer sk_test_123");
+ await page.locator('[data-test="send-submit"]').click();
+ await expect(page.locator('[data-test="send-status"]')).toContainText(
+ "is not a header",
+ );
+ await expect(page.locator('[data-test="request"]')).toHaveCount(0);
+ });
+
test("delete-request removes a single card", async ({ page, request }) => {
await post(request, endpointUrl, { data: "first" });
const response = await request.post(endpointUrl, {
diff --git a/public/app.css b/public/app.css
index 42422c4..f0eda88 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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-outline-style:solid;--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-700:oklch(55.5% .163 48.998);--color-emerald-50:oklch(97.9% .021 166.113);--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}}@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.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}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.start{inset-inline-start:var(--spacing)}.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-6{margin-top:calc(var(--spacing) * 6)}.mt-auto{margin-top:auto}.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}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.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-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}.flex-shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.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}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.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-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-blue-50{background-color:var(--color-blue-50)}.bg-emerald-50{background-color:var(--color-emerald-50)}.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-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-1\.5{padding-block:calc(var(--spacing) * 1.5)}.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)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.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{--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)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px 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)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.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)}.ring{--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)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);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,)}.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-rose-100:hover{background-color:var(--color-rose-100)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-rose-600:hover{color:var(--color-rose-600)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}}.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-rose-500:focus-visible{--tw-ring-color:var(--color-rose-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)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@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-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@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:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}
\ No newline at end of file
+@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:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}
\ No newline at end of file
diff --git a/public/endpoint.js b/public/endpoint.js
index 00bced4..8c34021 100644
--- a/public/endpoint.js
+++ b/public/endpoint.js
@@ -14,6 +14,18 @@
OPTIONS: "bg-sky-50 text-sky-700",
};
+ // How many cards are in the DOM at once, and how many more each reveal adds.
+ const PAGE_SIZE = 25;
+
+ // Relative timestamps are re-evaluated on this interval. Anything longer and
+ // "a few seconds ago" is still on screen a minute later.
+ const TICK_MS = 30_000;
+
+ // Reconnect backoff bounds. The page is designed to sit open for hours, so a
+ // dropped socket is expected rather than exceptional.
+ const RECONNECT_MIN_MS = 1_000;
+ const RECONNECT_MAX_MS = 30_000;
+
function transformRequest(r) {
return { ...r, createdAt: new Date(r.createdAt) };
}
@@ -47,6 +59,15 @@
return this.requests.filter((r) => r.method === this.methodFilter);
},
+ get filtered() {
+ return !!this.methodFilter || !!this.search;
+ },
+
+ clearFilters() {
+ this.methodFilter = "";
+ this.search = "";
+ },
+
fetchRequests() {
if (!this.endpointId) return;
const url =
@@ -91,12 +112,24 @@
function endpointPageFactory() {
return {
METHODS,
- sendForm: { method: "POST", body: "", headers: "" },
+ pageSize: PAGE_SIZE,
+ sendForm: { method: "POST", path: "", body: "", headers: "" },
sendStatus: "",
- copyLabel: "Copy",
+ sendFailed: false,
copiedKey: null,
+ pendingDeleteAll: false,
+ announcement: "",
+ connection: "connecting",
+ renderLimit: PAGE_SIZE,
+ // Bumped on an interval purely so relative-time expressions that read it
+ // re-evaluate. Alpine has no other reason to know that wall-clock time
+ // passed, so without this every timestamp freezes at first render.
+ tick: 0,
_baseTitle: document.title,
_unread: 0,
+ _reconnectDelay: RECONNECT_MIN_MS,
+ _socket: null,
+ _closed: false,
// Alpine calls this once when the component mounts. Endpoint id and
// WebSocket URL come from data-* attributes on the root element so the
@@ -105,16 +138,59 @@
const endpointId = this.$el.dataset.endpointId;
if (!endpointId) return;
const scheme = location.protocol === "https:" ? "wss:" : "ws:";
- const wsUrl = `${scheme}//${location.host}/ws/${endpointId}`;
+ this._wsUrl = `${scheme}//${location.host}/ws/${endpointId}`;
Alpine.store("main").setEndpoint(endpointId);
- this._connectWebSocket(wsUrl);
+ this._connectWebSocket();
+ setInterval(() => this.tick++, TICK_MS);
document.addEventListener("visibilitychange", () => {
if (!document.hidden) this._clearUnread();
});
+ window.addEventListener("beforeunload", () => {
+ this._closed = true;
+ if (this._socket) this._socket.close();
+ });
},
- _connectWebSocket(url) {
- const socket = new WebSocket(url);
+ // Which of the three empty states applies. They are distinct on purpose:
+ // "nothing has arrived", "your filter hides everything" and "the
+ // connection is gone" are different facts and previously shared one panel
+ // that asserted the first regardless of which was true.
+ get streamState() {
+ if (Alpine.store("main").visibleRequests.length > 0) return "list";
+ if (Alpine.store("main").filtered) return "filtered";
+ return "waiting";
+ },
+
+ get renderedRequests() {
+ return Alpine.store("main").visibleRequests.slice(0, this.renderLimit);
+ },
+
+ get hiddenCount() {
+ return Math.max(
+ 0,
+ Alpine.store("main").visibleRequests.length - this.renderLimit,
+ );
+ },
+
+ showMore() {
+ this.renderLimit += PAGE_SIZE;
+ },
+
+ _connectWebSocket() {
+ if (this._closed) return;
+ this.connection =
+ this.connection === "live" ? "connecting" : this.connection;
+ const socket = new WebSocket(this._wsUrl);
+ this._socket = socket;
+
+ socket.addEventListener("open", () => {
+ this.connection = "live";
+ this._reconnectDelay = RECONNECT_MIN_MS;
+ // Traffic that landed while the socket was down is not replayed, so
+ // refetch to close the gap rather than silently missing it.
+ Alpine.store("main").fetchRequests();
+ });
+
socket.addEventListener("message", (payload) => {
let request;
try {
@@ -123,14 +199,28 @@
return;
}
Alpine.store("main").addRequest(request);
+ this.announce(`${request.method} request received`);
if (document.hidden) {
this._unread += 1;
this._renderUnread();
}
});
- socket.addEventListener("open", () => console.debug("ws:open"));
- socket.addEventListener("close", () => console.debug("ws:close"));
- socket.addEventListener("error", () => console.debug("ws:error"));
+
+ socket.addEventListener("close", () => this._scheduleReconnect());
+ socket.addEventListener("error", () => socket.close());
+ },
+
+ // Exponential backoff to a 30s ceiling. A page left open overnight should
+ // keep trying without hammering the server once it is genuinely gone.
+ _scheduleReconnect() {
+ if (this._closed) return;
+ this.connection = "disconnected";
+ const delay = this._reconnectDelay;
+ this._reconnectDelay = Math.min(delay * 2, RECONNECT_MAX_MS);
+ setTimeout(() => {
+ this.connection = "connecting";
+ this._connectWebSocket();
+ }, delay);
},
_renderUnread() {
@@ -160,15 +250,27 @@
},
formatTimeAgo: window.formatTimeAgo,
+ formatClock: window.formatClock,
+ formatBytes: window.formatBytes,
renderBody: window.renderBody,
- async copy(text) {
+ // Screen readers get no navigation on this page, so every change that a
+ // sighted user sees has to be spoken here or it does not exist.
+ announce(message) {
+ this.announcement = message;
+ },
+
+ async copy(text, key) {
try {
await window.copyToClipboard(text);
- this.copyLabel = "Copied!";
- setTimeout(() => (this.copyLabel = "Copy"), 1500);
+ this.copiedKey = key;
+ this.announce("Copied to clipboard");
+ setTimeout(() => {
+ if (this.copiedKey === key) this.copiedKey = null;
+ }, 1500);
} catch (err) {
console.error(err);
+ this.announce("Copy failed");
}
},
@@ -179,29 +281,59 @@
try {
await window.copyToClipboard(window.buildHarExport(requests));
this.copiedKey = key;
+ this.announce(`Copied ${requests.length} requests to clipboard`);
setTimeout(() => {
if (this.copiedKey === key) this.copiedKey = null;
}, 1500);
} catch (err) {
console.error(err);
+ this.announce("Copy failed");
}
},
+ confirmDeleteAll() {
+ this.pendingDeleteAll = true;
+ },
+
+ async deleteAllConfirmed() {
+ const count = Alpine.store("main").requests.length;
+ this.pendingDeleteAll = false;
+ await Alpine.store("main").deleteRequests();
+ this.announce(`Deleted ${count} requests`);
+ },
+
async sendCustom() {
const store = Alpine.store("main");
if (!store.endpointId) return;
- const headers = window.parseHeaderLines(this.sendForm.headers);
+ const parsed = window.parseHeaderLines(this.sendForm.headers);
+ if (parsed.invalid.length) {
+ this.sendFailed = true;
+ this.sendStatus = `Line ${parsed.invalid[0].line} is not a header: "${parsed.invalid[0].text}". Use Key: Value.`;
+ this.announce(this.sendStatus);
+ return;
+ }
+ const path = this.sendForm.path.replace(/^\/+/, "");
+ const target = `/to/${store.endpointId}${path ? "/" + path : ""}`;
try {
+ this.sendFailed = false;
this.sendStatus = "Sending…";
- const res = await fetch(`/to/${store.endpointId}`, {
+ const res = await fetch(target, {
method: this.sendForm.method,
- headers,
+ headers: parsed.headers,
body: this.sendForm.body || undefined,
});
- this.sendStatus = res.ok ? "Sent" : `Error: HTTP ${res.status}`;
- setTimeout(() => (this.sendStatus = ""), 2000);
+ this.sendFailed = !res.ok;
+ this.sendStatus = res.ok
+ ? `Sent ${this.sendForm.method}`
+ : `The server rejected it: HTTP ${res.status}.`;
+ this.announce(this.sendStatus);
+ if (res.ok) setTimeout(() => (this.sendStatus = ""), 2000);
} catch (err) {
- this.sendStatus = `Error: ${err.message}`;
+ // The browser refuses some combinations outright, e.g. a body on GET.
+ // Report its reason rather than swallowing it, and keep it on screen.
+ this.sendFailed = true;
+ this.sendStatus = `Could not send: ${err.message}`;
+ this.announce(this.sendStatus);
}
},
};
diff --git a/public/index.js b/public/index.js
index db181da..f670ace 100644
--- a/public/index.js
+++ b/public/index.js
@@ -46,19 +46,48 @@ window.copyToClipboard = async function (text) {
}
};
-/* Header parsing for the send-custom-request panel */
+/* Absolute clock time. This is the primary timestamp on a capture: an arrivals
+ board shows when something landed, and a relative age alone goes stale the
+ moment it is rendered. */
+
+const clockFormatter = new Intl.DateTimeFormat(undefined, {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+});
+
+window.formatClock = function (date) {
+ return clockFormatter.format(date);
+};
+
+/* Byte sizes for captured bodies. */
+
+window.formatBytes = function (bytes) {
+ if (bytes < 1024) return `${bytes} B`;
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
+};
+
+/* Header parsing for the send-custom-request panel.
+
+ Malformed lines are returned rather than dropped. Silently discarding a line
+ that is one typo away from an Authorization header, and then reporting
+ success, sends the user chasing an auth bug that does not exist. */
window.parseHeaderLines = function (text) {
- const out = {};
- if (!text) return out;
- for (const line of text.split(/\r?\n/)) {
+ const headers = {};
+ const invalid = [];
+ if (!text) return { headers, invalid };
+ text.split(/\r?\n/).forEach((line, i) => {
const trimmed = line.trim();
- if (!trimmed) continue;
+ if (!trimmed) return;
const idx = trimmed.indexOf(":");
- if (idx <= 0) continue;
- const k = trimmed.slice(0, idx).trim();
- const v = trimmed.slice(idx + 1).trim();
- if (k) out[k] = v;
- }
- return out;
+ const key = idx > 0 ? trimmed.slice(0, idx).trim() : "";
+ if (!key) {
+ invalid.push({ line: i + 1, text: trimmed });
+ return;
+ }
+ headers[key] = trimmed.slice(idx + 1).trim();
+ });
+ return { headers, invalid };
};
diff --git a/public/render-body.js b/public/render-body.js
index 8e40d90..7571037 100644
--- a/public/render-body.js
+++ b/public/render-body.js
@@ -1,6 +1,14 @@
/* Body rendering: best-effort content-type-aware display for a captured
request body (JSON pretty-print, multipart/form-data part list, XML
- highlighting, or escaped raw text). Loaded on every page. */
+ highlighting, or escaped raw text). Loaded on the endpoint page only. */
+
+/* Above this many characters the body is shown as plain escaped text.
+ Highlighting runs synchronously on the main thread and emits roughly one
+ element per token, so a large payload costs hundreds of milliseconds and tens
+ of thousands of nodes to colour text the reader has to scroll past anyway.
+ The body is still shown in full and still copyable; only the colour is
+ dropped. */
+const HIGHLIGHT_LIMIT = 40_000;
function htmlEscape(s) {
return String(s)
@@ -11,12 +19,16 @@ function htmlEscape(s) {
.replaceAll("'", "'");
}
-function highlightPrettyJSON(value) {
- const pretty = JSON.stringify(value, null, 2);
- if (window.hljs && window.hljs.getLanguage("json")) {
- return window.hljs.highlight(pretty, { language: "json" }).value;
+function highlight(text, language) {
+ if (text.length > HIGHLIGHT_LIMIT) return htmlEscape(text);
+ if (window.hljs && window.hljs.getLanguage(language)) {
+ return window.hljs.highlight(text, { language }).value;
}
- return htmlEscape(pretty);
+ return htmlEscape(text);
+}
+
+function highlightPrettyJSON(value) {
+ return highlight(JSON.stringify(value, null, 2), "json");
}
/* Case-insensitive lookup into a headers object whose values are either a
@@ -133,13 +145,8 @@ window.renderBody = function (body, headers) {
// not JSON
}
// Heuristic: looks like XML/HTML if it starts with '<'
- const trimmed = body.trimStart();
- if (
- trimmed.startsWith("<") &&
- window.hljs &&
- window.hljs.getLanguage("xml")
- ) {
- return window.hljs.highlight(body, { language: "xml" }).value;
+ if (body.trimStart().startsWith("<")) {
+ return highlight(body, "xml");
}
return htmlEscape(body);
};
diff --git a/src/styles/app.css b/src/styles/app.css
index f8927bc..53c481d 100644
--- a/src/styles/app.css
+++ b/src/styles/app.css
@@ -2,7 +2,12 @@
Everything here is import and configuration only; rules live in the modules
so each one stays small enough to read in full. */
-@import "tailwindcss";
+/* source(none) turns off automatic detection so only the directives below are
+ scanned. Left on, Tailwind walks the whole repository and mines every
+ non-ignored file for class-like strings, which means prose that merely
+ discusses a class name (DESIGN.md, a critique write-up) silently adds rules
+ to the shipped stylesheet, and editing .gitignore changes the build output. */
+@import "tailwindcss" source(none);
@import "./base.css";
@import "./components.css";
diff --git a/src/styles/components.css b/src/styles/components.css
index 9741cf0..1cf8b95 100644
--- a/src/styles/components.css
+++ b/src/styles/components.css
@@ -1,5 +1,12 @@
-/* Patterns that utility classes cannot express: a native control restyled from
- scratch, and a text affordance built from generated content. */
+/* Component classes for patterns that repeat across templates. Utilities stay
+ the default for one-off composition; anything that appears more than twice, or
+ whose tokens must not drift between call sites, lives here.
+
+ Focus is one system, not two: every control uses :focus-visible so a mouse
+ click never paints a ring on a button, while text fields still show one
+ because browsers treat click-focus on them as keyboard modality. The two ring
+ weights are deliberate and recorded in DESIGN.md: 2px indigo-500 on controls,
+ 1px indigo-400 plus a border shift on fields. */
@layer components {
/* Native select with the chevron drawn in, anchored right after the value.
@@ -24,4 +31,108 @@
text-align: left;
animation: httphq-dots 1.2s steps(4, end) infinite;
}
+
+ /* Focus ring, applied by the component classes below. Exposed separately for
+ the handful of elements that are not buttons or fields. */
+ .focus-ring {
+ @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500;
+ }
+
+ /* Buttons. One geometry, one focus treatment; variants change colour only. */
+ .btn {
+ @apply inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium;
+ @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500;
+ @apply disabled:opacity-50 disabled:cursor-not-allowed;
+ }
+
+ .btn-secondary {
+ @apply border border-slate-300 bg-white text-slate-700 hover:bg-slate-50;
+ }
+
+ .btn-danger {
+ @apply border border-rose-200 bg-rose-50 text-rose-700 hover:bg-rose-100;
+ @apply focus-visible:ring-rose-500;
+ }
+
+ .btn-primary {
+ @apply bg-indigo-600 px-4 py-2 text-white shadow-sm hover:bg-indigo-500;
+ @apply focus-visible:ring-offset-2;
+ }
+
+ /* Text-only action inside a card. Padded to clear the 24px target minimum
+ without changing where the label sits. */
+ .btn-inline {
+ @apply inline-flex items-center gap-1 rounded-sm px-1 py-1 text-sm text-slate-500;
+ @apply hover:text-indigo-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500;
+ }
+
+ .btn-inline-danger {
+ @apply hover:text-rose-600 focus-visible:ring-rose-500;
+ }
+
+ /* Form fields. The mono variant is for captured-data entry. */
+ .field {
+ @apply block w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm;
+ @apply focus:outline-none focus-visible:ring-1 focus-visible:ring-indigo-400 focus-visible:border-indigo-500;
+ }
+
+ .field-mono {
+ @apply font-mono;
+ }
+
+ .field-label {
+ @apply block text-xs font-medium text-slate-500 uppercase tracking-wide mb-1;
+ }
+
+ /* Section label inside a card. Same token as a field label with the spacing
+ a region heading needs. */
+ .region-label {
+ @apply text-xs font-medium text-slate-500 uppercase tracking-wide mb-2;
+ }
+
+ /* Surfaces. panel carries the resting whisper; panel-flat is the same edge
+ without it, for surfaces that sit inside another panel. */
+ .panel {
+ @apply rounded-lg border border-slate-200 bg-white shadow-sm;
+ }
+
+ .panel-flat {
+ @apply rounded-lg border border-slate-200 bg-white;
+ }
+
+ /* Key/value row. Stacks below the breakpoint so a value is never squeezed
+ into what is left of a fixed label column on a phone. */
+ .kv-row {
+ @apply flex flex-col sm:flex-row sm:gap-3 py-1.5 border-b border-slate-100 last:border-b-0;
+ }
+
+ .kv-row-tight {
+ @apply py-1;
+ }
+
+ .kv-key {
+ @apply text-slate-500 sm:w-40 sm:shrink-0;
+ }
+
+ .kv-value {
+ @apply min-w-0;
+ }
+
+ .icon {
+ @apply w-4 h-4 shrink-0;
+ }
+
+ .badge {
+ @apply inline-flex items-center justify-center rounded px-2 py-0.5 text-xs font-semibold uppercase tracking-wide shrink-0;
+ }
+
+ .code-block {
+ @apply text-xs font-mono bg-slate-50 border border-slate-200 rounded p-3 overflow-auto;
+ }
+
+ /* Absent value. slate-500 rather than slate-400: the lighter step measured
+ 2.63:1 on white, under the 4.5:1 floor. */
+ .empty-value {
+ @apply text-xs text-slate-500 italic;
+ }
}
diff --git a/src/views/endpoint.html b/src/views/endpoint.html
index d3ee4f7..47c18f5 100644
--- a/src/views/endpoint.html
+++ b/src/views/endpoint.html
@@ -5,30 +5,35 @@
x-data="endpointPage"
data-endpoint-id="{{.EndpointID}}"
>
+ Captured requests for {{.EndpointID}}
+
+
+
+
-
-
-
- Your unique URL
-
+
+
+
+ Send requests to this URL
+
{{.EndpointURL}}
+
+ Anyone with this URL can read every request sent to it. Requests are
+ deleted after 4 hours.
+
-
-
+
+
- Send a test request
+
+ Send a test request
+
Captured requests for {{.EndpointID}}
+ + + + -+ Send requests to this URL +
{{.EndpointURL}}
+ Anyone with this URL can read every request sent to it. Requests are + deleted after 4 hours. +