/* ==========================================================================
   Polyatic shared design system — every hub tool links this file and uses its
   classes so the whole hub looks like one product. Tool-specific tweaks go in a
   small <style> block on the page; do NOT re-declare these tokens/components.
   Design laws (see PORTFOLIO-BACKLOG / builderNotes): result-first, live
   feedback, what's interactive looks interactive, wraps on mobile, both themes.
   ========================================================================== */
:root{
  --bg:#f4f5fb; --surface:#ffffff; --surface-2:#f0f1f9; --ink:#14162a; --muted:#5b6180;
  --line:#e4e5f1; --accent:#4f52f0; --accent-2:#7a73f7; --accent-soft:rgba(79,82,240,.09);
  --ok:#12a67c; --warn:#e0932a; --bad:#e5484d;
  --s1:#e5484d; --s2:#ef8f4a; --s3:#e0b32a; --s4:#3aa564; --s5:#12a67c;
  --shadow:0 1px 2px rgba(20,22,42,.05), 0 12px 34px rgba(20,22,42,.09);
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --mono:ui-monospace,"SF Mono","JetBrains Mono","Cascadia Code",Menlo,Consolas,monospace;
  --r:1rem;
}
@media (prefers-color-scheme:dark){
  :root{--bg:#0a0c16;--surface:#141627;--surface-2:#1b1e31;--ink:#eceffa;--muted:#8b91b0;
    --line:#262a40;--accent:#7f84ff;--accent-2:#a08cff;--accent-soft:rgba(127,132,255,.13);
    --ok:#2ad19b;--warn:#f2c94c;--bad:#ff6165;
    --s1:#ff6165;--s2:#ffa457;--s3:#f2c94c;--s4:#4cc47c;--s5:#2ad19b;
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 16px 40px rgba(0,0,0,.5);}
}
:root[data-theme="light"]{--bg:#f4f5fb;--surface:#fff;--surface-2:#f0f1f9;--ink:#14162a;--muted:#5b6180;--line:#e4e5f1;--accent:#4f52f0;--accent-2:#7a73f7;--accent-soft:rgba(79,82,240,.09);--ok:#12a67c;--warn:#e0932a;--bad:#e5484d;--s1:#e5484d;--s2:#ef8f4a;--s3:#e0b32a;--s4:#3aa564;--s5:#12a67c;--shadow:0 1px 2px rgba(20,22,42,.05),0 12px 34px rgba(20,22,42,.09);}
:root[data-theme="dark"]{--bg:#0a0c16;--surface:#141627;--surface-2:#1b1e31;--ink:#eceffa;--muted:#8b91b0;--line:#262a40;--accent:#7f84ff;--accent-2:#a08cff;--accent-soft:rgba(127,132,255,.13);--ok:#2ad19b;--warn:#f2c94c;--bad:#ff6165;--s1:#ff6165;--s2:#ffa457;--s3:#f2c94c;--s4:#4cc47c;--s5:#2ad19b;--shadow:0 1px 2px rgba(0,0,0,.4),0 16px 40px rgba(0,0,0,.5);}

*{box-sizing:border-box}
/* fluid scale ladder: the whole UI grows with the display */
html{font-size:16px}
@media(min-width:1680px){html{font-size:17px}}
@media(min-width:2100px){html{font-size:18.5px}}
@media(min-width:2800px){html{font-size:20.5px}}
@media(min-width:3600px){html{font-size:22.5px}}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.55;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
.wrap{max-width:42rem;margin:0 auto;padding:1.375rem 1.125rem 4.5rem}
.wrap.wide{max-width:54rem}
.wrap.tool{max-width:72rem}
a{color:var(--accent);text-underline-offset:0.125rem}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* skip-to-content link: off-screen until keyboard-focused, then reveals top-left */
.skip-link{position:absolute;left:0.5rem;top:-4rem;z-index:200;background:var(--ink);color:var(--bg);padding:0.5rem 0.875rem;border-radius:0.5rem;font-weight:600;font-size:0.875rem;text-decoration:none;transition:top .15s ease}
.skip-link:focus{top:0.5rem;box-shadow:var(--shadow)}

/* brand / breadcrumb bar */
.brand{display:flex;align-items:center;gap:0.625rem;margin-bottom:1.625rem}
.brand .mark{width:1.625rem;height:1.625rem;border-radius:0.5rem;background:conic-gradient(from 210deg,var(--accent),var(--accent-2),var(--accent));box-shadow:0 2px 8px var(--accent-soft);flex:none}
.brand b{font-size:1rem;letter-spacing:-.2px}
.brand b a{color:inherit;text-decoration:none}
.brand .crumb{color:var(--muted);font-size:0.875rem}
.brand .crumb::before{content:"/";margin:0 0.5rem;color:var(--line)}
.brand .spacer{flex:1}
.tt{width:2.125rem;height:2.125rem;border-radius:0.5625rem;border:1px solid var(--line);background:var(--surface);color:var(--muted);cursor:pointer;font-size:0.9375rem;display:grid;place-items:center}
.tt:hover{color:var(--ink);border-color:var(--muted)}

/* tool intro */
.tool-head h1{font-size:1.8125rem;letter-spacing:-.8px;margin:0 0 0.375rem;text-wrap:balance}
.tool-head .sub{color:var(--muted);font-size:1rem;margin:0 0 1.375rem;max-width:46ch}

/* card */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:1.25rem}

/* segmented mode switch (flex-wrap: long tab labels stack instead of overflowing at <=320px) */
.modes{display:flex;flex-wrap:wrap;gap:0.25rem;background:var(--surface-2);border-radius:0.75rem;padding:0.25rem;margin-bottom:1.125rem}
.modes button{flex:1;border:0;background:transparent;color:var(--muted);font:inherit;font-weight:600;font-size:0.90625rem;padding:0.5625rem;border-radius:0.5625rem;cursor:pointer;transition:.15s}
.modes button.active{background:var(--surface);color:var(--ink);box-shadow:0 1px 3px rgba(0,0,0,.08)}

/* result readout — the hero of a tool: big, wraps, integrated actions */
.readout{display:flex;align-items:center;gap:0.625rem;background:var(--surface-2);border:1px solid var(--line);border-radius:0.8125rem;padding:0.375rem 0.375rem 0.375rem 1.125rem}
.readout .val{flex:1;min-width:0;font-family:var(--mono);font-size:1.375rem;font-weight:600;letter-spacing:.5px;line-height:1.45;padding:0.6875rem 0;white-space:normal;overflow-wrap:anywhere;word-break:break-word}
.readout .val .d{color:var(--accent)} .readout .val .s{color:var(--s2)}

/* buttons */
.btn{height:2.75rem;padding:0 1.125rem;border:0;border-radius:0.6875rem;background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#fff;font:inherit;font-weight:600;font-size:0.90625rem;cursor:pointer;transition:.15s}
.btn:hover{filter:brightness(1.06)} .btn:active{transform:scale(.97)}
.btn.done{background:var(--s5)}
.btn.ghost{background:var(--surface);color:var(--ink);border:1px solid var(--line)}
.btn.ghost:hover{border-color:var(--accent);color:var(--accent);filter:none}
/* dark-theme .btn ink (T-POLISH.1, closes SWEEP.1): white text on the dark accent
   gradient (#7f84ff -> #a08cff) measures 3.16:1 / 2.73:1 — below the WCAG AA
   4.5:1 bar for normal-size text. In dark themes the primary-button ink switches
   to the dark page background #0a0c16: 6.17:1 vs #7f84ff and 7.14:1 vs #a08cff
   (and 9.93:1 on .btn.done's --s5 #2ad19b). .ghost is excluded — it already uses
   var(--ink) on var(--surface). The light-theme .btn declarations above are
   byte-untouched. Pinned against drift by test/contrast-vectors.test.cjs §7. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .btn:not(.ghost){color:#0a0c16}
}
:root[data-theme="dark"] .btn:not(.ghost){color:#0a0c16}
.icon{width:2.75rem;height:2.75rem;flex:none;border:1px solid var(--line);background:var(--surface);color:var(--muted);border-radius:0.6875rem;cursor:pointer;font-size:1.1875rem;display:grid;place-items:center;transition:.15s}
.icon:hover{color:var(--accent);border-color:var(--accent)} .icon:active{transform:scale(.94)}
.icon.spin{animation:spin .5s ease}
@keyframes spin{to{transform:rotate(360deg)}}

/* meter (strength, fill, progress) */
.meter{display:flex;align-items:center;gap:0.75rem;margin:0.875rem 0.125rem 0}
.meter .track{flex:1;height:0.4375rem;border-radius:0.3125rem;background:var(--surface-2);overflow:hidden}
.meter .fill{height:100%;width:0;border-radius:0.3125rem;transition:width .3s cubic-bezier(.2,.7,.3,1),background .3s;background:var(--muted)}
.meter .label{font-size:0.8125rem;font-weight:700;min-width:5.125rem;text-align:right}
.meter .aux{font-size:0.78125rem;color:var(--muted);font-variant-numeric:tabular-nums;min-width:3.5rem;text-align:right}

/* controls */
.controls{margin-top:1.375rem;display:grid;gap:1.25rem}
.field{min-width:0}/* fields sit in grids/flex rows: without this a wide intrinsic control (e.g. datetime-local) blows the column out at 320px */
.field .head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:0.5625rem}
.field .head .k{font-size:0.8125rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.field .head .v{font-family:var(--mono);font-size:1.0625rem;font-weight:700;color:var(--accent);font-variant-numeric:tabular-nums}
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:0.4375rem;border-radius:0.3125rem;background:linear-gradient(90deg,var(--accent) var(--pct,40%),var(--surface-2) var(--pct,40%));cursor:pointer}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.375rem;height:1.375rem;border-radius:50%;background:var(--surface);border:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,.18);cursor:grab}
input[type=range]::-moz-range-thumb{width:1.375rem;height:1.375rem;border-radius:50%;background:var(--surface);border:2px solid var(--accent);cursor:grab}
.chips{display:flex;flex-wrap:wrap;gap:0.5rem}
.chip{border:1px solid var(--line);background:var(--surface);color:var(--muted);font:inherit;font-weight:600;font-size:0.875rem;padding:0.5rem 0.875rem;border-radius:0.625rem;cursor:pointer;transition:.14s}
.chip.on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}
input[type=text],input[type=number],input[type=search],input[type=date],input[type=time],input[type=datetime-local],input[type=email],input[type=url],select,textarea{width:100%;padding:0.6875rem 0.75rem;border:1px solid var(--line);border-radius:0.6875rem;background:var(--surface);color:var(--ink);font:inherit;font-size:0.9375rem}
input,select,textarea{min-width:0}/* form controls must shrink inside flex/grid on narrow phones */
textarea{min-height:8.75rem;resize:vertical;font-family:var(--mono);line-height:1.5}
label.check{display:flex;align-items:center;gap:0.5625rem;font-size:0.9375rem;cursor:pointer}
label.check>span{min-width:0}/* wrap mixed text+<code> label prose in one span so it wraps as a unit on narrow phones */
label.check input{width:1.0625rem;height:1.0625rem;accent-color:var(--accent)}

/* stat tiles (counters, results grids) */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(6.875rem,1fr));gap:0.625rem}
.stat{background:var(--surface-2);border:1px solid var(--line);border-radius:0.75rem;padding:0.75rem 0.875rem}
.stat .k{font-size:0.75rem;color:var(--muted)} .stat .v{font-size:1.375rem;font-weight:700;font-variant-numeric:tabular-nums}

/* trust line */
.trust{display:flex;gap:0.5625rem;align-items:flex-start;margin:1.125rem 0.125rem 0;color:var(--muted);font-size:0.84375rem;overflow-wrap:anywhere}
.trust>*{min-width:0}/* long unbreakable code tokens (e.g. encodeURIComponent/decodeURIComponent) must not set the flex item's min width */
.trust b{color:var(--ink);font-weight:600}

/* long-form explainer / FAQ (SEO) */
.explainer{margin-top:2.75rem;max-width:64ch}
.explainer h2{font-size:1.1875rem;letter-spacing:-.3px;margin:1.75rem 0 0.5rem}
.explainer h3{font-size:1rem;margin:1.25rem 0 0.25rem}
.explainer p{color:var(--muted);font-size:0.9375rem;margin:0 0 0.75rem}
.explainer code{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--line);border-radius:0.375rem;padding:0.0625rem 0.375rem;font-size:0.8125rem;color:var(--ink);overflow-wrap:anywhere}/* long unbreakable tokens (percent-encoded URLs, hashes) wrap instead of dragging 320px pages sideways */
/* block code samples (<pre><code>…): pre keeps white-space:pre by default, so a long CSS line stretched 320px pages sideways (2026-07-17 sweep). Wrap the lines and never exceed the column. */
.explainer pre{max-width:100%;overflow-x:auto}
.explainer pre code{white-space:pre-wrap;overflow-wrap:anywhere;display:inline-block;max-width:100%;box-sizing:border-box}
.explainer table{border-collapse:collapse;margin:0.625rem 0;font-size:0.875rem;width:100%;max-width:27.5rem}
.explainer th,.explainer td{text-align:left;padding:0.375rem 0.75rem;border-bottom:1px solid var(--line)}
/* narrow phones: reference tables become their own scroll container instead of dragging the whole page sideways */
@media(max-width:640px){.explainer table{display:block;overflow-x:auto;max-width:100%}}
/* guide CTA callouts: the .btn label is a single anonymous flex item that refuses
   to shrink (min-width:auto on flex:none), so a long label ("Try the JSON Formatter
   & Validator →") drags 320px pages sideways. Cap it at the container and let the
   text wrap. .explainer-scoped so it outranks the guides' page-local .callout .btn. */
.explainer .callout .btn{max-width:100%;box-sizing:border-box;height:auto;min-height:2.75rem;line-height:1.35;padding-top:0.5rem;padding-bottom:0.5rem;white-space:normal;text-align:center}

/* explainer + related-tools sidebar (v2.1): the 64ch explainer alone left a huge
   empty gutter on tool pages; beside it now sits a cross-link card so the lower
   page fills the canvas like the hub does */
@media(min-width:1080px){
  .explainer-row{display:grid;grid-template-columns:minmax(0,64ch) minmax(14.5rem,18rem);gap:2.75rem;align-items:start}
  .explainer-row .explainer{margin-top:2.75rem}
  .explainer-row .related{position:sticky;top:1.25rem}
}
.related{margin-top:2.75rem;background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:1.125rem 1.25rem}
.related h2{font-size:0.875rem;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);margin:0 0 0.625rem}
.related ul{list-style:none;margin:0;padding:0;display:grid;gap:0.5625rem}
.related a{text-decoration:none;font-weight:600;font-size:0.90625rem}
.related a:hover{text-decoration:underline}

footer.site{margin-top:2.75rem;padding-top:1.25rem;border-top:1px solid var(--line);color:var(--muted);font-size:0.8125rem;text-align:center}
footer.site a{color:var(--muted)}
[hidden]{display:none!important}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
@media(max-width:520px){.readout .val{font-size:1.1875rem}.tool-head h1{font-size:1.5625rem}}

/* ==========================================================================
   Layout v2 (2026-07-08) — tools get a real desktop canvas.
   .wrap.tool (1100px) + .duo two-pane grid: pane-main = inputs/controls,
   pane-result = the live result (sticky on desktop). DOM order controls the
   MOBILE stack (transform tools put pane-main first: input-first; generator
   tools put pane-result first: result-first); on >=920px the grid areas place
   main left / result right regardless of DOM order.
   .grid2 pairs related controls in two columns on >=680px (.span2 = full row).
   ========================================================================== */
.grid2{display:grid;gap:1.25rem}
.grid2 .span2{grid-column:1/-1}
@media(min-width:680px){.grid2{grid-template-columns:1fr 1fr;column-gap:1.625rem}}
.duo{display:grid;gap:1.375rem}
.duo>.pane-main,.duo>.pane-result{min-width:0}
@media(min-width:920px){
  /* proportional panes (v2.1): a near-fixed 460px result pane squeezed the input
     side to <50% of a laptop window; both panes now scale with the viewport */
  .duo{grid-template-columns:minmax(0,1.15fr) minmax(20.5rem,.85fr);gap:1.625rem;align-items:start;grid-template-areas:"main side"}
  .duo>.pane-main{grid-area:main}
  .duo>.pane-result{grid-area:side;position:sticky;top:1.25rem}
  .tool-head h1{font-size:2.125rem}
  .tool-head .sub{font-size:1.0625rem;max-width:60ch}
}

/* Capture slot (T-CAP.1) — INERT scaffolding. Hidden by default so the
   container reserves NO space and causes zero CLS while off. cap-slot.js keeps
   it empty until an owner go-live flips CAPTURE_ENABLED; no ad code ships now. */
.cap-slot{display:none}
