/* ==========================================================================
   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}

/* tool switcher — the only navigation a tool page had was the wordmark, which
   reads as a logo rather than a way out. 102 tools and no route between any two
   of them without going home first. Pure CSS <details>: no JS, no external host,
   collapses on mobile by construction, both themes via tokens. */
.toolnav{position:relative;margin-left:auto;flex:none}
.toolnav>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:0.375rem;
  padding:0.375rem 0.75rem;border:1px solid var(--line);border-radius:0.6875rem;
  background:var(--surface);color:var(--ink);font-size:0.875rem;font-weight:600;
  transition:border-color .15s,color .15s}
.toolnav>summary::-webkit-details-marker{display:none}
.toolnav>summary::after{content:"";width:0.4375rem;height:0.4375rem;margin-left:0.125rem;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(45deg) translate(-0.125rem,-0.125rem);transition:transform .15s}
.toolnav[open]>summary::after{transform:rotate(-135deg) translate(-0.0625rem,-0.0625rem)}
.toolnav>summary:hover{border-color:var(--accent);color:var(--accent)}
.toolnav>summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.toolnav-menu{position:absolute;right:0;top:calc(100% + 0.5rem);z-index:150;min-width:13.5rem;
  display:flex;flex-direction:column;padding:0.375rem;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow)}
.toolnav-menu a{padding:0.5rem 0.625rem;border-radius:0.5rem;color:var(--ink);
  text-decoration:none;font-size:0.875rem;white-space:nowrap}
.toolnav-menu a:hover{background:var(--bg);color:var(--accent)}
.toolnav-menu .all{margin-top:0.25rem;border-top:1px solid var(--line);padding-top:0.5rem;font-weight:600}
/* Below 520px the header wraps and .toolnav drops to its own row. margin-left:auto
   would keep it pinned right, and since .toolnav-menu is absolute WITHIN it, the
   panel then starts at the control's left edge and runs off-screen — measured at
   390px: menu right edge 505 against a 390 viewport, which also gave the document
   horizontal overflow. Dropping the auto margin puts control and panel on the left,
   which fixes the overflow and puts the control in thumb reach. */
@media (max-width:520px){
  .brand{flex-wrap:wrap}
  .toolnav{margin-left:0}
  .toolnav-menu{right:auto;left:0;min-width:min(15rem,calc(100vw - 2.5rem))}
}
.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)}
.maker{margin-top:0.6rem;font-size:0.8125rem;opacity:0.85;line-height:1.5}
[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}
