/* ============================================================================
   Developer Lab — page furniture
   ----------------------------------------------------------------------------
   The listener console (REST and SOAP are one page) was built from Bootstrap
   cards and a row of identical text buttons. Everything on it had the same
   weight: the URL somebody came to copy read exactly like the request count,
   and Delete read exactly like Logs.

   This sheet gives the Lab its own vocabulary — a page head, an info note, an
   endpoint card, an action button, a tip strip — so the same components can be
   taken up by the Database Lab, the request log and the API tester without
   being reinvented three more times.

   WHY A NEW FILE. tool-studio.css is the STUDIO skin, shared by 41 pages
   including the compilers, the interview pages and the tools. Nothing in it is
   Developer-Lab-specific and nothing Lab-specific belongs in it. The one
   exception is the usage meter (.jch-meter), which lives there because the
   Database Lab shows it too and already loads that sheet.

   TOKENS. Everything colour comes from the --ts-* set that .tstudio defines,
   so this sheet inherits dark mode for free. It deliberately does NOT use the
   --jch-ti-* tone tokens: those are injected into the document by app.js at
   runtime, which means a tile tinted with them is untinted until that script
   has run. The tones below are declared here and are correct on first paint.
   ========================================================================== */

/* ---------------------------------------------------------------- page head */

/* Breadcrumb back to the hub. Small, quiet, above the title - it is a way out,
   not an action, and the old full-size button competed with New endpoint. */
.dl-back {
  display: inline-flex; align-items: center; gap: .5rem;
  font-size: .82rem; font-weight: 600; text-decoration: none;
  color: var(--ts-muted); margin-bottom: 1rem;
  transition: color .15s ease;
}
.dl-back:hover { color: var(--ts-brand-t); }
.dl-back .ico {
  width: 1.9rem; height: 1.9rem; border-radius: 50%; display: grid; place-items: center;
  background: var(--ts-card); border: 1px solid var(--ts-line); color: inherit;
  transition: transform .15s ease, border-color .15s ease;
}
.dl-back:hover .ico { transform: translateX(-2px); border-color: var(--ts-brand-t); }

.dl-head {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1rem;
  margin-bottom: 1.1rem;
}
.dl-head-mark {
  flex: 0 0 auto; width: 3.6rem; height: 3.6rem; border-radius: 1rem;
  display: grid; place-items: center; font-size: 1.4rem;
  background: var(--ts-brand-s); color: var(--ts-brand-t);
}
.dl-head-txt { flex: 1 1 20rem; min-width: 0; }
.dl-head-txt h1 {
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem); font-weight: 800;
  letter-spacing: -.02em; color: var(--ts-ink); margin: 0 0 .25rem;
}
.dl-head-txt p { margin: 0; font-size: .88rem; line-height: 1.6; color: var(--ts-muted); }
.dl-head-side { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }

/* ------------------------------------------------------------------- a note */

/* The retention notice, and anything else the page has to say once. A tinted
   panel rather than a line of small print: it is telling somebody their URL
   will be deleted, which is not a footnote. */
.dl-note {
  display: flex; gap: .85rem; align-items: flex-start;
  padding: .9rem 1.1rem; border-radius: .9rem; margin-bottom: 1.1rem;
  background: var(--ts-brand-s); border: 1px solid transparent;
  font-size: .84rem; line-height: 1.6; color: var(--ts-ink);
}
.dl-note .ico {
  flex: 0 0 auto; width: 1.6rem; height: 1.6rem; border-radius: 50%;
  display: grid; place-items: center; font-size: .78rem;
  background: var(--ts-card); color: var(--ts-brand-t);
}
.dl-note strong { color: var(--ts-brand-t); font-weight: 750; }
[data-bs-theme="dark"] .dl-note { border-color: var(--ts-line); }

/* --------------------------------------------------------- an endpoint card */

.dl-ep {
  background: var(--ts-card); border: 1px solid var(--ts-line); border-radius: 1.1rem;
  padding: 1.1rem 1.2rem; margin-bottom: .9rem;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.dl-ep:hover { border-color: color-mix(in srgb, var(--ts-brand) 28%, var(--ts-line));
  box-shadow: 0 .7rem 1.6rem rgba(15, 23, 42, .07); }
[data-bs-theme="dark"] .dl-ep:hover { box-shadow: 0 .7rem 1.6rem rgba(0, 0, 0, .3); }

/* A paused endpoint answers nothing, so it is drawn as switched off rather than
   merely faded: the old rule was opacity .6 on the whole card, which dimmed the
   URL and the deletion date too - the two things that are still true. */
.dl-ep.is-off { background: var(--ts-soft); }
.dl-ep.is-off .dl-ep-name { color: var(--ts-muted); }

/* Four zones on a wide screen, and they collapse in a deliberate order rather
   than by accident: the URL keeps the full width it needs for as long as
   possible, because it is the reason the page exists. */
.dl-ep-row {
  display: grid; gap: 1rem 1.1rem; align-items: center;
  grid-template-columns: auto minmax(11rem, 1.1fr) minmax(0, 1.6fr) auto;
}

/* the mark ---------------------------------------------------------------- */
.dl-ep-mark {
  width: 3rem; height: 3rem; border-radius: .85rem; display: grid; place-items: center;
  font-size: .92rem; font-weight: 800; letter-spacing: .02em;
  background: var(--dl-tone-bg, var(--ts-brand-s)); color: var(--dl-tone-fg, var(--ts-brand-t));
  user-select: none;
}
/* Six tones, picked from the endpoint name so the same listener keeps the same
   colour between visits. Declared here, not taken from the --jch-ti-* set that
   app.js injects at runtime - a tile that is grey until a script arrives looks
   broken on every hard refresh. */
.dl-tone-0 { --dl-tone-bg: rgba(139, 92, 246, .14); --dl-tone-fg: #6d28d9; }
.dl-tone-1 { --dl-tone-bg: rgba(34, 197, 94, .14);  --dl-tone-fg: #15803d; }
.dl-tone-2 { --dl-tone-bg: rgba(14, 165, 233, .14); --dl-tone-fg: #0369a1; }
.dl-tone-3 { --dl-tone-bg: rgba(245, 158, 11, .16); --dl-tone-fg: #b45309; }
.dl-tone-4 { --dl-tone-bg: rgba(244, 63, 94, .13);  --dl-tone-fg: #be123c; }
.dl-tone-5 { --dl-tone-bg: rgba(20, 184, 166, .15); --dl-tone-fg: #0f766e; }
[data-bs-theme="dark"] .dl-tone-0 { --dl-tone-fg: #c4b5fd; }
[data-bs-theme="dark"] .dl-tone-1 { --dl-tone-fg: #86efac; }
[data-bs-theme="dark"] .dl-tone-2 { --dl-tone-fg: #7dd3fc; }
[data-bs-theme="dark"] .dl-tone-3 { --dl-tone-fg: #fcd34d; }
[data-bs-theme="dark"] .dl-tone-4 { --dl-tone-fg: #fda4af; }
[data-bs-theme="dark"] .dl-tone-5 { --dl-tone-fg: #5eead4; }

/* identity ---------------------------------------------------------------- */
.dl-ep-id { min-width: 0; }
.dl-ep-title { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
/* Live or paused, in the one place the eye already is. A dot rather than a word,
   with the word carried on the pill beside it when it is off. */
.dl-ep-dot {
  flex: 0 0 auto; width: .5rem; height: .5rem; border-radius: 50%;
  background: #16a34a; box-shadow: 0 0 0 3px rgba(22, 163, 74, .16);
}
.dl-ep.is-off .dl-ep-dot { background: var(--ts-faint); box-shadow: none; }
.dl-ep-name {
  font-weight: 750; font-size: .98rem; color: var(--ts-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 14rem;
}
.dl-pill {
  font-size: .66rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  padding: .2rem .5rem; border-radius: .45rem;
  background: var(--dl-tone-bg, var(--ts-brand-s)); color: var(--dl-tone-fg, var(--ts-brand-t));
}
.dl-pill.is-public  { --dl-tone-bg: rgba(34, 197, 94, .14);  --dl-tone-fg: #15803d; }
.dl-pill.is-private { --dl-tone-bg: rgba(139, 92, 246, .14); --dl-tone-fg: #6d28d9; }
.dl-pill.is-protected { --dl-tone-bg: rgba(245, 158, 11, .16); --dl-tone-fg: #b45309; }
.dl-pill.is-paused  { --dl-tone-bg: rgba(100, 116, 139, .16); --dl-tone-fg: #475569; }
[data-bs-theme="dark"] .dl-pill.is-public { --dl-tone-fg: #86efac; }
[data-bs-theme="dark"] .dl-pill.is-private { --dl-tone-fg: #c4b5fd; }
[data-bs-theme="dark"] .dl-pill.is-protected { --dl-tone-fg: #fcd34d; }
[data-bs-theme="dark"] .dl-pill.is-paused { --dl-tone-fg: #cbd5e1; }

.dl-ep-meta { display: flex; flex-direction: column; gap: .15rem; margin-top: .4rem; }
.dl-ep-line {
  display: flex; align-items: center; gap: .4rem;
  font-size: .78rem; line-height: 1.5; color: var(--ts-muted);
}
.dl-ep-line .fa-solid, .dl-ep-line .fa-regular, .dl-ep-line svg { font-size: .72rem; opacity: .85; }
/* The deletion line takes its colour from how close it is. Plain text, as in the
   design, rather than a badge - but a date that is hours away must not read the
   same as one three months away. */
.dl-ep-line.is-soon { color: #b45309; font-weight: 650; }
.dl-ep-line.is-urgent { color: #be123c; font-weight: 700; }
[data-bs-theme="dark"] .dl-ep-line.is-soon { color: #fcd34d; }
[data-bs-theme="dark"] .dl-ep-line.is-urgent { color: #fda4af; }

/* the url ----------------------------------------------------------------- */
.dl-ep-url { min-width: 0; }
.dl-ep-url .lbl {
  display: block; font-size: .62rem; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ts-faint); margin-bottom: .3rem;
}
.dl-url-field {
  display: flex; align-items: stretch; gap: .5rem;
  background: var(--ts-soft); border: 1px solid var(--ts-line); border-radius: .7rem;
  padding: .1rem .1rem .1rem .7rem;
}
.dl-ep.is-off .dl-url-field { background: var(--ts-card); }
.dl-url {
  flex: 1 1 auto; min-width: 0; align-self: center;
  font-family: var(--phoenix-font-monospace, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: .78rem; color: var(--ts-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dl-url-copy {
  flex: 0 0 auto; width: 2.3rem; border: 0; border-radius: .6rem; cursor: pointer;
  background: transparent; color: var(--ts-muted);
  display: grid; place-items: center;
  transition: background .14s ease, color .14s ease;
}
.dl-url-copy:hover { background: var(--ts-brand-s); color: var(--ts-brand-t); }
.dl-url-copy:focus-visible { outline: 2px solid var(--ts-brand-t); outline-offset: -2px; }

/* actions ----------------------------------------------------------------- */
.dl-ep-acts { display: flex; gap: .45rem; flex: 0 0 auto; }
/* Icon over label, in a tinted square. Four text buttons in a row made Delete
   look exactly like Logs; a colour each is what separates "read this" from
   "destroy this" at a glance, before the label is read at all. */
.dl-act {
  width: 4rem; padding: .5rem .25rem; border-radius: .8rem; cursor: pointer;
  border: 1px solid transparent; background: var(--dl-a-bg); color: var(--dl-a-fg);
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  font-size: .68rem; font-weight: 700; line-height: 1.1; text-decoration: none;
  transition: transform .14s ease, box-shadow .14s ease, background .14s ease;
}
.dl-act .ico { font-size: .95rem; }
.dl-act:hover { transform: translateY(-2px); color: var(--dl-a-fg);
  box-shadow: 0 .4rem .9rem color-mix(in srgb, var(--dl-a-fg) 22%, transparent); }
.dl-act:focus-visible { outline: 2px solid var(--dl-a-fg); outline-offset: 2px; }
.dl-act.is-blue  { --dl-a-bg: rgba(14, 165, 233, .12);  --dl-a-fg: #0369a1; }
.dl-act.is-amber { --dl-a-bg: rgba(245, 158, 11, .14);  --dl-a-fg: #b45309; }
.dl-act.is-violet{ --dl-a-bg: rgba(139, 92, 246, .13);  --dl-a-fg: #6d28d9; }
.dl-act.is-red   { --dl-a-bg: rgba(244, 63, 94, .11);   --dl-a-fg: #be123c; }
[data-bs-theme="dark"] .dl-act.is-blue   { --dl-a-fg: #7dd3fc; }
[data-bs-theme="dark"] .dl-act.is-amber  { --dl-a-fg: #fcd34d; }
[data-bs-theme="dark"] .dl-act.is-violet { --dl-a-fg: #c4b5fd; }
[data-bs-theme="dark"] .dl-act.is-red    { --dl-a-fg: #fda4af; }

/* the editor, when a card is expanded ------------------------------------- */
.dl-ep-editor {
  margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px dashed var(--ts-line);
}

/* ------------------------------------------------------------------ the tip */
.dl-tip {
  display: flex; gap: .85rem; align-items: flex-start; margin-top: 1.2rem;
  padding: .9rem 1.1rem; border-radius: .9rem;
  background: var(--ts-soft); border: 1px solid var(--ts-line);
  font-size: .82rem; line-height: 1.6; color: var(--ts-muted);
}
.dl-tip .ico {
  flex: 0 0 auto; width: 1.7rem; height: 1.7rem; border-radius: 50%;
  display: grid; place-items: center; font-size: .8rem;
  background: rgba(245, 158, 11, .16); color: #b45309;
}
[data-bs-theme="dark"] .dl-tip .ico { color: #fcd34d; }
.dl-tip a { font-weight: 700; color: var(--ts-brand-t); text-decoration: none; }
.dl-tip a:hover { text-decoration: underline; }

/* ---------------------------------------------------------- the empty state */
.dl-empty {
  text-align: center; padding: 3rem 1rem;
  border: 1px dashed var(--ts-line); border-radius: 1.1rem; background: var(--ts-soft);
}
.dl-empty .ico {
  width: 3.4rem; height: 3.4rem; border-radius: 1rem; margin: 0 auto .9rem;
  display: grid; place-items: center; font-size: 1.3rem;
  background: var(--ts-brand-s); color: var(--ts-brand-t);
}
.dl-empty p { color: var(--ts-muted); font-size: .88rem; margin-bottom: 1rem; }

/* ------------------------------------------------------------------ narrow */

/* 1200px: the actions drop under the URL, which keeps both readable rather than
   squeezing four 4rem buttons against a truncated address. */
@media (max-width: 1199.98px) {
  .dl-ep-row { grid-template-columns: auto minmax(0, 1fr); }
  .dl-ep-url { grid-column: 1 / -1; }
  .dl-ep-acts { grid-column: 1 / -1; justify-content: flex-start; flex-wrap: wrap; }
}

/* 576px: the mark goes - two initials are decoration once there is one card per
   screen - and the actions become a four-across row that fills the width, so
   each stays a comfortable tap target instead of shrinking. */
@media (max-width: 575.98px) {
  .dl-ep { padding: 1rem; }
  .dl-ep-row { grid-template-columns: minmax(0, 1fr); }
  .dl-ep-mark { display: none; }
  .dl-ep-name { max-width: none; white-space: normal; }
  .dl-ep-acts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .4rem; }
  .dl-act { width: auto; }
  .dl-head-side { width: 100%; }
  .dl-head-side .ts-btn { flex: 1 1 auto; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .dl-back .ico, .dl-act, .dl-ep, .dl-url-copy { transition: none; }
  .dl-act:hover, .dl-back:hover .ico { transform: none; }
}

/* ============================================================================
   Request Logs
   ----------------------------------------------------------------------------
   The listing was a Bootstrap card wrapped round a .table-sm: every column the
   same weight, the status code a generic badge, and the expanded detail a flat
   run of four <pre> blocks with the delete button under them.

   The page is a debugging tool, and the way it gets used is scan-then-open: run
   the eye down a column looking for the row that is wrong, open it, read a body.
   So the columns sort and the cells are toned - a 500 must not look like a 200 -
   and the opened row gets a rail of tabs, because "the request" and "the
   response" are two questions and only one of them is being asked at a time.

   Everything here is built from the --ts-* tokens .tstudio defines, so it
   inherits dark mode from the skin rather than declaring it a second time.
   ========================================================================== */

/* ------------------------------------------------------------- the filters */

.dl-filters {
  background: var(--ts-card); border: 1px solid var(--ts-line); border-radius: 1.1rem;
  padding: 1.1rem 1.2rem; margin-bottom: 1.1rem;
}
/* A twelve-column grid rather than Bootstrap's row/col, because the six controls
   are six DIFFERENT widths - a method picker needs a third of what a date does -
   and expressing that in col-lg-* classes puts the layout in the markup, where
   one breakpoint cannot be changed without editing every field. */
.dl-fgrid { display: grid; gap: .85rem 1rem; grid-template-columns: repeat(12, minmax(0, 1fr)); }
.dl-f { min-width: 0; }
.dl-f.span-3 { grid-column: span 3; }
.dl-f.span-2 { grid-column: span 2; }
.dl-f.span-1 { grid-column: span 1; }
.dl-f.span-12 { grid-column: 1 / -1; }

.dl-f label, .dl-f-lbl {
  display: block; font-size: .74rem; font-weight: 650; color: var(--ts-muted);
  margin-bottom: .35rem;
}
.dl-input {
  width: 100%; border: 1px solid var(--ts-line); border-radius: .65rem;
  background: var(--ts-card); color: var(--ts-ink);
  padding: .5rem .7rem; font-size: .83rem; line-height: 1.4;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.dl-input::placeholder { color: var(--ts-faint); }
.dl-input:focus {
  outline: none; border-color: var(--ts-brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ts-brand) 18%, transparent);
}
/* The native arrow is a different shape in every browser and the wrong colour in
   dark mode. One chevron, drawn here, so the six controls match each other. */
select.dl-input {
  appearance: none; -webkit-appearance: none; padding-right: 2.1rem; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2394a3b8' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .7rem center; background-size: .95rem;
}
/* The date pickers are native, and a native picker draws its own calendar glyph
   and its own text. Without this it draws both for a light page on a dark card. */
[data-bs-theme="dark"] .dl-input { color-scheme: dark; }

.dl-search { position: relative; }
.dl-search .ico {
  position: absolute; left: .8rem; top: 50%; transform: translateY(-50%);
  font-size: .8rem; color: var(--ts-faint); pointer-events: none;
}
.dl-search .dl-input { padding-left: 2.2rem; }
/* Says what the box does NOT cover. Bodies live in their own table so the listing
   query never touches them, and a search that silently misses the thing people
   most expect it to find is worse than one that admits it. */
.dl-fhint { margin-top: .4rem; font-size: .74rem; line-height: 1.5; color: var(--ts-faint); }

/* Clear sits on the same row as five labelled fields, so it has to line up with
   the bottom of a control rather than with a label it does not have. */
.dl-f.is-action { display: flex; align-items: flex-end; }
.dl-f.is-action .ts-btn { width: 100%; justify-content: center; }

/* --------------------------------------------------------------- the table */

.dl-tablewrap {
  background: var(--ts-card); border: 1px solid var(--ts-line); border-radius: 1.1rem;
  overflow: hidden;
}
/* The horizontal scroll goes on an inner element, not the card, so the rounded
   corners and the border stay put while eight columns slide underneath them. */
.dl-scroll { overflow-x: auto; }
table.dl-table { width: 100%; margin: 0; border-collapse: separate; border-spacing: 0; }
.dl-table th, .dl-table td { padding: .65rem .8rem; text-align: left; }
.dl-table thead th {
  background: var(--ts-head); border-bottom: 1px solid var(--ts-line);
  font-size: .78rem; font-weight: 700; color: var(--ts-ink); white-space: nowrap;
}
.dl-table tbody td {
  border-bottom: 1px solid var(--ts-line); font-size: .81rem; color: var(--ts-ink);
  vertical-align: middle;
}
.dl-table tbody tr:last-child td { border-bottom: 0; }
/* The expansion is a table cell and must not wear a cell's padding: the panel inside it sets its
   own, and two of them stacked gives the opened row a 2rem gutter the closed ones do not have. */
.dl-table td.dl-detail-cell { padding: 0; background: var(--ts-card); }

/* A header that sorts is a button, not a th with a click handler: it is reached by
   Tab, fired by Enter and announced as pressable, none of which a bare cell is. */
.dl-sort {
  display: inline-flex; align-items: center; gap: .4rem;
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; color: inherit;
}
.dl-sort:hover { color: var(--ts-brand-t); }
.dl-sort:focus-visible { outline: 2px solid var(--ts-brand); outline-offset: 3px; border-radius: .3rem; }
/* Unsorted shows both arrows at low contrast - "this can be sorted". Sorted shows
   one, in the brand colour, so which column drives the order is unmissable. */
.dl-sort .car { display: grid; line-height: .55; font-size: .5rem; color: var(--ts-faint); }
.dl-sort.is-on { color: var(--ts-brand-t); }
.dl-sort.is-on .car { color: var(--ts-brand-t); font-size: .6rem; line-height: 1; }

.dl-row { cursor: pointer; transition: background-color .12s ease; }
.dl-row:hover { background: var(--ts-soft); }
.dl-row.is-open { background: var(--ts-brand-s); }
.dl-row.is-open td { border-bottom-color: transparent; }

/* the caret, the tick, the row menu ---------------------------------------- */
.dl-caret, .dl-rowmenu {
  width: 1.8rem; height: 1.8rem; border-radius: .5rem; cursor: pointer;
  border: 1px solid transparent; background: transparent; color: var(--ts-muted);
  display: grid; place-items: center; font-size: .72rem;
  transition: background-color .14s ease, color .14s ease, transform .18s ease;
}
.dl-caret:hover, .dl-rowmenu:hover {
  background: var(--ts-card); border-color: var(--ts-line); color: var(--ts-ink);
}
.dl-row.is-open .dl-caret { transform: rotate(180deg); color: var(--ts-brand-t); }
.dl-tick {
  width: .95rem; height: .95rem; accent-color: var(--ts-brand);
  cursor: pointer; vertical-align: middle;
}

/* the cells ---------------------------------------------------------------- */
.dl-when { white-space: nowrap; color: var(--ts-muted); font-variant-numeric: tabular-nums; }
/* One tone per verb. A page of webhook traffic is mostly POST with the odd GET,
   and colour is what makes the odd one findable without reading the column. */
.dl-meth {
  font-weight: 800; font-size: .76rem; letter-spacing: .03em;
  color: var(--dl-tone-fg, var(--ts-brand-t));
}
.dl-meth.m-get    { --dl-tone-fg: #4f46e5; }
.dl-meth.m-post   { --dl-tone-fg: #15803d; }
.dl-meth.m-put    { --dl-tone-fg: #b45309; }
.dl-meth.m-patch  { --dl-tone-fg: #6d28d9; }
.dl-meth.m-delete { --dl-tone-fg: #be123c; }
[data-bs-theme="dark"] .dl-meth.m-get    { --dl-tone-fg: #a5b4fc; }
[data-bs-theme="dark"] .dl-meth.m-post   { --dl-tone-fg: #86efac; }
[data-bs-theme="dark"] .dl-meth.m-put    { --dl-tone-fg: #fcd34d; }
[data-bs-theme="dark"] .dl-meth.m-patch  { --dl-tone-fg: #c4b5fd; }
[data-bs-theme="dark"] .dl-meth.m-delete { --dl-tone-fg: #fda4af; }

.dl-epname {
  color: var(--ts-brand-t); font-weight: 650;
  display: inline-block; max-width: 12rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: middle;
}

/* The status code, drawn as its class. tabular-nums and a min-width, so a column
   of them is a column of aligned digits rather than a ragged edge. */
.dl-status {
  display: inline-block; min-width: 2.9rem; text-align: center;
  padding: .2rem .5rem; border-radius: .5rem;
  font-size: .74rem; font-weight: 800; font-variant-numeric: tabular-nums;
  background: var(--dl-tone-bg); color: var(--dl-tone-fg);
}
.dl-status.s-ok   { --dl-tone-bg: rgba(34, 197, 94, .14);  --dl-tone-fg: #15803d; }
.dl-status.s-info { --dl-tone-bg: rgba(14, 165, 233, .14); --dl-tone-fg: #0369a1; }
.dl-status.s-warn { --dl-tone-bg: rgba(245, 158, 11, .16); --dl-tone-fg: #b45309; }
.dl-status.s-bad  { --dl-tone-bg: rgba(244, 63, 94, .13);  --dl-tone-fg: #be123c; }
[data-bs-theme="dark"] .dl-status.s-ok   { --dl-tone-fg: #86efac; }
[data-bs-theme="dark"] .dl-status.s-info { --dl-tone-fg: #7dd3fc; }
[data-bs-theme="dark"] .dl-status.s-warn { --dl-tone-fg: #fcd34d; }
[data-bs-theme="dark"] .dl-status.s-bad  { --dl-tone-fg: #fda4af; }

/* The outcome in plain words, carrying the same class as a dot. The words are what
   it means; the dot is what makes a bad one findable at arm's length. */
.dl-outcome { display: inline-flex; align-items: center; gap: .45rem; white-space: nowrap; }
.dl-outcome i {
  flex: 0 0 auto; width: .45rem; height: .45rem; border-radius: 50%;
  background: currentColor; font-style: normal;
}
.dl-outcome.s-ok   { color: #15803d; }
.dl-outcome.s-info { color: #0369a1; }
.dl-outcome.s-warn { color: #b45309; }
.dl-outcome.s-bad  { color: #be123c; }
[data-bs-theme="dark"] .dl-outcome.s-ok   { color: #86efac; }
[data-bs-theme="dark"] .dl-outcome.s-info { color: #7dd3fc; }
[data-bs-theme="dark"] .dl-outcome.s-warn { color: #fcd34d; }
[data-bs-theme="dark"] .dl-outcome.s-bad  { color: #fda4af; }

.dl-num { white-space: nowrap; color: var(--ts-muted); font-variant-numeric: tabular-nums; }

/* the row menu ------------------------------------------------------------- */

/* FIXED, and appended to <body> rather than positioned inside the row. The table
   lives in an overflow-x scroller, and a menu positioned inside one is clipped by
   it - which on a narrow window means the menu opens and cannot be seen. */
.dl-menu {
  position: fixed; z-index: 1080; min-width: 13rem; padding: .35rem;
  background: var(--ts-card); border: 1px solid var(--ts-line); border-radius: .8rem;
  box-shadow: 0 .9rem 2rem rgba(15, 23, 42, .16);
}
[data-bs-theme="dark"] .dl-menu { box-shadow: 0 .9rem 2rem rgba(0, 0, 0, .5); }
.dl-menu button {
  display: flex; align-items: center; gap: .65rem; width: 100%; text-align: left;
  border: 0; background: none; cursor: pointer; border-radius: .55rem;
  padding: .5rem .65rem; font-size: .82rem; color: var(--ts-ink);
}
.dl-menu button .ico { width: 1rem; text-align: center; color: var(--ts-faint); font-size: .78rem; }
.dl-menu button:hover { background: var(--ts-soft); }
.dl-menu button.is-danger { color: #be123c; }
.dl-menu button.is-danger .ico { color: #be123c; }
.dl-menu button.is-danger:hover { background: rgba(244, 63, 94, .1); }
[data-bs-theme="dark"] .dl-menu button.is-danger,
[data-bs-theme="dark"] .dl-menu button.is-danger .ico { color: #fda4af; }
.dl-menu hr { margin: .3rem .4rem; border: 0; border-top: 1px solid var(--ts-line); }

/* ------------------------------------------------------- the opened request */

.dl-detail { display: flex; gap: 1.2rem; padding: 1.1rem 1.2rem 1.3rem; }
/* Icon over label, in a rail down the left. Four tabs across the top of a panel
   that is itself inside a table row reads as a second row of column headers. */
.dl-tabs {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: .25rem;
  width: 5rem; padding-right: 1rem; border-right: 1px solid var(--ts-line);
}
.dl-tab {
  border: 0; background: none; cursor: pointer; border-radius: .7rem;
  padding: .6rem .3rem; display: flex; flex-direction: column; align-items: center; gap: .35rem;
  font-size: .68rem; font-weight: 700; line-height: 1.2; color: var(--ts-muted);
  transition: background-color .14s ease, color .14s ease;
}
.dl-tab .ico { font-size: 1.05rem; }
.dl-tab:hover { background: var(--ts-soft); color: var(--ts-ink); }
.dl-tab.is-on { background: var(--ts-brand-s); color: var(--ts-brand-t); }
.dl-tab:focus-visible { outline: 2px solid var(--ts-brand); outline-offset: 2px; }

.dl-panes { flex: 1 1 auto; min-width: 0; }

/* the facts strip ---------------------------------------------------------- */
.dl-meta {
  display: grid; gap: .9rem 1.5rem; margin-bottom: 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
.dl-meta .k { display: block; font-size: .73rem; color: var(--ts-muted); margin-bottom: .15rem; }
.dl-meta .v {
  display: block; font-size: .79rem; color: var(--ts-ink); word-break: break-all;
  font-family: var(--phoenix-font-monospace, ui-monospace, SFMono-Regular, Menlo, monospace);
}
.dl-meta .v.plain { font-family: inherit; word-break: normal; }

/* a code panel ------------------------------------------------------------- */
.dl-panels { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }
.dl-panels.is-single { grid-template-columns: minmax(0, 1fr); }
.dl-panel { min-width: 0; }
.dl-panel > .h { display: flex; align-items: baseline; gap: .5rem; margin-bottom: .4rem; }
.dl-panel > .h b { font-size: .79rem; font-weight: 700; color: var(--ts-ink); }
.dl-panel > .h .n { font-size: .72rem; color: var(--ts-faint); }
.dl-code {
  position: relative;
  background: var(--ts-soft); border: 1px solid var(--ts-line); border-radius: .75rem;
}
.dl-code pre {
  margin: 0; padding: .7rem 2.6rem .7rem .85rem;
  white-space: pre-wrap; word-break: break-word;
  max-height: 16rem; overflow: auto;
  font-family: var(--phoenix-font-monospace, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: .765rem; line-height: 1.65; color: var(--ts-ink);
}
.dl-code.is-tall pre { max-height: 30rem; }
.dl-code .empty { color: var(--ts-faint); font-style: italic; }
/* Over the scroller rather than in the heading, so it stays reachable when a body
   is long enough that the heading has scrolled out of the pane. */
.dl-copy {
  position: absolute; top: .4rem; right: .4rem;
  width: 1.8rem; height: 1.8rem; border: 0; border-radius: .5rem; cursor: pointer;
  background: transparent; color: var(--ts-faint);
  display: grid; place-items: center; font-size: .78rem;
  transition: background-color .14s ease, color .14s ease;
}
.dl-copy:hover { background: var(--ts-brand-s); color: var(--ts-brand-t); }
.dl-copy.is-done { color: #15803d; background: rgba(34, 197, 94, .14); }
[data-bs-theme="dark"] .dl-copy.is-done { color: #86efac; }
/* Both glyphs ship in the button and the class picks one, rather than the tick being written in
   on click. The FontAwesome build on these pages replaces every icon element with an <svg> of its
   own making, so markup swapped in afterwards is markup that arrives as a bare empty span - and a
   copy button that goes blank for a second and a half reads as a copy that failed. FA carries the
   extra classes through onto the <svg> it generates, so these selectors survive the rewrite. */
.dl-copy .i-done { display: none; }
.dl-copy.is-done .i-copy { display: none; }
.dl-copy.is-done .i-done { display: inline-block; }

.dl-detail-foot { margin-top: 1.2rem; display: flex; flex-wrap: wrap; gap: .6rem; }

/* --------------------------------------------------------------- the pager */

.dl-pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-top: 1.1rem;
}
.dl-pager .n { font-size: .81rem; color: var(--ts-muted); }
.dl-pages { display: flex; align-items: center; gap: .35rem; }
.dl-page {
  min-width: 2.2rem; height: 2.2rem; padding: 0 .75rem;
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  border: 1px solid var(--ts-line); border-radius: .6rem; cursor: pointer;
  background: var(--ts-card); color: var(--ts-ink); font-size: .8rem; font-weight: 650;
  transition: border-color .14s ease, color .14s ease;
}
.dl-page:hover:not(:disabled):not(.is-on) { border-color: var(--ts-brand-t); color: var(--ts-brand-t); }
.dl-page:disabled { opacity: .45; cursor: not-allowed; }
.dl-page.is-on { background: var(--ts-brand); border-color: var(--ts-brand); color: #fff; }
.dl-page.is-gap {
  border-color: transparent; background: none; cursor: default;
  color: var(--ts-faint); padding: 0; min-width: 1.2rem;
}

/* ------------------------------------------------------------------ narrow */

/* 1400px: still six across, but even. A 1/12 column is about 70px once the page
   has narrowed this far, and "Clear" plus its glyph does not fit in 70px - the
   label wraps under the icon and the button grows to two lines. */
@media (max-width: 1399.98px) {
  .dl-f.span-3, .dl-f.span-1 { grid-column: span 2; }
}

/* 1200px: the filter grid loses its six-across row. Three fields per line keeps
   every label above its own control instead of wrapping under the neighbour's. */
@media (max-width: 1199.98px) {
  .dl-f.span-3, .dl-f.span-2, .dl-f.span-1 { grid-column: span 4; }
}
@media (max-width: 767.98px) {
  .dl-f.span-3, .dl-f.span-2, .dl-f.span-1 { grid-column: span 6; }
  .dl-filters { padding: 1rem; }
  /* The rail becomes a strip. A 5rem column beside a 320px screen leaves the body
     panels too narrow to read, and the tabs are four items - they fit across. */
  .dl-detail { flex-direction: column; gap: .9rem; padding: 1rem; }
  .dl-tabs {
    flex-direction: row; width: auto; padding-right: 0; padding-bottom: .7rem;
    border-right: 0; border-bottom: 1px solid var(--ts-line); overflow-x: auto;
  }
  .dl-tab { flex: 1 0 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .dl-row, .dl-caret, .dl-rowmenu, .dl-tab, .dl-copy, .dl-page, .dl-input { transition: none; }
  .dl-row.is-open .dl-caret { transform: none; }
}

/* ============================================================================
   A toast
   ----------------------------------------------------------------------------
   A confirmation that does not depend on where the page is scrolled. Saving an
   endpoint happens in the Configure panel, which on a page with several
   endpoints is a long way down; the banner at the top of the page confirmed it
   somewhere the reader was not looking, and then the panel stayed open, so the
   only visible outcome of pressing Save was nothing at all.

   Errors deliberately do NOT come through here. A toast is gone in four seconds
   and a failed save is something the reader has to act on, so #dl-error keeps
   its banner - transient is the right shape for "done", not for "did not".

   A SECOND COPY, knowingly. The same component exists as .jch-toast in
   css/project-doc.css, and the two pages share no stylesheet: the only sheet
   both load is tool-studio.css, the 41-page studio skin, and neither this nor
   the project dashboard is reason enough to put page furniture in there and
   move that cache key. Kept visually identical so the site has one toast, and
   named apart so the duplication is visible to whoever unifies them.
   ========================================================================== */
.dl-toast {
  position: fixed; z-index: 1090; left: 50%; bottom: 24px; transform: translateX(-50%);
  display: flex; align-items: center; gap: .6rem; max-width: min(92vw, 30rem);
  background: #16143a; color: #fff; border-radius: 8px;
  padding: .7rem 1rem; font-size: .875rem; font-weight: 600;
  box-shadow: 0 8px 30px rgba(22, 20, 58, .35);
  animation: dl-toast-in .22s cubic-bezier(.2, 0, 0, 1);
}
.dl-toast .fa-solid { color: #3ddc9a; }
.dl-toast.is-out { opacity: 0; transition: opacity .25s ease; }
@keyframes dl-toast-in { from { opacity: 0; transform: translate(-50%, 10px); }
                         to   { opacity: 1; transform: translate(-50%, 0); } }
@media (prefers-reduced-motion: reduce) {
  .dl-toast { animation: none; }
  .dl-toast.is-out { transition: none; }
}

/* ---------------------------------------------------------------- the gate */

/* Shown in place of the whole console when the server answers 402. Built like
   .dl-empty rather than as an alert strip, because it is not a message about a
   failed action - it is the page, for anyone who has not bought the feature.
   The tone is deliberately not a warning: nothing has gone wrong, and their
   endpoints are still capturing while they decide. */
.dl-gate {
  text-align: center; padding: 3.2rem 1.25rem;
  border: 1px solid var(--ts-line); border-radius: 1.1rem; background: var(--ts-card);
}
.dl-gate .ico {
  width: 3.6rem; height: 3.6rem; border-radius: 1rem; margin: 0 auto 1rem;
  display: grid; place-items: center; font-size: 1.4rem;
  background: var(--ts-brand-s); color: var(--ts-brand-t);
}
.dl-gate h2 {
  margin: 0 0 .5rem; font-size: 1.25rem; font-weight: 800;
  letter-spacing: -.02em; color: var(--ts-ink);
}
.dl-gate p {
  max-width: 34rem; margin: 0 auto 1.25rem;
  font-size: .88rem; line-height: 1.65; color: var(--ts-muted);
}
/* The reassurance, quieter than the offer above it. */
.dl-gate .dl-gate-note {
  margin: 1.25rem auto 0; max-width: 30rem;
  font-size: .78rem; line-height: 1.6; color: var(--ts-faint);
}

/* ============================================================================
   The Developer Lab shell (.dl-shell - NOT .dl-page, which is the Request Logs pager button)
   ----------------------------------------------------------------------------
   Every Lab tool page is now the same shape: a rail of the Lab's tools down
   the left, a breadcrumb, and the page's own content to the right. The rail
   is rendered by app.js (renderDevLabShell) from the same list the navbar's
   mega-menu reads, so the two cannot list different tools or different names.

   The three sandboxes keep their full-width IDE layout and do not mount it.
   ========================================================================== */

.dl-shell { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.25rem; align-items: start; }
@media (min-width: 992px) {
  .dl-shell { grid-template-columns: 16.5rem minmax(0, 1fr); }
  #dl-rail { position: sticky; top: 5.25rem; }
}
.dl-shell-main { min-width: 0; }

/* ------------------------------------------------------------------- collapsing the rail
   The rail is 16.5rem of a working area that pages like the Kafka producer need for three
   columns of controls. One click folds it to its icons and the page keeps the width; the choice
   is remembered across every Lab page. Desktop only: below the lg breakpoint the rail is already
   a horizontal strip above the content. */
.dl-rail { position: relative; }
.dl-rail-toggle {
  position: absolute; right: -.8rem; top: 1.15rem; width: 1.6rem; height: 1.6rem; border-radius: 50%;
  border: 1px solid var(--ts-line); background: var(--ts-card); color: var(--ts-muted);
  display: none; place-items: center; font-size: .6rem; cursor: pointer; padding: 0; z-index: 3;
  box-shadow: 0 1px 3px rgba(15, 23, 42, .14); transition: color .12s ease, border-color .12s ease;
}
.dl-rail-toggle:hover, .dl-rail-toggle:focus-visible { color: var(--ts-brand-t); border-color: var(--ts-brand-t); outline: none; }
@media (min-width: 992px) {
  .dl-rail-toggle { display: grid; }
  .dl-shell.is-rail-collapsed { grid-template-columns: 4.4rem minmax(0, 1fr); }
  .is-rail-collapsed .dl-rail { padding: .6rem .55rem; }
  .is-rail-collapsed .dl-rail-head { justify-content: center; padding: .25rem 0 .7rem; gap: 0; }
  .is-rail-collapsed .dl-rail-head > span:not(.dl-rail-mark),
  .is-rail-collapsed .dl-rail-lbl,
  .is-rail-collapsed .dl-rail-chev,
  .is-rail-collapsed .dl-rail-pro-txt,
  .is-rail-collapsed .dl-rail-pro-chev { display: none; }
  .is-rail-collapsed .dl-rail-link { justify-content: center; padding: .35rem; }
  .is-rail-collapsed .dl-rail-pro { justify-content: center; padding: .55rem; }
  .is-rail-collapsed .dl-rail-toggle .fa-solid { transform: rotate(180deg); }
}
/* The old "← Developer Lab" link above each title is what the breadcrumb replaced. Hidden here
   rather than removed from nine pages: one rule, and the anchor stays for anything that reads
   it (the SOAP variant of the listener page rewrites its neighbours by id). */
.dl-shell .dl-back { display: none; }

/* ---- the rail ---- */
.dl-rail {
  display: flex; flex-direction: column; gap: .25rem;
  background: var(--ts-card); border: 1px solid var(--ts-line); border-radius: 1.1rem;
  padding: .85rem; box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
}
.dl-rail-head {
  display: flex; align-items: center; gap: .7rem; padding: .35rem .35rem .85rem;
  text-decoration: none; color: inherit; border-bottom: 1px solid var(--ts-line); margin-bottom: .5rem;
}
.dl-rail-head:hover { color: inherit; }
.dl-rail-mark {
  flex: 0 0 auto; width: 2.6rem; height: 2.6rem; border-radius: .85rem; display: grid; place-items: center;
  font-size: 1.05rem; color: #fff; background: linear-gradient(145deg, #6d5df0, #4f46e5);
  box-shadow: 0 6px 14px rgba(79, 70, 229, .28);
}
.dl-rail-head b { display: block; font-size: .98rem; font-weight: 800; letter-spacing: -.01em; color: var(--ts-ink); line-height: 1.2; }
.dl-rail-head i { display: block; font-style: normal; font-size: .7rem; color: var(--ts-muted); margin-top: .1rem; }

.dl-rail-nav { display: flex; flex-direction: column; gap: .15rem; }
.dl-rail-link {
  display: flex; align-items: center; gap: .65rem; padding: .45rem .5rem; border-radius: .75rem;
  text-decoration: none; color: var(--ts-ink); font-size: .84rem; font-weight: 600;
  transition: background .14s ease, color .14s ease;
}
.dl-rail-link:hover { background: var(--ts-soft); color: var(--ts-ink); }
.dl-rail-link:focus-visible { outline: 2px solid var(--ts-brand); outline-offset: 2px; }
.dl-rail-ti { width: 2.1rem; height: 2.1rem; border-radius: .65rem; margin-right: 0; }
.dl-rail-ti > * { font-size: .9rem; }
.dl-rail-lbl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-rail-chev { font-size: .6rem; color: var(--ts-faint); opacity: 0; transition: opacity .14s ease, transform .14s ease; }
.dl-rail-link:hover .dl-rail-chev { opacity: 1; transform: translateX(2px); }
/* The current tool: solid brand, white text, and an opaque tile so the glyph stays readable. */
.dl-rail-link.is-active { background: var(--ts-brand); color: #fff; box-shadow: 0 6px 14px rgba(79, 70, 229, .28); }
.dl-rail-link.is-active:hover { background: var(--ts-brand-d); color: #fff; }
.dl-rail-link.is-active .jch-ti { background: rgba(255, 255, 255, .92); }
.dl-rail-link.is-active .dl-rail-chev { opacity: 1; color: rgba(255, 255, 255, .85); }

/* The PRO card at the foot: the navbar's crown, said where a free-tier limit is felt. */
.dl-rail-pro {
  display: flex; align-items: center; gap: .65rem; margin-top: .6rem; padding: .7rem .75rem;
  border-radius: .9rem; text-decoration: none; color: #fff;
  background: linear-gradient(120deg, #7c3aed, #4f46e5 60%, #6366f1);
  box-shadow: 0 8px 18px rgba(99, 102, 241, .3); transition: transform .14s ease, box-shadow .14s ease;
}
.dl-rail-pro:hover { color: #fff; transform: translateY(-1px); box-shadow: 0 10px 22px rgba(99, 102, 241, .38); }
.dl-rail-pro-ico {
  flex: 0 0 auto; width: 2.1rem; height: 2.1rem; border-radius: .65rem; display: grid; place-items: center;
  background: rgba(255, 255, 255, .18); color: #fde68a; font-size: .95rem;
}
.dl-rail-pro-txt { flex: 1 1 auto; min-width: 0; }
.dl-rail-pro-txt b { display: block; font-size: .82rem; font-weight: 750; line-height: 1.2; }
.dl-rail-pro-txt i { display: block; font-style: normal; font-size: .68rem; opacity: .85; margin-top: .1rem; }
.dl-rail-pro-chev { font-size: .62rem; opacity: .8; }

/* Narrow: the rail becomes a strip of chips above the content, scrolling sideways. A vertical
   list of thirteen tools on a phone pushes the page it belongs to below the fold. */
@media (max-width: 991.98px) {
  .dl-rail { padding: .6rem; }
  .dl-rail-head { border-bottom: 0; margin-bottom: .1rem; padding-bottom: .35rem; }
  .dl-rail-nav { flex-direction: row; gap: .35rem; overflow-x: auto; padding-bottom: .25rem; scrollbar-width: thin; }
  .dl-rail-link { flex: 0 0 auto; padding: .35rem .6rem .35rem .4rem; font-size: .78rem; }
  .dl-rail-chev { display: none; }
  .dl-rail-pro { margin-top: .35rem; }
}

/* ---- the breadcrumb ---- */
.dl-crumbs { margin-bottom: .9rem; }
.dl-crumbs-list {
  list-style: none; margin: 0; padding: 0; display: flex; align-items: center; flex-wrap: wrap;
  gap: .45rem; font-size: .78rem; color: var(--ts-muted);
}
.dl-crumbs-list li { display: inline-flex; align-items: center; gap: .45rem; }
.dl-crumbs-list li + li::before { content: "\203A"; font-size: .95rem; line-height: 1; color: var(--ts-faint); }
.dl-crumbs-list a { color: var(--ts-muted); text-decoration: none; font-weight: 600; }
.dl-crumbs-list a:hover { color: var(--ts-brand-t); }
.dl-crumbs-list li[aria-current] { color: var(--ts-ink); font-weight: 700; }

/* ---- the hero card ----
   Inside the shell the page head becomes a card: a tinted panel, a large tile, the title and
   the one-paragraph explanation, and optionally a row of feature chips beneath. It is the same
   .dl-head markup every page already has, restyled - so nothing had to be re-authored. */
.dl-shell .dl-head {
  position: relative; overflow: hidden; gap: 1.1rem; align-items: center;
  padding: 1.35rem 1.4rem; border-radius: 1.2rem; margin-bottom: 1.1rem;
  background: linear-gradient(135deg, rgba(79, 70, 229, .10), rgba(14, 165, 233, .06) 55%, var(--ts-card));
  border: 1px solid color-mix(in srgb, var(--ts-brand) 18%, var(--ts-line));
}
.dl-shell .dl-head::after {
  content: ""; position: absolute; right: -4rem; top: -4rem; width: 12rem; height: 12rem; border-radius: 50%;
  background: radial-gradient(circle, rgba(79, 70, 229, .14), transparent 70%); pointer-events: none;
}
.dl-shell .dl-head-mark {
  width: 5.2rem; height: 5.2rem; border-radius: 1.3rem; font-size: 2rem; color: #fff;
  background: linear-gradient(145deg, #6d5df0, #4f46e5); box-shadow: 0 12px 24px rgba(79, 70, 229, .3);
}
.dl-shell .dl-head-txt h1 { font-size: clamp(1.5rem, 1.2rem + 1.1vw, 2rem); }
.dl-shell .dl-head-txt p { max-width: 46rem; }
.dl-chips { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .85rem; }
.dl-chip {
  display: inline-flex; align-items: center; gap: .4rem; padding: .32rem .7rem; border-radius: 999px;
  font-size: .72rem; font-weight: 650; color: var(--ts-ink);
  background: var(--ts-card); border: 1px solid var(--ts-line); box-shadow: 0 1px 2px rgba(15, 23, 42, .05);
}
.dl-chip .fa-solid { font-size: .7rem; color: var(--ts-brand-t); }
[data-bs-theme="dark"] .dl-shell .dl-head {
  background: linear-gradient(135deg, rgba(129, 140, 248, .14), rgba(56, 189, 248, .06) 55%, var(--ts-card));
}

@media (prefers-reduced-motion: reduce) {
  .dl-rail-link, .dl-rail-chev, .dl-rail-pro { transition: none; }
  .dl-rail-pro:hover { transform: none; }
}


/* ---- listener page: the cap line, the panel error, the editor's labels ---- */
.dl-cap { flex-basis: 100%; text-align: right; font-size: .74rem; color: var(--ts-muted); }
.dl-cap.is-full { color: #b45309; font-weight: 650; }
[data-bs-theme="dark"] .dl-cap.is-full { color: #fcd34d; }
@media (max-width: 991.98px) { .dl-cap { text-align: left; } }
.dl-ep-err { font-size: .8rem; font-weight: 600; color: #be123c; }
[data-bs-theme="dark"] .dl-ep-err { color: #fda4af; }
.dl-ep-line a { color: var(--ts-brand-t); font-weight: 700; text-decoration: none; }
.dl-ep-line a:hover { text-decoration: underline; }
.dl-ep-editor .dl-f-lbl { margin-bottom: .35rem; }
.dl-ep-editor textarea.dl-input { resize: vertical; }
.dl-modal-err { flex: 1 1 auto; font-size: .78rem; font-weight: 600; color: #be123c; }
[data-bs-theme="dark"] .dl-modal-err { color: #fda4af; }


/* ---- listener page: the SOAP-version pill and the editor's template row ---- */
.dl-pill.is-soap { --dl-tone-bg: rgba(99, 102, 241, .14); --dl-tone-fg: #4338ca; }
[data-bs-theme="dark"] .dl-pill.is-soap { --dl-tone-fg: #a5b4fc; }
.dl-tpl-row { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .75rem; }
.dl-tpl-row .dl-f-lbl { margin: 0; flex-basis: 100%; }
.dl-tpl-row select.dl-input { width: auto; min-width: 16rem; max-width: 100%; }


/* ---- request logs: three columns a phone can do without, and the Live toggle ---- */
@media (max-width: 767.98px) { .dl-table .dl-col-x { display: none; } }
.ts-btn.is-live { color: #15803d; border-color: rgba(34, 197, 94, .45); background: rgba(34, 197, 94, .1); }
.ts-btn.is-live .fa-solid, .ts-btn.is-live svg { animation: dlLivePulse 1.4s ease-in-out infinite; }
[data-bs-theme="dark"] .ts-btn.is-live { color: #86efac; }
@keyframes dlLivePulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .ts-btn.is-live .fa-solid, .ts-btn.is-live svg { animation: none; } }

/* ---- the PRO banner (jch-devlab-meter.js renders it once the day's allowance is spent) ----
   Full width above the work: a mascot, the pitch, four things PRO buys, and one button. Soft
   lavender so it reads as an offer rather than an error - the red box for that is elsewhere. */
#jch-pro-upsell { margin: 0 0 1rem; }
#jch-pro-upsell[hidden] { display: none; }
.jch-pro-up {
  position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: 1.25rem 1.75rem; padding: 1.35rem 3.25rem 1.35rem 1.5rem; border-radius: 1.35rem;
  border: 1px solid color-mix(in srgb, var(--ts-brand) 26%, var(--ts-line));
  background:
    radial-gradient(circle at 12% 20%, rgba(255, 255, 255, .9), transparent 38%),
    linear-gradient(118deg, #ece6ff 0%, #f5f2ff 48%, #eaeeff 100%);
  box-shadow: 0 .6rem 1.6rem rgba(99, 102, 241, .12);
  animation: jchProUpIn .32s ease-out both;
}
@keyframes jchProUpIn { from { opacity: 0; transform: translateY(-.35rem); } to { opacity: 1; transform: none; } }
[data-bs-theme="dark"] .jch-pro-up {
  background: radial-gradient(circle at 12% 20%, rgba(139, 92, 246, .16), transparent 40%),
    linear-gradient(118deg, #211c3f 0%, #1b1f36 55%, #1b2133 100%);
  box-shadow: 0 .6rem 1.6rem rgba(0, 0, 0, .35);
}
.jch-pro-up-close {
  position: absolute; top: .8rem; right: .85rem; width: 2rem; height: 2rem; border-radius: 50%;
  border: 0; background: transparent; color: var(--ts-muted); font-size: 1rem; cursor: pointer;
  display: grid; place-items: center; transition: background .14s ease, color .14s ease;
}
.jch-pro-up-close:hover { background: rgba(99, 102, 241, .12); color: var(--ts-ink); }
.jch-pro-up-close:focus-visible { outline: 2px solid var(--ts-brand); outline-offset: 2px; }
.jch-pro-up-art { display: block; width: 8.5rem; flex: 0 0 auto; }
.jch-pro-up-bot { width: 100%; height: auto; display: block; filter: drop-shadow(0 .5rem .9rem rgba(79, 70, 229, .22)); }
.jch-pro-up-bot .bot-head, .jch-pro-up-bot .bot-body, .jch-pro-up-bot .bot-ear { fill: #fff; stroke: #d9d6f5; stroke-width: 1.5; }
.jch-pro-up-bot .bot-face { fill: #2b2f4a; }
.jch-pro-up-bot .bot-eye, .jch-pro-up-bot .bot-mouth { fill: none; stroke: #a5b4fc; stroke-width: 3; stroke-linecap: round; }
.jch-pro-up-bot .bot-line { stroke: #6366f1; stroke-width: 3; stroke-linecap: round; }
.jch-pro-up-bot .bot-antenna { fill: #6366f1; }
.jch-pro-up-bot .bot-plate { fill: #22263f; stroke: #fbbf24; stroke-width: 2; }
.jch-pro-up-bot .bot-plate-txt { fill: #fbbf24; font: 800 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif; letter-spacing: .04em; }
.jch-pro-up-bot .bot-crown { fill: #f59e0b; stroke: #d97706; stroke-width: 1.2; stroke-linejoin: round; }
.jch-pro-up-bot .bot-spark { fill: #fbbf24; }
[data-bs-theme="dark"] .jch-pro-up-bot .bot-head, [data-bs-theme="dark"] .jch-pro-up-bot .bot-body,
[data-bs-theme="dark"] .jch-pro-up-bot .bot-ear { fill: #e5e7ff; stroke: #8b8fc7; }
.jch-pro-up-body { display: flex; flex-direction: column; gap: .35rem; min-width: 0; }
.jch-pro-up-pill {
  align-self: flex-start; display: inline-flex; align-items: center; gap: .4rem; padding: .28rem .8rem;
  border-radius: 999px; font-size: .78rem; font-weight: 750; color: #6d28d9;
  background: linear-gradient(90deg, #ede9fe, #f3e8ff); border: 1px solid rgba(124, 58, 237, .18);
}
[data-bs-theme="dark"] .jch-pro-up-pill { color: #c4b5fd; background: rgba(124, 58, 237, .18); }
.jch-pro-up-h1 { font-size: clamp(1.25rem, 2.2vw, 1.85rem); font-weight: 850; line-height: 1.15; color: var(--ts-ink); letter-spacing: -.01em; }
.jch-pro-up-h1 em {
  font-style: normal; background: linear-gradient(90deg, #7c3aed, #4f46e5);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.jch-pro-up-crown { color: #f59e0b; font-size: .8em; vertical-align: .05em; }
.jch-pro-up-sub { font-size: .92rem; color: var(--ts-muted); line-height: 1.5; max-width: 46rem; }
.jch-pro-up-feats { display: flex; flex-wrap: wrap; gap: .5rem 0; margin-top: .55rem; }
.jch-pro-up-feat {
  display: inline-flex; align-items: center; gap: .6rem; padding: 0 1.1rem 0 0; margin-right: 1.1rem;
  border-right: 1px solid color-mix(in srgb, var(--ts-brand) 18%, var(--ts-line)); font-size: .8rem; line-height: 1.2;
}
.jch-pro-up-feat:last-child { border-right: 0; margin-right: 0; padding-right: 0; }
.jch-pro-up-feat small { display: block; font-size: .78rem; font-weight: 600; color: var(--ts-ink); }
.jch-pro-up-feat b { display: block; font-size: .84rem; font-weight: 800; color: var(--ts-ink); }
.jch-pro-up-feat-ico {
  flex: 0 0 auto; width: 2.35rem; height: 2.35rem; border-radius: .8rem; display: grid; place-items: center;
  font-size: .95rem; box-shadow: 0 .2rem .5rem rgba(15, 23, 42, .08);
}
.jch-pro-up-feat-ico.is-bolt { background: #e0e7ff; color: #4f46e5; }
.jch-pro-up-feat-ico.is-db   { background: #dcfce7; color: #16a34a; }
.jch-pro-up-feat-ico.is-star { background: #fce7f3; color: #db2777; }
.jch-pro-up-feat-ico.is-help { background: #e0f2fe; color: #0284c7; }
.jch-pro-up-side { display: flex; flex-direction: column; align-items: flex-end; gap: .35rem; }
.jch-pro-up-note {
  position: relative; max-width: 11rem; padding-right: 2.2rem; text-align: right; color: #7c3aed;
  font-family: "Segoe Print", "Bradley Hand", "Comic Sans MS", "Marker Felt", cursive;
  font-size: .92rem; line-height: 1.25; transform: rotate(-5deg); margin-bottom: .6rem;
}
[data-bs-theme="dark"] .jch-pro-up-note { color: #c4b5fd; }
.jch-pro-up-arrow { position: absolute; right: -.1rem; bottom: -1.35rem; width: 2rem; height: 2rem; transform: rotate(18deg); }
.jch-pro-up-cta {
  display: inline-flex; align-items: center; gap: .55rem; padding: .8rem 1.5rem; border-radius: .9rem;
  font-size: .95rem; font-weight: 800; color: #fff; text-decoration: none; white-space: nowrap;
  background: linear-gradient(120deg, #7c3aed, #4f46e5 60%, #6366f1);
  box-shadow: 0 .55rem 1.2rem rgba(99, 102, 241, .35); transition: transform .14s ease, box-shadow .14s ease;
}
.jch-pro-up-cta:hover { color: #fff; transform: translateY(-1px); box-shadow: 0 .7rem 1.5rem rgba(99, 102, 241, .45); }
.jch-pro-up-cta .fa-crown { color: #fde68a; }
.jch-pro-up-cta .fa-arrow-right { font-size: .8em; }
@media (max-width: 1199.98px) {
  .jch-pro-up { grid-template-columns: auto minmax(0, 1fr); }
  .jch-pro-up-side { grid-column: 1 / -1; flex-direction: row; align-items: center; justify-content: flex-end; }
  .jch-pro-up-note { display: none; }
}
@media (max-width: 767.98px) {
  .jch-pro-up { grid-template-columns: minmax(0, 1fr); padding: 1.1rem 1.1rem 1.15rem; gap: .9rem; }
  .jch-pro-up-art { width: 5.5rem; }
  .jch-pro-up-feat { border-right: 0; margin-right: 0; padding-right: 0; flex: 1 1 45%; }
  .jch-pro-up-feats { gap: .6rem .5rem; }
  .jch-pro-up-side { justify-content: stretch; }
  .jch-pro-up-cta { width: 100%; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) { .jch-pro-up { animation: none; } }
