:root {
  --bg: #fbfbfd; --fg: #1d1d1f; --muted: #6e6e73; --line: #e4e4e7;
  --card: #ffffff; --accent: #2563eb; --code-bg: #f3f4f6; --code-fg: #1d1d1f;
  --error: #b91c1c; --error-bg: #fef2f2;
  --warn: #92400e; --warn-bg: #fef3c7; --warn-line: #fcd34d;
  /* The heat scale the tree colors with. Five stops rather than a gradient:
     a node is either unremarkable or it is one of a handful worth looking at,
     and a continuous ramp makes every node look mildly interesting. */
  --h0: #eef2ff; --h1: #dbeafe; --h2: #fde68a; --h3: #fdba74; --h4: #fca5a5;
  --h-fg: #1d1d1f;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f1115; --fg: #e6e6e9; --muted: #9a9aa3; --line: #25272e;
    --card: #15171d; --accent: #6ea8fe; --code-bg: #1c1f27; --code-fg: #e6e6e9;
    --error: #f87171; --error-bg: #2a1414;
    --warn: #fcd34d; --warn-bg: #2b2312; --warn-line: #7c5e10;
    --h0: #1a2030; --h1: #1e3a5f; --h2: #5a4711; --h3: #6b3f16; --h4: #6e2020;
    --h-fg: #e6e6e9;
  }
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--fg);
  font: 1rem/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.wrap { width: 100%; max-width: 82rem; margin: 0 auto; padding: 3rem clamp(1rem, 4vw, 3rem) 5rem; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .92em; }

header.hero { margin-bottom: 2.5rem; text-align: center; }
h1 { font-size: 2.1rem; line-height: 1.1; margin: 0; letter-spacing: -0.02em; }
.tagline { color: var(--muted); margin: .65rem auto 0; max-width: 42rem; }

.pane { margin-bottom: 1.25rem; }
.pane h2 { font-size: .95rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 .5rem; }
.pane-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .75rem; margin-bottom: .5rem; }
.pane-head h2 { margin: 0; }

/* The optional inputs collapse, so the plan box is the only thing demanding
   attention on arrival — but the summary states what each one buys, because
   "optional" alone reads as "ignore me". */
details.optional { border: 1px solid var(--line); border-radius: 10px; padding: .7rem .9rem; background: var(--card); }
details.optional > summary {
  cursor: pointer; font-size: .95rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); list-style: revert;
}
details.optional[open] > summary { margin-bottom: .7rem; }
.hint { text-transform: none; letter-spacing: 0; font-size: .85rem; }
.note { font-size: .85rem; color: var(--muted); margin: 0 0 .7rem; }

textarea {
  display: block; width: 100%; min-height: 12rem; margin: 0; padding: .9rem 1.1rem;
  background: var(--code-bg); color: var(--code-fg); border: 1px solid var(--line); border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82rem; line-height: 1.5;
  resize: vertical; overflow: auto; white-space: pre; text-align: left;
}
#plan { min-height: 16rem; }
textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.context-row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 1rem 0 0; font-size: .88rem; }
.ctx-label { color: var(--muted); }
.ctx-chip { background: var(--code-bg); border: 1px solid var(--line); border-radius: 999px; padding: .1rem .6rem; font-weight: 600; }
.ctx-chip.is-schema { border-color: var(--accent); color: var(--accent); }
.ctx-next { color: var(--muted); font-style: italic; }

.controls { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin: 1rem 0 1.5rem; }
.btn {
  display: inline-block; min-width: 9rem; background: var(--accent); color: #fff; border: none; cursor: pointer;
  padding: .6rem 1.3rem; border-radius: 9px; font-weight: 600; font-size: .95rem; font-family: inherit;
}
.btn:hover:not(:disabled) { opacity: .92; }
.btn:disabled { background: var(--muted); cursor: default; }
.check { font-size: .85rem; color: var(--muted); display: flex; align-items: center; gap: .4rem; cursor: pointer; }

.error { background: var(--error-bg); color: var(--error); border: 1px solid var(--error); border-radius: 10px; padding: .8rem 1rem; font-size: .9rem; margin-bottom: 1rem; }

.summary { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; padding: .9rem 1.1rem; margin-bottom: 1.5rem;
           background: var(--card); border: 1px solid var(--line); border-radius: 10px; font-size: .88rem; }
.summary .k { color: var(--muted); }
.summary .v { font-weight: 600; }
.summary .scrubbed { color: var(--muted); font-style: italic; }

.toggles { display: flex; gap: .25rem; }
.tog { background: var(--code-bg); color: var(--muted); border: 1px solid var(--line); cursor: pointer;
       padding: .25rem .7rem; border-radius: 7px; font-size: .82rem; font-family: inherit; }
.tog.is-on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.dim-note { font-size: .82rem; color: var(--muted); margin: 0 0 .6rem; }

/* Both wide views scroll inside themselves. A plan is arbitrarily wide and the
   page must never scroll sideways on a phone. */
.scroll-x { width: 100%; overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; background: var(--card); }

.tree { min-width: max-content; padding: .5rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82rem; }
.node { border-radius: 7px; margin: 1px 0; --depth: 0; }
.node > .node-line, .node > .node-detail { margin-left: calc(var(--depth) * 1.4rem); }
.node-line { display: flex; align-items: baseline; gap: .5rem; padding: .3rem .5rem; cursor: pointer; border-radius: 7px; white-space: nowrap; }
.node-line:hover { outline: 1px solid var(--accent); }
.twist { color: var(--muted); width: .8rem; flex: none; }
.node-kind { font-weight: 700; }
.node-target { color: var(--muted); }
.node-rel { color: var(--muted); font-size: .92em; }
.node-ms { margin-left: auto; padding-left: 1.5rem; font-variant-numeric: tabular-nums; }
.node-share { color: var(--muted); font-variant-numeric: tabular-nums; }
.node.hot > .node-line { box-shadow: inset 3px 0 0 var(--accent); }

/* The clamp flag is the one presentational thing that must never be dropped:
   under a Gather the exclusive-time subtraction can go negative, and a node
   showing 0.00 ms because of that is not a node that took no time. */
.approx { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-line); border-radius: 5px;
          padding: 0 .35rem; font-size: .78rem; font-weight: 600; }

.node-detail { display: none; padding: .35rem .5rem .6rem 2rem; border-top: 1px dashed var(--line); }
.node.open > .node-detail { display: block; }
.node-detail dl { display: grid; grid-template-columns: max-content 1fr; gap: .15rem .9rem; margin: 0; }
.node-detail dt { color: var(--muted); }
.node-detail dd { margin: 0; white-space: pre-wrap; word-break: break-word; }

table.nodes { min-width: max-content; width: 100%; border-collapse: collapse; font-size: .82rem; }
table.nodes th, table.nodes td { padding: .4rem .7rem; text-align: right; white-space: nowrap; }
table.nodes th:first-child, table.nodes td:first-child { text-align: left; }
table.nodes thead th { position: sticky; top: 0; background: var(--card); border-bottom: 1px solid var(--line);
                       cursor: pointer; user-select: none; color: var(--muted); font-weight: 600; }
table.nodes thead th:hover { color: var(--fg); }
table.nodes thead th[aria-sort]:not([aria-sort="none"]) { color: var(--accent); }
table.nodes tbody tr:nth-child(even) { background: color-mix(in srgb, var(--code-bg) 55%, transparent); }
table.nodes td.num { font-variant-numeric: tabular-nums; }
table.nodes td.over { color: var(--error); }
table.nodes td.under { color: var(--warn); }

footer { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--line); color: var(--muted); font-size: .85rem; text-align: center; }

@media (max-width: 34rem) {
  .wrap { padding: 2rem 1rem 3.5rem; }
  h1 { font-size: 1.6rem; }
  .btn { width: 100%; }
  .summary { gap: .35rem 1rem; }
}

/* ------------------------------------------------------------- findings */

/* Severity and confidence are separate claims and are styled separately:
   severity drives the card's left edge (how much this costs you), confidence
   drives the badge (how sure pgqe is). A critical/speculative finding must not
   look like a critical/certain one — carrying confidence through the whole
   pipeline is pointless if the page flattens it here. */
.finding { border: 1px solid var(--line); border-left: 4px solid var(--muted); border-radius: 10px;
           background: var(--card); margin-bottom: .6rem; }
.finding.sev-critical { border-left-color: #dc2626; }
.finding.sev-high     { border-left-color: #ea580c; }
.finding.sev-medium   { border-left-color: #d97706; }
.finding.sev-low      { border-left-color: #65a30d; }
.finding.sev-info     { border-left-color: var(--muted); }

.finding-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem;
                padding: .7rem .9rem; cursor: pointer; }
.finding-head:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.finding-title { font-weight: 600; flex: 1 1 16rem; }
.rule-id { color: var(--muted); font-size: .78rem; }

.badge { border-radius: 5px; padding: .05rem .45rem; font-size: .72rem; font-weight: 700;
         text-transform: uppercase; letter-spacing: .03em; }
.badge.sev { background: var(--code-bg); color: var(--fg); border: 1px solid var(--line); }
.sev-critical .badge.sev { background: #dc2626; color: #fff; border-color: #dc2626; }
.sev-high     .badge.sev { background: #ea580c; color: #fff; border-color: #ea580c; }
.sev-medium   .badge.sev { background: #d97706; color: #fff; border-color: #d97706; }

/* Certain is stated plainly; speculative is visibly hedged — dashed, not
   solid, so the difference survives a glance and a grayscale screenshot. */
.badge.conf { border: 1px solid var(--line); background: transparent; color: var(--muted); }
.conf-certain     .badge.conf { border-color: var(--accent); color: var(--accent); border-style: solid; }
.conf-likely      .badge.conf { border-color: var(--warn); color: var(--warn); border-style: solid; }
.conf-speculative .badge.conf { border-color: var(--muted); color: var(--muted); border-style: dashed; }

.finding-body { display: none; padding: 0 .9rem .9rem; }
.finding.open > .finding-body { display: block; }
.finding-detail { margin: 0 0 .6rem; }
.conf-note { margin: 0 0 .8rem; font-size: .85rem; color: var(--muted); font-style: italic; }

dl.evidence { display: grid; grid-template-columns: max-content 1fr; gap: .2rem .9rem; margin: 0 0 .9rem;
              font-size: .85rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
dl.evidence dt { color: var(--muted); }
dl.evidence dd { margin: 0; word-break: break-word; }

.remedy { border-top: 1px solid var(--line); padding-top: .7rem; }
.remedy p { margin: 0 0 .6rem; }
.sql-row { display: flex; align-items: flex-start; gap: .5rem; }
pre.sql { flex: 1; margin: 0; padding: .6rem .8rem; overflow-x: auto; background: var(--code-bg);
          color: var(--code-fg); border-radius: 8px; white-space: pre-wrap; word-break: break-word;
          font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82rem; }
.copy { flex: none; background: var(--code-bg); color: var(--fg); border: 1px solid var(--line);
        border-radius: 7px; padding: .35rem .7rem; font-size: .8rem; font-family: inherit; cursor: pointer; }
.copy:hover { border-color: var(--accent); color: var(--accent); }

.no-findings { color: var(--muted); font-size: .9rem; margin: 0; padding: .8rem .9rem;
               border: 1px dashed var(--line); border-radius: 10px; }

/* How the deeper modes get discovered. Without this, a page full of
   speculative findings looks like the best pgqe can do. */
.ctx-prompt { margin-top: .8rem; padding: .8rem .9rem; font-size: .87rem;
              background: color-mix(in srgb, var(--accent) 7%, transparent);
              border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); border-radius: 10px; }

/* Clicking a finding takes you to the node it is about. */
.node.targeted > .node-line { outline: 2px solid var(--accent); outline-offset: 1px; }

.rollup { background: var(--code-bg); color: var(--muted); border: 1px solid var(--line); cursor: pointer;
          padding: .25rem .7rem; border-radius: 7px; font-size: .82rem; font-family: inherit; }
.rollup.is-on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
table.nodes td.empty { text-align: left; color: var(--muted); font-style: italic; white-space: normal; }

/* ---------------------------------------------------------- file input */

/* Both panes take a file OR a paste. Not either/or: a dump is a file on disk
   and a plan from a chat window is a clipboard, and forcing one through the
   other's route is the friction this avoids. */
.file-row { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: .5rem; }
.file-btn {
  display: inline-block; background: var(--code-bg); color: var(--fg); border: 1px solid var(--line);
  border-radius: 7px; padding: .3rem .8rem; font-size: .82rem; cursor: pointer;
}
.file-btn:hover { border-color: var(--accent); color: var(--accent); }
.file-btn input { display: none; }
.file-note { font-size: .82rem; color: var(--muted); }
/* A refused file has to look refused — the same size that is fine in a paste
   is a 413 once it comes from disk, and that surprise is the point of checking. */
.file-note.too-big { color: var(--error); }

.pane.dropping { outline: 2px dashed var(--accent); outline-offset: 4px; }

/* ------------------------------------------------- the pg_dump suggestion */

/* Sits inside the context prompt: "add a schema" and "here is the command that
   produces one" are the same thought, and separating them would mean finding
   the command elsewhere at the moment you want it. */
.dump { margin-top: .8rem; }
.dump-lead { margin: 0 0 .5rem; font-size: .87rem; }
.dump-caveats { margin: .6rem 0 0; padding-left: 1.1rem; font-size: .8rem; color: var(--muted); }
.dump-caveats li { margin-bottom: .25rem; }
