{
  "baseRepository": "https://github.com/identity-md-launches/launch-2921-workflow-frontend-stage-context",
  "baseCommit": "6a2c438c6962d3e798cafbfc0f9248e1d8b34788",
  "changes": "Three UI fixes only: missing processed FDV wording, responsive chart height, full-width desktop contribution form. Preserve all contracts and transaction logic.",
  "files": [
    {
      "path": "web/src/components.tsx",
      "baseSha256": "29b4b9a78beb66d024e8ee412a6bf441f4babddc11819b14256169e3f1e4ba7c",
      "sha256": "0a01bf50eaa40b4528aeb5410ede5660eaaf628fbc529739ad358b4b60f12bd7",
      "content": "import { trapDialogFocus } from \"./focus\";\nimport { useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { formatUnits, getAddress, maxUint256, type Address } from \"viem\";\nimport { useApp, type Transaction } from \"./context\";\nexport function display(n: bigint | undefined, dec = 18, digits = 4): string {\n  if (n === undefined) return \"Unavailable\";\n  if (n === maxUint256) return \"Unlimited\";\n  const [whole, fraction = \"\"] = formatUnits(n, dec).split(\".\");\n  const shown = fraction.slice(0, digits).replace(/0+$/, \"\");\n  if (n > 0n && whole === \"0\" && !shown)\n    return `<${digits ? \"0.\" + \"0\".repeat(digits - 1) + \"1\" : \"1\"}`;\n  const grouped = whole.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n  return grouped + (shown ? \".\" + shown : \"\");\n}\nexport function UsdValue({\n  value,\n  token = \"UNLK2\",\n}: {\n  value?: bigint;\n  token?: \"UNLK2\" | \"IMD\";\n}) {\n  const { data: d, stale } = useApp();\n  const rate =\n    !stale && d?.imdUsd[1] && (token === \"IMD\" || d?.tokenPrice[1])\n      ? token === \"IMD\"\n        ? d.imdUsd[0]\n        : (d.imdUsd[0] * d.tokenPrice[0]) / 10n ** 18n\n      : undefined;\n  return (\n    <span className=\"usd-value muted\">\n      {value !== undefined && rate !== undefined\n        ? `Reference \u2248 $${display((value * rate) / 10n ** 18n, 18, 2)} USD`\n        : \"USD context unavailable\"}\n    </span>\n  );\n}\nexport const exact = (n: bigint | undefined, dec = 18) =>\n  n === undefined ? \"Unavailable\" : formatUnits(n, dec);\nexport function AddressLink({\n  address,\n  label,\n}: {\n  address?: string;\n  label?: string;\n}) {\n  const { r } = useApp();\n  const [copied, setCopied] = useState(false);\n  if (!address) return <span className=\"muted\">Not available</span>;\n  return (\n    <div className=\"address-line\">\n      <a\n        href={`${r.d.network.explorer}/address/${address}`}\n        target=\"_blank\"\n        rel=\"noreferrer\"\n        className=\"address\"\n        title={address}\n      >\n        {label ?? getAddress(address)}\n      </a>\n      <button\n        className=\"small\"\n        aria-label={`Copy ${label ?? address}`}\n        onClick={() => {\n          void navigator.clipboard.writeText(getAddress(address)).then(() => {\n            setCopied(true);\n            setTimeout(() => setCopied(false), 2000);\n          });\n        }}\n      >\n        {copied ? \"Copied\" : \"Copy\"}\n      </button>\n    </div>\n  );\n}\nexport function Stat({\n  label,\n  value,\n  unit,\n  note,\n}: {\n  label: string;\n  value: ReactNode;\n  unit?: string;\n  note?: string;\n}) {\n  return (\n    <div className=\"stat\">\n      <span className=\"muted\">{label}</span>\n      <div className=\"stat-value\">\n        {value}\n        {unit && <span className=\"unit\"> {unit}</span>}\n      </div>\n      {note && <span className=\"muted\">{note}</span>}\n    </div>\n  );\n}\nexport function Field({\n  label,\n  value,\n  onChange,\n  hint,\n  type = \"text\",\n  placeholder,\n  inputMode,\n}: {\n  label: string;\n  value: string;\n  onChange: (s: string) => void;\n  hint?: string;\n  type?: string;\n  placeholder?: string;\n  inputMode?: \"decimal\" | \"numeric\" | \"text\";\n}) {\n  const id = useId();\n  return (\n    <div className=\"field\">\n      <label htmlFor={id}>{label}</label>\n      <input\n        id={id}\n        value={value}\n        type={type}\n        inputMode={inputMode}\n        onChange={(e) => onChange(e.target.value)}\n        placeholder={placeholder}\n        autoComplete=\"off\"\n        spellCheck={false}\n        aria-describedby={hint ? `${id}-hint` : undefined}\n      />\n      {hint && (\n        <span id={`${id}-hint`} className=\"muted\">\n          {hint}\n        </span>\n      )}\n    </div>\n  );\n}\nexport function Alert({\n  children,\n  error = false,\n}: {\n  children: ReactNode;\n  error?: boolean;\n}) {\n  return (\n    <p\n      className={error ? \"notice error\" : \"notice\"}\n      role={error ? \"alert\" : undefined}\n    >\n      {children}\n    </p>\n  );\n}\nexport function Action({\n  children,\n  onClick,\n  reason,\n  secondary = false,\n  requireWallet = true,\n}: {\n  children: ReactNode;\n  onClick: () => void;\n  reason?: string;\n  secondary?: boolean;\n  requireWallet?: boolean;\n}) {\n  const { wallet: w, busy, verified, stale } = useApp();\n  const handler = () => {\n    if (requireWallet && !w.address) {\n      w.setOpen(true);\n      return;\n    }\n    if (requireWallet && w.wrong) {\n      void w.switchNetwork();\n      return;\n    }\n    onClick();\n  };\n  const label =\n    requireWallet && !w.address\n      ? \"Connect wallet\"\n      : requireWallet && w.wrong\n        ? \"Switch network using the wallet button\"\n        : children;\n  const disabled =\n    busy ||\n    (requireWallet && w.wrong) ||\n    ((!requireWallet || (!!w.address && !w.wrong)) &&\n      (!verified || stale || !!reason));\n  return (\n    <>\n      <button\n        className={`action full ${secondary ? \"secondary\" : \"\"}`}\n        disabled={disabled}\n        onClick={handler}\n      >\n        {busy ? \"Transaction in progress\u2026\" : label}\n      </button>\n      {reason && <p className=\"helper\">{reason}</p>}\n    </>\n  );\n}\nexport function ContractAction({\n  tx,\n  reason,\n  secondary,\n}: {\n  tx: Transaction;\n  reason?: string;\n  secondary?: boolean;\n}) {\n  const { request } = useApp();\n  return (\n    <Action onClick={() => request(tx)} reason={reason} secondary={secondary}>\n      {tx.title}\n    </Action>\n  );\n}\nexport function Panel({\n  title,\n  children,\n  className = \"\",\n}: {\n  title: string;\n  children: ReactNode;\n  className?: string;\n}) {\n  return (\n    <section className={`panel ${className}`}>\n      <h2>{title}</h2>\n      {children}\n    </section>\n  );\n}\nexport function Progress({\n  label,\n  value,\n  target,\n  unit = \"\",\n  valid = true,\n  missing,\n}: {\n  label: string;\n  value?: bigint;\n  target?: bigint;\n  unit?: string;\n  valid?: boolean;\n  missing?: string;\n}) {\n  const pct =\n    value !== undefined && valid && target !== undefined && target > 0n\n      ? Math.min(100, Number((value * 10000n) / target) / 100)\n      : 0;\n  return (\n    <div className=\"progress-row\">\n      <div className=\"spread\">\n        <span>{label}</span>\n        <span>\n          {valid && value !== undefined && target !== undefined\n            ? `${pct.toFixed(1)}%`\n            : missing ?? \"Unavailable\"}\n        </span>\n      </div>\n      <progress\n        value={pct}\n        max={100}\n        aria-label={label}\n        aria-valuetext={\n          valid && value !== undefined && target !== undefined\n            ? `${pct.toFixed(1)}% completed`\n            : missing ?? \"Unavailable\"\n        }\n      />\n      <div className=\"spread muted\">\n        <span>\n          {valid ? display(value) : missing ?? \"Unavailable / stale\"} {valid || !missing ? unit : \"\"}\n        </span>\n        <span>\n          of {display(target, 18, 0)} {unit}\n        </span>\n      </div>\n      <div className=\"spread muted\">\n        <span>\n          Remaining:{\" \"}\n          {valid && value !== undefined && target !== undefined\n            ? display(target > value ? target - value : 0n)\n            : missing ?? \"Unavailable\"}{\" \"}\n          {valid || !missing ? unit : \"\"}\n        </span>\n        <span>\n          {valid && value !== undefined && target !== undefined\n            ? `${(100 - pct).toFixed(1)}% remaining`\n            : missing ?? \"Unavailable\"}\n        </span>\n      </div>\n    </div>\n  );\n}\nexport function Dialog({\n  open,\n  onClose,\n  title,\n  children,\n}: {\n  open: boolean;\n  onClose: () => void;\n  title: string;\n  children: ReactNode;\n}) {\n  const titleId = useId();\n  const ref = useRef<HTMLDialogElement>(null);\n  useEffect(() => {\n    if (open) ref.current?.showModal();\n    else ref.current?.close();\n  }, [open]);\n  return (\n    <dialog\n      onKeyDown={trapDialogFocus}\n      ref={ref}\n      aria-labelledby={titleId}\n      onCancel={onClose}\n      onClick={(e) => {\n        if (e.target === ref.current) onClose();\n      }}\n    >\n      <div className=\"dialog-heading\">\n        <h2 id={titleId}>{title}</h2>\n        <button aria-label=\"Close review\" onClick={onClose}>\n          \u00d7\n        </button>\n      </div>\n      {children}\n    </dialog>\n  );\n}\nexport function EmptyWallet() {\n  return (\n    <Alert>\n      Connect your wallet to read your balances and account rights. Public\n      protocol state is available without a wallet.\n    </Alert>\n  );\n}\nexport const asAddress = (v: string) => getAddress(v.trim()) as Address;\n"
    },
    {
      "path": "web/src/GateDoors.tsx",
      "baseSha256": "969cd8ae1fad75043bf092bc9a83f551589b5f8d46713a2ac659c969ea566f2a",
      "sha256": "998e3e98de54f848ef25df3627477f76079483f0cca51b37225c94c616d02f0c",
      "content": "import { loadGateBurn } from \"./gate-history\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useApp } from \"./context\";\nimport { display, Progress } from \"./components\";\nexport function GateDoors() {\n  const { r, data: d, stale, view } = useApp();\n  const [selected, setSelected] = useState<number>(),\n    [opening, setOpening] = useState(\"\");\n  const [history, setHistory] = useState<{ gate: number; amount: bigint; block: bigint }>();\n  const [historyError, setHistoryError] = useState(\"\");\n  const [historyLoading, setHistoryLoading] = useState(false);\n  const [historyRetry, retryHistory] = useState(0);\n  const previous = useRef<number | undefined>(undefined);\n  useEffect(() => {\n    if (d && !stale) {\n      if (previous.current !== undefined && d.gates > previous.current)\n        setOpening(\n          `Gate ${d.gates} opened permanently. FDV, volume or burn targets can each open a gate.`,\n        );\n      previous.current = d.gates;\n    }\n  }, [d?.gates, stale]);\n  const gate = selected ?? Math.min((d?.gates ?? 0) + 1, 10),\n    row = d?.gateTable[gate - 1];\n  const priceAge = d ? Date.now() / 1000 - Number(d.lastObservation) : Infinity;\n  const fresh = !!d && !stale,\n    opened = fresh && gate <= d.gates,\n    current = fresh && gate === d.gates + 1,\n    future = fresh && gate > d.gates + 1;\n  useEffect(() => {\n    let canceled = false;\n    setHistory(undefined);\n    setHistoryError(\"\");\n    setHistoryLoading(false);\n    if (!d || stale || current || future) return;\n    setHistoryLoading(true);\n    void loadGateBurn(r, gate, d.block).then((n) => {\n      if (!canceled) setHistory({ gate, amount: n, block: d.block });\n    }).catch(() => {\n      if (!canceled) setHistoryError(\"Full contribution history is unavailable from the configured RPCs. Connect an Ethereum wallet with historical-log support and retry. Partial results are discarded.\");\n    }).finally(() => { if (!canceled) setHistoryLoading(false); });\n    return () => { canceled = true; };\n  }, [r, gate, d?.gates, stale, current, future, historyRetry]);\n  const burnValue = current ? d?.burnProgress : future ? 0n : history?.gate === gate ? history.amount : undefined;\n\n  return (\n    <section className=\"gate-hall\" aria-label=\"Ten permanent gates\">\n      <h2 className=\"sr-only\">Ten permanent gates</h2>\n      <div className=\"door-grid\">\n        {Array.from({ length: 10 }, (_, i) => {\n          const n = i + 1,\n            state = !fresh\n              ? \"Unread\"\n              : n <= d.gates\n                ? \"Open\"\n                : n === d.gates + 1\n                  ? \"Current\"\n                  : \"Locked\";\n          return (\n            <button\n              className=\"gate-door\"\n              data-state={state}\n              key={n}\n              aria-pressed={gate === n}\n              aria-label={`Gate ${n}: ${state}, ${d?.gateTable[i]?.[0] ?? \"unavailable\"}% cumulative selling`}\n              onClick={() => setSelected(n)}\n            >\n              <span className=\"door-number\">{String(n).padStart(2, \"0\")}</span>\n              <span className=\"door-art\" aria-hidden=\"true\">\n                <img src={`./art/portal-${state === \"Open\" ? \"open\" : state === \"Current\" ? \"current\" : \"locked\"}.webp`} width=\"384\" height=\"382\" alt=\"\" decoding=\"async\" />\n              </span>\n              <strong>{d?.gateTable[i] ? `${d.gateTable[i][0]}%` : \"?\"}</strong>\n              <span className=\"door-state\">{state}</span>\n            </button>\n          );\n        })}\n      </div>\n      <div className=\"gate-disclosure\" aria-live=\"polite\">\n        <div className=\"spread gate-hall-heading\">\n          <h3>\n            Gate {gate} \u00b7{\" \"}\n            {opened\n              ? \"Open\"\n              : current\n                ? \"Current\"\n                : fresh\n                  ? \"Future\"\n                  : \"State unavailable\"}\n          </h3>\n          <span className=\"muted\">ANY ONE target opens a gate</span>\n        <a className=\"contribute-link\" href=\"#active-gate\" onClick={(e) => {\n          e.preventDefault();\n          const form = document.getElementById(\"active-gate\");\n          if (form) { form.scrollIntoView({ block: \"start\" }); form.focus({ preventScroll: true }); }\n          else view(\"Burn & Points\");\n        }}>\n          {d?.gates === 10 ? \"Burn for points\" : \"Contribute UNLK2\"} \u2193\n        </a>\n        </div>\n        <div className=\"gate-three\">\n          <Progress\n            label=\"FDV \u00b7 processed\"\n            missing={fresh && d?.lastGateFdv === 0n ? \"Not yet observed\" : undefined}\n            value={d?.lastGateFdv}\n            target={row?.[1]}\n            unit=\"USD\"\n            valid={\n              fresh &&\n              !!d?.gatePrice[1] &&\n              !!d?.tokenPrice[1] &&\n              d.lastGateFdv > 0n &&\n              priceAge >= 0 &&\n              priceAge <= 5400\n            }\n          />\n          <Progress\n            label=\"Cumulative official volume\"\n            value={d?.volume}\n            target={row?.[2]}\n            unit=\"USD\"\n            valid={fresh}\n          />\n          <Progress\n            label=\"This gate\u2019s burn\"\n            value={burnValue}\n            target={row?.[3]}\n            unit=\"UNLK2\"\n            valid={fresh && burnValue !== undefined}\n          />\n        </div>\n        {future && <p className=\"helper\">This gate has not collected contributions. Zero burn progress is derived from verified gatesOpen at block {d?.block.toString()}; the contract rejects contributions to future gates.</p>}\n        {historyLoading && <p className=\"helper\" role=\"status\">Reading gate {gate} contribution events\u2026</p>}\n        {historyError && <div className=\"history-error\" role=\"status\"><p className=\"helper\">{historyError}</p><button className=\"small\" onClick={() => retryHistory(n => n + 1)}>Retry gate history</button></div>}\n        {history && !current && <p className=\"helper\">Gate {gate} burn total from token Contributed events through block {history.block.toString()}. Other conditions may have opened this gate before its burn target was met.</p>}\n        <details className=\"observation-details\">\n          <summary>FDV &amp; gate accounting</summary>\n        <div className=\"gate-observation\">\n          <span>Indicative live FDV <strong>{fresh && d?.fdv[1] ? `$${display(d.fdv[0], 18, 2)}` : \"Unavailable\"}</strong></span>\n          <span>Last processed gate FDV <strong>{d && d.lastGateFdv > 0n ? `$${display(d.lastGateFdv, 18, 2)}` : \"Not yet observed\"}</strong></span>\n        </div>\n        <p className=\"helper observation-note\">\n          The processed observation and indicative price are separate. {d?.lastObservation ? `Last valid volume observation: ${new Date(Number(d.lastObservation) * 1000).toISOString()}.` : \"No valid gate-volume observation recorded yet.\"} Historical observations are not fresh quotes.\n          The collecting gate uses the hook\u2019s live burn counter. Past gates require complete contribution event history. Future gates cannot receive contributions.\n        </p>\n        <p className=\"helper\">\n          Selling percentages are cumulative entitlements, not a new allowance\n          each gate.{\" \"}\n          {d?.gates === 0\n            ? \"At gate 0: buys 20%; migration and native claims 0%.\"\n            : \"Your remaining allowance also reflects past sales and burns.\"}{\" \"}\n          <a href=\"#gates\">Numerical ladder and rules \u2197</a>\n        </p>\n        </details>\n      </div>\n      {opening && (\n        <p className=\"gate-opening\" role=\"status\">\n          {opening}\n        </p>\n      )}\n    </section>\n  );\n}\n"
    },
    {
      "path": "web/src/Trade.tsx",
      "baseSha256": "d9090348e637aa2b560a4e7792066f1ade1a8dd59fbfeb8b55263228049badcf",
      "sha256": "e4be877fe196220bd0b73acb611672dec51adbd3bfa7b18137d3b80fb6ae004d",
      "content": "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { decodeFunctionData, formatUnits, type Address } from \"viem\";\nimport { useApp } from \"./context\";\nimport { Action, Alert, display, Progress, UsdValue } from \"./components\";\nimport {\n  amount,\n  erc20Abi,\n  errorText,\n  minimumOut,\n  permitAbi,\n  routerAbi,\n  same,\n  swapData,\n} from \"./core\";\nimport { ETH_ROUTE_VERIFIED, ethSwapData, quoteEthRoute } from \"./eth-route\";\nimport { portion, sellable, gasReserve, spendableEth } from \"./shortcuts\";\nimport { verifyAuxiliary, walletQuoterAbi } from \"./integration\";\n\nexport function GateProgress() {\n  const { data: d, stale } = useApp();\n  const next = d?.gateTable[Math.min(d.gates, 9)];\n  const processed = d && d.lastGateFdv > 0n && d.lastObservation > 0n;\n  const age = d ? Date.now() / 1000 - Number(d.lastObservation) : Infinity;\n  const fresh = age >= 0 && age <= 5400;\n  return (\n    <>\n      <div className=\"spread\">\n        <h2>\n          {d?.gates === 10\n            ? \"Golden Vault \u00b7 all gates open\"\n            : `Active gate ${d ? d.gates + 1 : \"pending\"}`}\n        </h2>\n        <span className=\"gate-status\">\n          {d\n            ? d.gates === 10\n              ? \"100% \u00b7 every holder\"\n              : `${d.percents[0]}% buys / ${d.percents[1]}% other \u2192 ${next?.[0]}%`\n            : \"Awaiting state\"}\n        </span>\n      </div>\n      {d?.gates !== 10 && (\n        <>\n          <Progress\n            label=\"OR \u00b7 Contract FDV \u00b7 last processed\"\n            missing={fresh && d?.lastGateFdv === 0n ? \"Not yet observed\" : undefined}\n            value={processed ? d.lastGateFdv : undefined}\n            target={next?.[1]}\n            unit=\"USD\"\n            valid={\n              !!processed &&\n              !!fresh &&\n              !!d?.gatePrice[1] &&\n              !!d?.tokenPrice[1] &&\n              !stale\n            }\n          />\n          <Progress\n            label=\"OR \u00b7 Cumulative official gross USD volume\"\n            value={d?.volume}\n            target={next?.[2]}\n            unit=\"USD\"\n            valid={!!d && !stale}\n          />\n          <Progress\n            label=\"OR \u00b7 This gate\u2019s UNLK2 burn contribution\"\n            value={d?.burnProgress}\n            target={next?.[3]}\n            unit=\"UNLK2\"\n            valid={!!d && !stale}\n          />\n        </>\n      )}\n      <p className=\"helper\">\n        Any one trigger opens the gate permanently.{\" \"}\n        {stale\n          ? \"Live state unavailable; refresh to verify progress.\"\n          : \"Open states come only from stored gatesOpen.\"}\n      </p>\n      <details>\n        <summary>Market FDV, processed FDV and pending observations</summary>\n        <p>\n          Indicative market FDV:{\" \"}\n          {d?.fdv[1] && !stale ? `$${display(d.fdv[0], 18, 2)}` : \"Unavailable\"}\n          . Uses current totalSupply ({display(d?.supply, 18, 0)} UNLK2),\n          including reserves and excluding burns.\n        </p>\n        <p>\n          Historical last processed gate FDV:{\" \"}\n          {processed ? `$${display(d.lastGateFdv, 18, 2)}` : \"Not yet observed\"}\n          ; last FDV attempt block {d?.lastFdvBlock.toString() ?? \"\u2014\"}. Last\n          valid volume observation:{\" \"}\n          {d?.lastObservation\n            ? new Date(Number(d.lastObservation) * 1000).toISOString()\n            : \"None\"}\n          . Historical values are not fresh price quotes. lastFdvBlock also\n          advances on invalid attempts; the hook has no separate timestamp for a\n          successful FDV reading.\n        </p>\n        <p>\n          Gate IMD/USD reference:{\" \"}\n          {d?.gatePrice[1] && !stale\n            ? `$${display(d.gatePrice[0], 18, 6)}`\n            : \"Unavailable\"}\n          . Pending observation could reach{\" \"}\n          {!stale && d?.gatePrice[1] && d?.tokenPrice[1]\n            ? d.pendingGates\n            : \"unavailable\"}{\" \"}\n          gates; this is a preview, never an open state.\n        </p>\n        <p>\n          The immutable hook reads the official price left by the previous block\n          before the first successful official swap of a later block. It is not\n          an immediate spot gate. Its IMD-per-ETH tracker moves at most 5% per\n          hour, with a four-hour gap cap (upper bound \u00d71.20, lower bound \u00f71.20\n          per update). Gate USD uses the lower of spot IMD/USD and\n          tracker-derived IMD/USD; IMD price declines count immediately. Invalid\n          or stale feed prices do not count. Gates opened in storage never\n          close.\n        </p>\n      </details>\n    </>\n  );\n}\nexport function Ladder() {\n  return <GateProgress />;\n}\ntype Quote = {\n  out: bigint;\n  min: bigint;\n  amount: bigint;\n  fee: bigint;\n  lp: bigint;\n  at: number;\n  block: bigint;\n  tag: string;\n  referenceLp?: bigint;\n  gas?: bigint;\n  deadline?: bigint;\n};\nexport function Trade() {\n  const { r, data: d, wallet: w, request, verified, stale } = useApp();\n  const [buy, setBuy] = useState(true),\n    [payment, setPayment] = useState<\"IMD\" | \"ETH\">(\"IMD\"),\n    [input, setInput] = useState(\"\"),\n    [slippage, setSlippage] = useState(\"0.50\");\n  const [quote, setQuote] = useState<Quote>(),\n    [error, setError] = useState(\"\"),\n    [quoting, setQuoting] = useState(false),\n    [shortcutPending, setShortcutPending] = useState(false);\n  const [allow, setAllow] = useState<{\n    token: bigint;\n    permit: bigint;\n    expiry: number;\n  }>();\n  const version = useRef(0);\n  const eth = buy && payment === \"ETH\";\n  const from = buy ? payment : \"UNLK2\",\n    to = buy ? \"UNLK2\" : \"IMD\";\n  const source = (\n    buy ? r.d.network.pairToken.address : r.addresses.UnlockV2Token\n  ) as Address;\n  const available = stale\n    ? undefined\n    : buy\n      ? eth\n        ? d?.ethBalance\n        : d?.imdBalance\n      : d?.balance !== undefined && d.sellAllowance !== undefined\n        ? sellable(d.balance, d.sellAllowance)\n        : undefined;\n  const tag = [\n    buy,\n    payment,\n    input,\n    slippage,\n    w.address,\n    w.chainId,\n    d?.gates,\n    d?.tokenPrice[0],\n    d?.imdUsd[0],\n    d?.balance,\n    d?.imdBalance,\n    d?.ethBalance,\n    d?.sellAllowance,\n    d?.poolId,\n    d?.activated,\n    verified,\n    stale,\n  ].join(\":\");\n  const liveTag = useRef(tag);\n  liveTag.current = tag;\n  const allowance = useCallback(async () => {\n    if (!w.address || eth) return;\n    const [token, p] = await Promise.all([\n      r.client.readContract({\n        address: source,\n        abi: erc20Abi,\n        functionName: \"allowance\",\n        args: [w.address, r.d.network.uniswapV4.permit2],\n      }),\n      r.client.readContract({\n        address: r.d.network.uniswapV4.permit2,\n        abi: permitAbi,\n        functionName: \"allowance\",\n        args: [w.address, source, r.d.network.uniswapV4.universalRouter],\n      }),\n    ]);\n    const a = { token, permit: p[0], expiry: p[1] };\n    setAllow(a);\n    return a;\n  }, [r, source, w.address, eth]);\n  useEffect(() => {\n    if (quote && !eth) void allowance().catch((e) => setError(errorText(e)));\n  }, [d?.time, quote?.at, allowance, eth]);\n  const ready = () => {\n    if (\n      !d?.poolKey ||\n      !d.activated ||\n      !d.routerSupported ||\n      !verified ||\n      stale ||\n      !w.address ||\n      w.wrong\n    )\n      throw Error(\"Connect on Ethereum and wait for verified active trading.\");\n    if (!eth && !d!.quoterSupported)\n      throw Error(\n        \"The reviewed wallet quoter is not registered. IMD trading is unavailable.\",\n      );\n    if (eth && !ETH_ROUTE_VERIFIED)\n      throw Error(\"ETH mode awaits actual-buyer fork verification.\");\n  };\n  const getQuote = async () => {\n    const id = ++version.current,\n      atTag = tag;\n    setQuote(undefined);\n    setError(\"\");\n    setQuoting(true);\n    try {\n      ready();\n      const n = amount(input);\n      minimumOut(1n, slippage);\n      if (available === undefined || n > available)\n        throw Error(\n          buy\n            ? `Insufficient available ${from}.`\n            : \"Amount exceeds your currently sellable UNLK2.\",\n        );\n      let q: Omit<Quote, \"min\" | \"amount\" | \"tag\">;\n      if (eth) {\n        q = await quoteEthRoute(r, d!.poolKey!, w.address!, n, 1n, slippage);\n        const fees = await r.client.estimateFeesPerGas();\n        const balance = await r.client.getBalance({ address: w.address! });\n        if (n > spendableEth(balance, gasReserve(q.gas!, fees.maxFeePerGas)))\n          throw Error(\n            \"Keep more ETH for the fresh gas estimate and fee buffer. Use a smaller amount.\",\n          );\n      } else {\n        const target = await verifyAuxiliary(r, \"quoter\"),\n          block = await r.client.getBlockNumber();\n        const result = await r.client.simulateContract({\n          address: target,\n          abi: [...walletQuoterAbi, ...r.abis.UnlockV2Hook],\n          functionName: \"quoteExactInput\",\n          args: [same(source, d!.poolKey!.currency0), n],\n          account: w.address!,\n          blockNumber: block,\n        });\n        const [out, fee, lp] = result.result as readonly [\n          bigint,\n          bigint,\n          bigint,\n        ];\n        q = { out, fee, lp, block, at: Date.now() };\n      }\n      if (id !== version.current || liveTag.current !== atTag) return;\n      const min = minimumOut(q.out, slippage);\n      if (min <= 0n) throw Error(\"No usable output was quoted.\");\n      setQuote({ ...q, min, amount: n, tag: atTag });\n      await allowance();\n    } catch (e) {\n      if (id === version.current) setError(errorText(e));\n    } finally {\n      if (id === version.current) setQuoting(false);\n    }\n  };\n  useEffect(() => {\n    ++version.current;\n    setQuote(undefined);\n    setAllow(undefined);\n    setError(\"\");\n    setQuoting(false);\n    if (!input || !w.address || !d?.activated || !verified || stale) return;\n    const timer = setTimeout(() => void getQuote(), 500);\n    return () => {\n      clearTimeout(timer);\n      ++version.current;\n    };\n  }, [tag]);\n  useEffect(() => {\n    const timer = setInterval(() => {\n      if (quote && Date.now() - quote.at > 45000) {\n        setQuote(undefined);\n        setError(\"Quote expired. Refresh before trading.\");\n      }\n    }, 1000);\n    return () => clearInterval(timer);\n  }, [quote]);\n  const shortcut = async (percent: number) => {\n    const initialTag = tag;\n    setError(\"\");\n    setQuote(undefined);\n    ++version.current;\n    setShortcutPending(true);\n    try {\n      if (!w.address || w.wrong || !verified || stale)\n        throw Error(\"Connect on Ethereum and refresh balances first.\");\n      let base: bigint;\n      if (!buy) {\n        const [balance, quota] = await Promise.all([\n          r.client.readContract({\n            address: r.addresses.UnlockV2Token,\n            abi: erc20Abi,\n            functionName: \"balanceOf\",\n            args: [w.address],\n          }),\n          r.client.readContract({\n            address: r.addresses.UnlockV2Hook,\n            abi: r.abis.UnlockV2Hook,\n            functionName: \"sellAllowance\",\n            args: [w.address],\n          }),\n        ]);\n        base = sellable(balance, quota as bigint);\n      } else if (!eth)\n        base = await r.client.readContract({\n          address: source,\n          abi: erc20Abi,\n          functionName: \"balanceOf\",\n          args: [w.address],\n        });\n      else {\n        ready();\n        const [balance, fees] = await Promise.all([\n          r.client.getBalance({ address: w.address }),\n          r.client.estimateFeesPerGas(),\n        ]);\n        const seed = balance / 100n;\n        if (seed <= 0n) throw Error(\"No spendable ETH.\");\n        const probe = await quoteEthRoute(\n          r,\n          d!.poolKey!,\n          w.address,\n          seed,\n          1n,\n          slippage,\n        );\n        base = spendableEth(balance, gasReserve(probe.gas, fees.maxFeePerGas));\n        // Re-estimate the actual selected size; larger trades may cross more ticks.\n        for (let i = 0; i < 3; i++) {\n          const selected = portion(base, percent);\n          if (!selected) throw Error(\"No ETH remains after network fees.\");\n          const actual = await quoteEthRoute(\n            r,\n            d!.poolKey!,\n            w.address,\n            selected,\n            1n,\n            slippage,\n          );\n          const freshBalance = await r.client.getBalance({\n            address: w.address,\n          });\n          const freshFees = await r.client.estimateFeesPerGas();\n          const next = spendableEth(\n            freshBalance,\n            gasReserve(actual.gas, freshFees.maxFeePerGas),\n          );\n          if (portion(next, percent) >= selected) break;\n          base = next;\n          if (i === 2)\n            throw Error(\"Gas changed during estimation. Retry the shortcut.\");\n        }\n      }\n      if (liveTag.current !== initialTag) {\n        setError(\"State changed while calculating this amount. Retry the shortcut.\");\n        return;\n      }\n      setInput(formatUnits(portion(base, percent), 18));\n    } catch (e) {\n      setError(errorText(e));\n    } finally {\n      setShortcutPending(false);\n    }\n  };\n  const step = async () => {\n    try {\n      ready();\n      const q = quote;\n      if (!q || q.tag !== liveTag.current || Date.now() - q.at > 45000)\n        throw Error(\"Get a fresh quote first.\");\n      const revalidate = async () => {\n        if (q.tag !== liveTag.current)\n          throw Error(\"Wallet, inputs or state changed. Refresh the quote.\");\n        const h = (functionName: string, args: readonly unknown[] = []) =>\n          r.client.readContract({\n            address: r.addresses.UnlockV2Hook,\n            abi: r.abis.UnlockV2Hook,\n            functionName,\n            args,\n          });\n        if (\n          !(await h(\"activated\")) ||\n          !(await h(\"isRouter\", [r.d.network.uniswapV4.universalRouter]))\n        )\n          throw Error(\"Trading or router is unavailable.\");\n        if (eth) {\n          const fresh = await quoteEthRoute(\n            r,\n            d!.poolKey!,\n            w.address!,\n            q.amount,\n            q.min,\n            undefined,\n            q.deadline,\n          );\n          const [balance, fees] = await Promise.all([\n            r.client.getBalance({ address: w.address! }),\n            r.client.estimateFeesPerGas(),\n          ]);\n          if (\n            q.amount >\n            spendableEth(balance, gasReserve(fresh.gas, fees.maxFeePerGas))\n          )\n            throw Error(\n              \"ETH balance cannot cover input plus buffered gas. Reduce the amount.\",\n            );\n        } else {\n          const balance = await r.client.readContract({\n            address: source,\n            abi: erc20Abi,\n            functionName: \"balanceOf\",\n            args: [w.address!],\n          });\n          const base = buy\n            ? balance\n            : sellable(\n                balance,\n                (await h(\"sellAllowance\", [w.address!])) as bigint,\n              );\n          if (q.amount > base)\n            throw Error(\n              \"Balance or currently sellable amount changed. Refresh the quote.\",\n            );\n          const target = await verifyAuxiliary(r, \"quoter\");\n          const fresh = await r.client.simulateContract({\n            address: target,\n            abi: walletQuoterAbi,\n            functionName: \"quoteExactInput\",\n            args: [same(source, d!.poolKey!.currency0), q.amount],\n            account: w.address!,\n          });\n          if (fresh.result[0] < q.min)\n            throw Error(\n              \"Output fell below the reviewed minimum. Refresh the quote.\",\n            );\n        }\n      };\n      if (!eth) {\n        const a = await allowance();\n        if (q.tag !== liveTag.current || !a)\n          throw Error(\"Inputs changed. Refresh the quote.\");\n        if (a.token < q.amount)\n          return request({\n            title: `Approve ${from}`,\n            description: `Permit2 may spend exactly ${formatUnits(q.amount, 18)} ${from}.`,\n            address: source,\n            abi: erc20Abi,\n            functionName: \"approve\",\n            args: [r.d.network.uniswapV4.permit2, q.amount],\n            validUntil: q.at + 45000,\n            validate: revalidate,\n          });\n        if (a.permit < q.amount || a.expiry < Date.now() / 1000 + 300)\n          return request({\n            title: \"Approve router in Permit2\",\n            description: `Authorize exactly ${formatUnits(q.amount, 18)} ${from} for 30 minutes.`,\n            address: r.d.network.uniswapV4.permit2,\n            abi: permitAbi,\n            functionName: \"approve\",\n            args: [\n              source,\n              r.d.network.uniswapV4.universalRouter,\n              q.amount,\n              Math.floor(Date.now() / 1000) + 1800,\n            ],\n            validUntil: q.at + 45000,\n            validate: revalidate,\n          });\n      }\n      const deadline =\n        q.deadline ?? BigInt(Math.floor(Date.now() / 1000) + 180);\n      const data = eth\n        ? ethSwapData(\n            d!.poolKey!,\n            r.d.network.pairToken.address,\n            r.addresses.UnlockV2Token,\n            w.address!,\n            q.amount,\n            q.min,\n            deadline,\n          )\n        : swapData(\n            d!.poolKey!,\n            source,\n            q.amount,\n            q.min,\n            deadline,\n            !!r.d.network.uniswapV4.extendedSwapParams,\n          );\n      request({\n        title: `Swap ${from} \u2192 ${to}`,\n        description: `Spend ${formatUnits(q.amount, 18)} ${from}; receive at least ${formatUnits(q.min, 18)} ${to}.${eth ? \" Native ETH \u2192 pinned ETH/IMD pool \u2192 official IMD/UNLK2 pool \u2192 your wallet. Both legs revert together; no IMD approval is needed.\" : \" Only the hook\u2019s official pool is used.\"}`,\n        address: r.d.network.uniswapV4.universalRouter,\n        abi: routerAbi,\n        functionName: \"execute\",\n        args: decodeFunctionData({ abi: routerAbi, data }).args,\n        value: eth ? q.amount : 0n,\n        validUntil: q.at + 45000,\n        validate: revalidate,\n      });\n    } catch (e) {\n      setError(errorText(e));\n      setQuote(undefined);\n    }\n  };\n  const reason = !d\n    ? \"Reading verified pool state.\"\n    : !d.activated\n      ? d.setupReady\n        ? \"Trading closed \u00b7 setup verified; awaiting activation.\"\n        : \"Trading closed \u00b7 review live integration status.\"\n      : !d.routerSupported\n        ? \"Pinned Universal Router unavailable.\"\n        : eth && !ETH_ROUTE_VERIFIED\n          ? \"ETH buying is disabled until the actual-buyer fork suite passes.\"\n          : (!r.repair.quoter || !d.quoterSupported) && !eth\n            ? \"Reviewed wallet quoter unavailable or unregistered.\"\n            : undefined;\n  return (\n    <section className=\"panel swap-panel\" aria-label=\"Swap UNLK2\">\n      <div className=\"swap-topbar\">\n      <div className=\"spread\">\n        <h2>Swap UNLK2</h2>\n      </div>\n      <div className=\"segmented\">\n        <button aria-pressed={buy} onClick={() => setBuy(true)}>\n          Buy UNLK2\n        </button>\n        <button aria-pressed={!buy} onClick={() => setBuy(false)}>\n          Sell UNLK2\n        </button>\n      </div>\n      </div>\n      {buy && (\n        <div className=\"payment-choice\" role=\"group\" aria-label=\"Pay with\">\n          <span>Pay with</span>\n          <button\n            aria-pressed={payment === \"IMD\"}\n            onClick={() => {\n              setPayment(\"IMD\");\n              setInput(\"\");\n            }}\n          >\n            IMD\n          </button>\n          <button\n            aria-pressed={payment === \"ETH\"}\n            onClick={() => {\n              setPayment(\"ETH\");\n              setInput(\"\");\n            }}\n          >\n            ETH\n          </button>\n        </div>\n      )}\n      <div className=\"swap-input-row\">\n      <div className=\"spread\">\n        <label htmlFor=\"swap-amount\">You pay \u00b7 {from}</label>\n        <span className=\"muted\">\n          {buy ? \"Balance\" : \"Currently sellable\"} {display(available)}\n        </span>\n      </div>\n      <input\n        id=\"swap-amount\"\n        inputMode=\"decimal\"\n        autoComplete=\"off\"\n        placeholder=\"0.00\"\n        value={input}\n        onChange={(e) => setInput(e.target.value)}\n        aria-invalid={!!error}\n        aria-describedby={\n          error ? \"shortcut-context swap-error\" : \"shortcut-context\"\n        }\n      />\n      </div>\n      <div className=\"shortcuts\" role=\"group\" aria-label=\"Amount shortcuts\">\n        {[20, 50, 75, 100].map((p) => (\n          <button\n            key={p}\n            disabled={\n              !w.address ||\n              w.wrong ||\n              stale ||\n              shortcutPending ||\n              (eth && (!ETH_ROUTE_VERIFIED || !d?.activated))\n            }\n            onClick={() => void shortcut(p)}\n          >\n            {p === 100 ? \"MAX\" : `${p}%`}\n          </button>\n        ))}\n      </div>\n      {shortcutPending && <p className=\"helper\" role=\"status\">Reading fresh balance and gas\u2026</p>}\n      <div className=\"receive-line\">\n        <span className=\"muted\">You receive \u00b7 {to}</span>\n        <strong>\n          {quoting ? \"Quoting\u2026\" : quote ? display(quote.out) : \"Unavailable\"}\n        </strong>\n      </div>\n      <div className=\"swap-settings-row\">\n      <p className=\"visible-route\"><span>Fixed official route</span><strong>{eth ? \"ETH \u2192 IMD \u2192 UNLK2\" : buy ? \"IMD \u2192 UNLK2\" : \"UNLK2 \u2192 IMD\"}</strong></p>\n      <div className=\"slippage-setting\">\n        <label htmlFor=\"slippage\">Slippage %</label>\n        <input\n          id=\"slippage\"\n          inputMode=\"decimal\"\n          value={slippage}\n          onChange={(e) => setSlippage(e.target.value)}\n        />\n      </div>\n      </div>\n      {quote && (\n        <dl className=\"compact-dl\">\n          <dt>Minimum received</dt>\n          <dd>\n            {display(quote.min)} {to}\n          </dd>\n          <dt>Execution price \u00b7 incl. fees</dt>\n          <dd>\n            {display(\n              buy\n                ? (quote.amount * 10n ** 18n) / quote.out\n                : (quote.out * 10n ** 18n) / quote.amount,\n              18,\n              10,\n            )}{\" \"}\n            {buy ? from : \"IMD\"} / UNLK2\n          </dd>\n          <dt>Reward fee \u00b7 exact</dt>\n          <dd>{display(quote.fee)} IMD</dd>\n          <dt>Official LP fee \u00b7 estimate</dt>\n          <dd>\n            {display(quote.lp)} {eth ? \"IMD\" : from}\n          </dd>\n          {eth && (\n            <>\n              <dt>Reference LP fee \u00b7 estimate</dt>\n              <dd>{display(quote.referenceLp)} ETH</dd>\n              <dt>Route gas estimate</dt>\n              <dd>{quote.gas?.toString()} units</dd>\n            </>\n          )}\n          <dt>Quoted at</dt>\n          <dd>Block {quote.block.toString()} \u00b7 45s validity</dd>\n        </dl>\n      )}\n      {error && (\n        <div id=\"swap-error\">\n          <Alert error>{error}</Alert>\n        </div>\n      )}\n      <Action\n        onClick={() => void (quote ? step() : getQuote())}\n        reason={quoting ? \"Quoting\u2026\" : reason}\n      >\n        {quote\n          ? allow && !eth && allow.token < quote.amount\n            ? `Approve ${from}`\n            : allow &&\n                !eth &&\n                (allow.permit < quote.amount ||\n                  allow.expiry < Date.now() / 1000 + 300)\n              ? \"Approve router\"\n              : \"Review swap\"\n          : \"Get live quote\"}\n      </Action>\n      <details className=\"swap-context\">\n        <summary>Route, fees and protections</summary>\n      <p id=\"shortcut-context\" className=\"helper\">\n        {shortcutPending\n          ? \"Reading fresh balance and gas\u2026\"\n          : !buy\n            ? \"Percentages are portions of your CURRENTLY SELLABLE amount, capped by balance and onchain allowance.\"\n            : eth\n              ? \"Shortcuts use spendable ETH after a fresh gas estimate, 20% gas margin and 25% fee buffer.\"\n              : \"Percentages use your available IMD balance. Keep ETH for network fees.\"}\n      </p>\n      {!eth && (\n        <UsdValue\n          value={(() => {\n            try {\n              return amount(input);\n            } catch {\n              return undefined;\n            }\n          })()}\n          token={buy ? \"IMD\" : \"UNLK2\"}\n        />\n      )}\n        <p>\n          {eth\n            ? \"Native ETH \u2192 ETH/IMD v4 (1% LP) \u2192 official IMD/UNLK2 (1.25% LP + 1% IMD reward fee) \u2192 connected buyer. Full-route RPC simulation includes first-leg price changes and hook fees. No pre-owned IMD, approvals, split routes or fallback pools. Unsupported simulation stops the trade.\"\n            : \"IMD \u2194 UNLK2 through the hook\u2019s official pool only. WalletQuoter includes hook effects and wallet sell rights. 1.25% LP fee and 1% IMD reward fee.\"}{\" \"}\n          Network fees are additional. Minimum output and a three-minute router\n          deadline are enforced. Every transaction is simulated again before\n          signing.\n        </p>\n      </details>\n    </section>\n  );\n}\n"
    },
    {
      "path": "web/src/style.css",
      "baseSha256": "8629b436ed828df04f094ac74bc295f01575220a7806f5792e54603cacc947c2",
      "sha256": "73356e8cbd765cbd92530df7f4e451f23fdc02b8883634529a66913ad630c229",
      "content": ":root {\n  color-scheme: dark;\n  --bg: #050807;\n  --surface: #09100e;\n  --hover: #14251e;\n  --text: #f2f1e6;\n  --muted: #a9b9b1;\n  --border: #35453b;\n  --mint: #54edb3;\n  --cyan: #8fe4dc;\n  --amber: #ecd08c;\n  --error: #ffb0a8;\n  --ink: #04110b;\n  --sm: 0.8125rem;\n  --body: 0.875rem;\n  --input: 1rem;\n  --sub: 1.125rem;\n  --heading: 1.5rem;\n  --amount: 1.75rem;\n  font-family: \"Manrope\", system-ui, sans-serif;\n  font-size: 16px;\n  font-variant-numeric: tabular-nums;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  background: var(--bg);\n  color: var(--text);\n}\n* {\n  box-sizing: border-box;\n}\nbody {\n  background: var(--bg);\n  margin: 0;\n  font-size: var(--body);\n  line-height: 1.6;\n}\nbutton,\ninput,\nselect,\ntextarea {\n  font: inherit;\n  color: inherit;\n}\nbutton,\na,\ninput,\nselect,\ntextarea,\nsummary {\n  -webkit-tap-highlight-color: transparent;\n}\nbutton {\n  cursor: pointer;\n  border: 1px solid var(--border);\n  background: transparent;\n  padding: 9px 14px;\n  min-height: 44px;\n  border-radius: 2px;\n  line-height: 1.45;\n}\nbutton:disabled {\n  cursor: not-allowed;\n  color: var(--muted);\n  background: var(--surface);\n}\na {\n  color: var(--cyan);\n  text-underline-offset: 4px;\n  text-decoration-thickness: 1px;\n}\na,\nbutton,\nsummary {\n  touch-action: manipulation;\n}\na:hover {\n  text-decoration: underline;\n}\nbutton:hover:enabled {\n  background: var(--hover);\n  border-color: var(--muted);\n}\n:focus-visible {\n  outline: 2px solid var(--cyan);\n  outline-offset: 4px;\n}\ninput,\ntextarea,\nselect {\n  width: 100%;\n  min-width: 0;\n  border: 1px solid var(--border);\n  background: var(--bg);\n  border-radius: 2px;\n  padding: 11px 12px;\n  min-height: 44px;\n  font-size: var(--input);\n}\ninput::placeholder {\n  color: var(--muted);\n  opacity: 1;\n}\ntextarea {\n  resize: vertical;\n}\ninput[type=\"checkbox\"] {\n  width: 20px;\n  min-height: 20px;\n  height: 20px;\n  accent-color: var(--mint);\n  flex-shrink: 0;\n  margin: 0;\n}\nh1,\nh2,\nh3,\np {\n  margin: 0;\n}\nh1 {\n  font-size: var(--heading);\n  font-weight: 600;\n  line-height: 1.3;\n  letter-spacing: -0.03em;\n}\nh2 {\n  font-size: var(--sub);\n  font-weight: 600;\n  line-height: 1.4;\n  margin-bottom: 16px;\n}\nh3 {\n  font-size: var(--input);\n  font-weight: 600;\n  margin: 18px 0 10px;\n  line-height: 1.5;\n}\np {\n  max-width: 76ch;\n  margin: 12px 0;\n}\nsummary {\n  cursor: pointer;\n  min-height: 44px;\n  padding: 10px 0;\n  color: var(--cyan);\n}\ndetails > p {\n  font-size: var(--sm);\n  color: var(--muted);\n}\nselect {\n  cursor: pointer;\n}\npre {\n  white-space: pre-wrap;\n  overflow-wrap: anywhere;\n  font-size: var(--sm);\n  line-height: 1.7;\n}\ntable {\n  width: 100%;\n  border-collapse: collapse;\n  table-layout: fixed;\n  font-size: var(--sm);\n}\nth,\ntd {\n  text-align: start;\n  border-bottom: 1px solid var(--border);\n  padding: 12px 8px;\n  overflow-wrap: anywhere;\n  font-weight: 400;\n}\nthead th {\n  color: var(--muted);\n}\ncaption {\n  text-align: start;\n  color: var(--muted);\n  padding: 0 0 12px;\n  line-height: 1.5;\n}\ndl {\n  margin: 0;\n}\ndt {\n  color: var(--muted);\n}\ndd {\n  margin: 0;\n  overflow-wrap: anywhere;\n}\nlabel {\n  display: block;\n}\n.muted {\n  color: var(--muted);\n}\n.mint {\n  color: var(--mint);\n}\n.amber {\n  color: var(--amber);\n}\n.error {\n  color: var(--error);\n}\n.address {\n  overflow-wrap: anywhere;\n  word-break: break-word;\n}\n.small {\n  font-size: var(--sm);\n  min-height: 36px;\n  padding: 6px 10px;\n}\n.full {\n  width: 100%;\n  margin-top: 10px;\n}\n.action {\n  background: var(--mint);\n  color: var(--ink);\n  border-color: var(--mint);\n  font-weight: 600;\n}\n.action:hover:enabled {\n  background: #c6f9e1;\n  border-color: #c6f9e1;\n}\n.action.secondary {\n  background: transparent;\n  color: var(--text);\n  border-color: var(--border);\n}\n.action:disabled {\n  color: var(--muted);\n  background: var(--hover);\n  border-color: var(--border);\n}\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  overflow: hidden;\n}\n.skip-link {\n  position: fixed;\n  inset-block-start: 8px;\n  inset-inline-start: 8px;\n  transform: translateY(-160%);\n  background: var(--mint);\n  color: var(--ink);\n  padding: 12px;\n  z-index: 20;\n}\n.skip-link:focus {\n  transform: translateY(0);\n}\n.site-header {\n  max-width: 1440px;\n  margin: auto;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 12px;\n  padding: 18px 32px;\n}\n.brand {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  color: var(--text);\n  text-decoration: none;\n  font-size: var(--sub);\n  font-weight: 600;\n  letter-spacing: -0.02em;\n  flex-shrink: 0;\n}\n.brand svg {\n  color: var(--mint);\n}\n.version {\n  font-size: var(--body);\n  color: var(--mint);\n  margin-left: 8px;\n}\n.header-utilities {\n  display: flex;\n  gap: 22px;\n  align-items: center;\n  min-width: 0;\n}\n.chain-label {\n  color: var(--muted);\n  font-size: var(--sm);\n}\n.chain-label i {\n  display: inline-block;\n  width: 6px;\n  height: 6px;\n  background: var(--mint);\n  border-radius: 50%;\n  margin-right: 8px;\n}\n.wallet-button {\n  border-color: var(--mint);\n  color: var(--mint);\n}\n.desktop-nav {\n  max-width: 1360px;\n  margin: 0 auto;\n  display: flex;\n  gap: 5px;\n  flex-wrap: wrap;\n  border-top: 1px solid var(--border);\n  border-bottom: 1px solid var(--border);\n  padding: 3px 0;\n}\n.desktop-nav a {\n  color: var(--muted);\n  text-decoration: none;\n  padding: 10px 14px;\n  min-height: 44px;\n  font-size: var(--sm);\n  border-bottom: 2px solid transparent;\n}\n.desktop-nav a[aria-current=\"page\"] {\n  color: var(--mint);\n  border-color: var(--mint);\n}\n.mobile-nav {\n  display: none;\n}\nmain {\n  max-width: 1440px;\n  margin: auto;\n  padding: 20px 32px 40px;\n  min-height: calc(100vh - 220px);\n}\n.page-heading {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 16px;\n  margin-bottom: 14px;\n}\n.page-heading h1 .muted {\n  font-weight: 400;\n  font-size: var(--sub);\n  letter-spacing: 0;\n}\n.read-status {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  font-size: var(--sm);\n}\n.system-message {\n  display: flex;\n  gap: 16px;\n  align-items: center;\n  justify-content: space-between;\n  border-left: 2px solid var(--amber);\n  padding: 10px 14px;\n  margin: 0 0 20px;\n  background: var(--surface);\n  font-size: var(--sm);\n}\n.system-message details {\n  max-width: 420px;\n}\n.system-message summary {\n  padding: 0;\n  min-height: 32px;\n}\n.system-message p {\n  overflow-wrap: anywhere;\n}\n.market-strip {\n  display: grid;\n  grid-template-columns: repeat(4, minmax(0, 1fr));\n  border-block: 1px solid var(--border);\n  padding: 12px;\n  margin-bottom: 16px;\n  gap: 20px;\n}\n.stat {\n  min-width: 0;\n}\n.stat > span {\n  font-size: var(--sm);\n}\n.stat-value {\n  font-size: var(--input);\n  overflow-wrap: anywhere;\n  line-height: 1.6;\n}\n.unit {\n  font-size: var(--sm);\n  color: var(--muted);\n}\n.panel {\n  border: 1px solid var(--border);\n  padding: 20px;\n  min-width: 0;\n  background: var(--surface);\n  margin-bottom: 24px;\n}\n.panel > .spread h2 {\n  margin-bottom: 0;\n}\n.panel > h2 {\n  margin-bottom: 18px;\n}\n.spread {\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n  gap: 12px;\n  min-width: 0;\n}\n.spread > span {\n  overflow-wrap: anywhere;\n}\n.two-col {\n  display: grid;\n  grid-template-columns: repeat(2, minmax(0, 1fr));\n  gap: 24px;\n}\n.two-col .panel {\n  margin-bottom: 0;\n}\n.two-col {\n  margin-bottom: 24px;\n}\n.trade-grid {\n  display: grid;\n  grid-template-columns: minmax(0, 1fr) minmax(340px, 0.85fr);\n  gap: 24px;\n  align-items: start;\n}\n.swap-panel {\n  grid-column: 2;\n  grid-row: 1;\n}\n.ladder {\n  grid-column: 1;\n  grid-row: 1;\n}\n.segmented {\n  display: flex;\n  border: 1px solid var(--border);\n  margin: 20px 0;\n}\n.segmented button {\n  flex: 1;\n  border: 0;\n  border-radius: 0;\n  color: var(--muted);\n  padding: 11px 8px;\n  min-width: 0;\n}\n.segmented button[aria-pressed=\"true\"] {\n  color: var(--mint);\n  background: var(--hover);\n  box-shadow: inset 0 -2px var(--mint);\n}\n.field-heading {\n  font-size: var(--sm);\n  margin-top: 20px;\n}\n.amount-input {\n  display: flex;\n  align-items: center;\n  gap: 16px;\n  margin-top: 7px;\n  border-bottom: 1px solid var(--border);\n}\n.amount-input input {\n  border: 0;\n  background: transparent;\n  padding: 8px 0;\n  font-size: var(--amount);\n  width: 100%;\n  min-width: 0;\n  line-height: 1.6;\n}\n.amount-input > span {\n  color: var(--mint);\n  flex-shrink: 0;\n}\n.swap-direction {\n  color: var(--muted);\n  margin: 9px 0;\n  font-size: var(--input);\n}\n.quote-output {\n  font-size: var(--amount);\n  line-height: 1.7;\n  overflow-wrap: anywhere;\n  min-height: 48px;\n}\n.slippage {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 12px;\n  margin: 18px 0;\n}\n.slippage > div {\n  display: flex;\n  align-items: center;\n  gap: 7px;\n}\n.slippage input {\n  width: 80px;\n  padding: 8px;\n}\n.compact-dl {\n  font-size: var(--sm);\n  margin: 12px 0;\n}\n.compact-dl:not(:has(.dl-row)) {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 6px 14px;\n}\n.compact-dl dd {\n  text-align: end;\n}\n.dl-row {\n  display: flex;\n  justify-content: space-between;\n  gap: 16px;\n  margin: 7px 0;\n}\n.dl-row dd {\n  text-align: end;\n}\n.helper {\n  color: var(--muted);\n  font-size: var(--sm);\n  line-height: 1.6;\n  margin: 10px 0;\n}\n.progress-row {\n  margin: 22px 0;\n}\n.progress-row .spread {\n  font-size: var(--sm);\n}\nprogress {\n  appearance: none;\n  -webkit-appearance: none;\n  display: block;\n  width: 100%;\n  height: 5px;\n  border: 0;\n  border-radius: 0;\n  margin: 9px 0;\n  background: var(--border);\n}\nprogress::-webkit-progress-bar {\n  background: var(--border);\n}\nprogress::-webkit-progress-value {\n  background: var(--mint);\n}\nprogress::-moz-progress-bar {\n  background: var(--mint);\n}\n.ladder-title {\n  margin-top: 30px;\n  padding-top: 20px;\n  border-top: 1px solid var(--border);\n}\n.ladder-title h3 {\n  margin: 0;\n  font-size: var(--body);\n}\n.ladder-title > span {\n  font-size: var(--sm);\n}\n.ladder-head,\n.ladder-row {\n  display: grid;\n  grid-template-columns: 1fr 75px 96px;\n  gap: 12px;\n  padding: 10px 12px;\n  border-bottom: 1px solid var(--border);\n  font-size: var(--sm);\n}\n.ladder-head {\n  color: var(--muted);\n  margin-top: 12px;\n}\n.ladder-row > span:first-child {\n  display: flex;\n  align-items: center;\n  gap: 16px;\n}\n.ladder-row > span:last-child {\n  text-align: end;\n  color: var(--muted);\n}\n.ladder-row.current {\n  border-left: 2px solid var(--mint);\n  background: var(--hover);\n}\n.ladder-row.opened {\n  color: var(--mint);\n}\n.ladder-segment {\n  height: 3px;\n  background: var(--border);\n  max-width: 140px;\n}\n.current .ladder-segment,\n.opened .ladder-segment {\n  background: var(--mint);\n}\n.field {\n  margin: 18px 0;\n}\n.field label {\n  margin-bottom: 7px;\n}\n.field .muted {\n  display: block;\n  margin-top: 6px;\n  font-size: var(--sm);\n}\n.check {\n  display: flex;\n  gap: 12px;\n  align-items: center;\n  min-height: 44px;\n  margin: 16px 0;\n  cursor: pointer;\n  font-size: var(--sm);\n}\n.metrics {\n  display: grid;\n  grid-template-columns: repeat(2, minmax(0, 1fr));\n  gap: 20px;\n  margin: 22px 0;\n}\n.form-grid {\n  display: grid;\n  grid-template-columns: repeat(2, minmax(0, 1fr));\n  gap: 18px;\n}\n.notice {\n  border-left: 2px solid var(--amber);\n  padding: 8px 12px;\n  color: var(--amber);\n  background: var(--bg);\n  font-size: var(--sm);\n}\n.notice.error {\n  color: var(--error);\n  border-color: var(--error);\n}\n.address-line {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  margin: 12px 0;\n  min-width: 0;\n}\n.address-line a {\n  min-width: 0;\n}\n.address-line button {\n  flex-shrink: 0;\n}\n.contract-row {\n  border-bottom: 1px solid var(--border);\n  padding: 6px 0 16px;\n}\n.contract-row h3 {\n  margin-top: 10px;\n}\n.contract-row p {\n  font-size: var(--sm);\n}\n.review-list {\n  display: grid;\n  grid-template-columns: 140px minmax(0, 1fr);\n  gap: 12px;\n  margin: 20px 0;\n  font-size: var(--sm);\n}\n.allocation > div {\n  display: flex;\n  justify-content: space-between;\n  border-bottom: 1px solid var(--border);\n  padding: 8px 0;\n}\n.allocation span {\n  color: var(--mint);\n}\n.golden-graphic {\n  color: var(--amber);\n  padding: 12px 0;\n}\n.checklist > div {\n  display: grid;\n  grid-template-columns: 20px minmax(0, 1fr) 56px;\n  gap: 12px;\n  padding: 10px 0;\n  border-bottom: 1px solid var(--border);\n  font-size: var(--sm);\n}\n.checklist > div > span:last-child {\n  text-align: end;\n}\n.transaction-link {\n  display: block;\n  margin-bottom: 14px;\n}\nfooter {\n  max-width: 1440px;\n  margin: 0 auto;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 20px;\n  padding: 24px 40px;\n  border-top: 1px solid var(--border);\n  color: var(--muted);\n  font-size: var(--sm);\n}\ndialog {\n  background: var(--surface);\n  color: var(--text);\n  border: 1px solid var(--border);\n  border-radius: 3px;\n  padding: 24px;\n  max-width: 540px;\n  width: calc(100% - 32px);\n  max-height: 90dvh;\n  overscroll-behavior: contain;\n}\ndialog::backdrop {\n  background: rgba(0, 0, 0, 0.8);\n}\n.dialog-heading {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 20px;\n  margin-bottom: 12px;\n}\n.dialog-heading h2 {\n  margin: 0;\n}\n.dialog-heading > button {\n  font-size: var(--sub);\n  min-width: 44px;\n}\n.wallet-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  width: 100%;\n  min-height: 52px;\n  margin: 10px 0;\n  text-decoration: none;\n  border: 1px solid var(--border);\n  padding: 12px;\n  color: var(--text);\n}\n.wallet-row img {\n  width: 24px;\n  height: 24px;\n  object-fit: contain;\n}\n.wallet-row > span {\n  margin-left: auto;\n}\n.wallet-dialog > p {\n  font-size: var(--sm);\n}\n@media (min-width: 1441px) {\n  .desktop-nav {\n    max-width: 1360px;\n  }\n}\n@media (max-width: 1100px) {\n  .site-header {\n    padding: 20px 24px;\n  }\n  .desktop-nav {\n    margin: 0 24px;\n  }\n  main {\n    padding: 24px;\n  }\n  .trade-grid {\n    gap: 20px;\n    grid-template-columns: minmax(0, 1fr) minmax(320px, 0.9fr);\n  }\n  .panel {\n    padding: 20px;\n  }\n  .two-col {\n    gap: 20px;\n  }\n  footer {\n    padding: 24px;\n  }\n}\n@media (max-width: 760px) {\n  .site-header {\n    padding: 16px;\n    gap: 8px;\n  }\n  .brand {\n    font-size: var(--input);\n    gap: 8px;\n  }\n  .brand svg {\n    width: 22px;\n    height: 26px;\n  }\n  .version {\n    margin-left: 6px;\n    font-size: var(--sm);\n  }\n  .chain-label {\n    display: none;\n  }\n  .header-utilities {\n    gap: 0;\n  }\n  .wallet-button {\n    font-size: var(--sm);\n    padding: 9px 10px;\n  }\n  .desktop-nav {\n    display: none;\n  }\n  .mobile-nav {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    gap: 14px;\n    padding: 0 16px 14px;\n    border-bottom: 1px solid var(--border);\n  }\n  .mobile-nav select {\n    max-width: 210px;\n    width: 65%;\n    font-size: var(--body);\n  }\n  .mobile-nav > span {\n    font-size: var(--sm);\n    white-space: nowrap;\n  }\n  main {\n    padding: 20px 16px 32px;\n  }\n  .page-heading {\n    align-items: flex-start;\n    margin-bottom: 16px;\n    flex-direction: column;\n    gap: 10px;\n  }\n  h1 {\n    font-size: var(--heading);\n  }\n  .read-status {\n    width: 100%;\n    justify-content: space-between;\n  }\n  .market-strip {\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n    gap: 12px 16px;\n    padding: 12px 0;\n    margin-bottom: 16px;\n  }\n  .market-strip .stat-value {\n    font-size: var(--body);\n  }\n  .market-strip .stat:nth-child(2),\n  .market-strip .stat:nth-child(4) {\n    display: none;\n  }\n  .market-strip .stat:nth-child(3) {\n    text-align: end;\n  }\n  .trade-grid,\n  .two-col {\n    display: flex;\n    flex-direction: column;\n    gap: 16px;\n  }\n  .trade-grid > .panel,\n  .two-col > .panel {\n    width: 100%;\n    margin-bottom: 0;\n  }\n  .trade-grid {\n    margin-bottom: 20px;\n  }\n  .swap-panel {\n    order: 0;\n  }\n  .ladder {\n    order: 1;\n  }\n  .panel {\n    padding: 16px;\n    margin-bottom: 16px;\n  }\n  .swap-panel .segmented {\n    margin: 16px 0;\n  }\n  .field-heading {\n    margin-top: 14px;\n  }\n  .quote-output {\n    min-height: 42px;\n    line-height: 1.5;\n  }\n  .slippage {\n    margin: 12px 0;\n  }\n  .swap-direction {\n    margin: 6px 0;\n  }\n  .system-message {\n    align-items: flex-start;\n    flex-direction: column;\n    gap: 4px;\n    font-size: var(--sm);\n    margin-bottom: 16px;\n  }\n  .system-message p {\n    margin: 8px 0;\n  }\n  .system-message summary {\n    min-height: 32px;\n  }\n  .review-list {\n    grid-template-columns: 100px minmax(0, 1fr);\n    gap: 10px;\n  }\n  .form-grid {\n    grid-template-columns: 1fr;\n    gap: 4px;\n  }\n  .metrics {\n    gap: 16px;\n  }\n  .compact-dl:not(:has(.dl-row)) {\n    grid-template-columns: 1fr 1fr;\n  }\n  th,\n  td {\n    padding: 12px 4px;\n  }\n  .ladder-head,\n  .ladder-row {\n    grid-template-columns: 1fr 56px 80px;\n    gap: 8px;\n    padding-inline: 6px;\n  }\n  .ladder-row > span:first-child {\n    gap: 10px;\n  }\n  .ladder-title {\n    gap: 8px;\n  }\n  .ladder-title h3 {\n    font-size: var(--sm);\n  }\n  .progress-row {\n    margin: 20px 0;\n  }\n  footer {\n    flex-direction: column;\n    align-items: flex-start;\n    gap: 10px;\n    padding: 20px 16px;\n  }\n  dialog {\n    padding: 20px;\n  }\n  .wallet-dialog {\n    margin: auto 0 0;\n    width: 100%;\n    max-width: 100%;\n    max-height: 88dvh;\n    border-radius: 4px 4px 0 0;\n  }\n  .wallet-row {\n    min-height: 52px;\n  }\n  .dl-row {\n    gap: 10px;\n  }\n  .dl-row dt,\n  .dl-row dd {\n    min-width: 0;\n    overflow-wrap: anywhere;\n  }\n}\n@media (prefers-reduced-motion: no-preference) {\n  button,\n  a {\n    transition:\n      background-color 0.12s,\n      border-color 0.12s,\n      color 0.12s;\n  }\n}\n@media (forced-colors: active) {\n  :focus-visible {\n    outline-color: Highlight;\n  }\n  .action {\n    border: 2px solid ButtonText;\n  }\n  progress {\n    forced-color-adjust: auto;\n  }\n}\n.usd-value {\n  display: block;\n  font-size: var(--sm);\n  margin: 6px 0;\n  line-height: 1.5;\n}\n.wallet-row {\n  overflow-wrap: anywhere;\n}\n.dashboard-grid {\n  display: grid;\n  grid-template-columns: minmax(0, 1fr) 360px;\n  gap: 16px;\n  align-items: start;\n}\n.dashboard-grid .swap-panel {\n  grid-column: auto;\n  grid-row: auto;\n}\n.chart-panel {\n  min-width: 0;\n  min-height: 560px;\n}\n.swap-panel {\n  min-width: 0;\n}\n.swap-panel > * + * {\n  margin-top: 12px;\n}\n.staircase {\n  width: 100%;\n  height: 300px;\n  margin: 12px 0;\n  background: #050505;\n}\n.staircase text {\n  fill: #aaa;\n  font-family: inherit;\n  font-size: 12px;\n}\n.staircase .gate-node {\n  cursor: pointer;\n}\n.staircase .gate-node:focus circle {\n  stroke: #91d7e5;\n  stroke-width: 3;\n}\n.gate-detail {\n  padding: 12px;\n  border: 1px solid #333;\n}\n.market-chart {\n  min-height: 365px;\n  position: relative;\n}\n.market-canvas {\n  height: 340px;\n  width: 100%;\n}\n.chart-readout {\n  font-size: var(--sm);\n  min-height: 25px;\n  color: var(--muted);\n  overflow-wrap: anywhere;\n}\n.chart-empty {\n  min-height: 300px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n}\n.chart-panel select {\n  width: auto;\n}\n.chart-panel .segmented button {\n  padding: 8px 10px;\n}\n.active-gate {\n  margin-top: 16px;\n  border-top: 2px solid var(--mint);\n}\n.active-gate {\n  display: grid;\n  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n  gap: 24px;\n}\n.active-gate > .two-col {\n  margin: 0;\n  display: block;\n}\n.active-gate > .two-col > .panel {\n  padding: 0;\n  border: 0;\n  background: transparent;\n}\n.active-gate > .two-col > .panel + .panel {\n  margin-top: 16px;\n}\n.home-burn-notes > summary {\n  font-size: var(--sm);\n}\n.gate-status {\n  font-size: var(--sm);\n  text-align: end;\n}\n.gate-legend {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 16px;\n  font-size: var(--sm);\n  color: var(--muted);\n}\n.gate-detail {\n  background: #050505;\n}\n.gate-selector {\n  margin-top: 12px;\n  display: grid;\n  grid-template-columns: repeat(10, minmax(0, 1fr));\n  gap: 4px;\n}\n.gate-selector button {\n  padding: 6px 0;\n  min-width: 0;\n}\n.gate-selector button[aria-pressed=\"true\"] {\n  border-color: var(--cyan);\n  color: var(--cyan);\n}\n.gate-selector button[data-state=\"Opened\"] {\n  color: var(--mint);\n}\n.gate-selector button[data-state=\"Current\"] {\n  color: var(--amber);\n}\n.chart-tabs {\n  margin-bottom: 12px;\n}\n.setup-link {\n  color: var(--amber);\n}\n.swap-context {\n  font-size: var(--sm);\n}\n.market-strip {\n  background: #050505;\n}\n@media (max-width: 1050px) {\n  .dashboard-grid {\n    grid-template-columns: minmax(0, 1fr) 320px;\n  }\n}\n@media (max-width: 800px) {\n  .dashboard-grid {\n    display: flex;\n    flex-direction: column;\n    gap: 16px;\n  }\n  .dashboard-grid > .panel {\n    width: 100%;\n    margin: 0;\n  }\n  .dashboard-grid .swap-panel {\n    order: -1;\n  }\n  .chart-panel {\n    min-height: 0;\n  }\n  .staircase {\n    min-height: 0;\n  }\n  .chart-panel .spread {\n    flex-wrap: wrap;\n  }\n  .active-gate {\n    display: block;\n  }\n  .gate-overview {\n    margin-bottom: 24px;\n  }\n  .gate-selector {\n    grid-template-columns: repeat(5, minmax(0, 1fr));\n  }\n  .gate-selector button {\n    min-height: 44px;\n  }\n}\n.home-actions {\n  display: flex;\n  justify-content: space-between;\n  flex-wrap: wrap;\n  gap: 8px 16px;\n  margin: 0 0 14px;\n  font-size: var(--sm);\n  color: var(--muted);\n}\n.home-actions a {\n  color: var(--mint);\n  min-height: 24px;\n}\n.slippage-setting {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 16px;\n}\n.slippage-setting input {\n  width: 92px;\n}\n.active-gate .notice {\n  margin: 12px 0;\n}\n.active-gate .progress-row {\n  margin: 18px 0;\n}\n@media (max-width: 760px) {\n  .site-header {\n    padding: 12px 16px;\n  }\n  main {\n    padding-top: 14px;\n  }\n  .page-heading {\n    gap: 6px;\n    margin-bottom: 10px;\n  }\n  .home-actions {\n    margin-bottom: 10px;\n  }\n  .swap-panel > * + * {\n    margin-top: 10px;\n  }\n  .swap-panel .segmented {\n    margin: 10px 0;\n  }\n  .active-gate .spread {\n    flex-wrap: wrap;\n    gap: 4px 12px;\n  }\n  .gate-status {\n    text-align: start;\n  }\n  .home-actions > span {\n    display: none;\n  }\n  .system-message {\n    gap: 0;\n    padding: 8px 12px;\n    margin-bottom: 10px;\n  }\n  .staircase {\n    height: auto;\n  }\n}\n/* Ancient-tech art system. These shared rules are the final visual layer. */\n:root {\n  --metal: #8b8364;\n  --gold-surface: #211c10;\n  --font-display: \"Cinzel\", Georgia, serif;\n  --font-mono: \"IBM Plex Mono\", Consolas, monospace;\n}\nbody { background: radial-gradient(ellipse at 50% 4%, #0b2018 0, transparent 580px), var(--bg); }\nh1, h2, .brand { font-family: var(--font-display); font-weight: 600; }\nh1, h2 { color: var(--text); letter-spacing: 0; }\nh2 { font-size: var(--heading); line-height: 1.35; }\nh3 { color: var(--amber); }\ninput, select, textarea { background: #050b08; border-radius: 4px; }\ninput, .stat-value, .door-number, .gate-door strong, .progress-row, .compact-dl dd, .address, pre { font-family: var(--font-mono); }\nbutton { background: linear-gradient(180deg, #15211d, #0a100e); border-radius: 5px; transition: background-color 150ms, border-color 150ms, color 150ms, transform 150ms; }\nbutton:active:enabled { transform: scale(.96); }\nbutton:disabled { border-color: #29372f; background: #0c1511; color: var(--muted); }\nbutton:hover:enabled { background: #18392a; border-color: var(--mint); }\n.action, .preview-action { border-color: #35e6a8; background: linear-gradient(180deg, #164d36 0, #08301f 50%, #0a2319 100%); color: var(--text); box-shadow: inset 0 0 10px #24d99526; font-weight: 600; }\n.action:hover:enabled, .preview-action:hover:enabled { color: #fff; background: #115638; border-color: #a2ffdb; }\n.action.secondary { color: var(--text); background: #0b1510; border-color: var(--border); box-shadow: none; }\n:focus-visible { outline: 2px solid #b9fff2; outline-offset: 3px; }\nmain:focus { outline-offset: -2px; }\n.site-header { max-width: 1600px; padding: 8px 28px; min-height: 70px; border-bottom: 1px solid var(--metal); gap: 24px; }\n.brand { font-size: var(--heading); gap: 10px; letter-spacing: 0; }\n.brand img { object-fit: contain; width: 38px; height: 44px; }\n.version { font: inherit; color: var(--text); margin-left: 6px; }\n.header-utilities { gap: 14px; flex-shrink: 0; }\n.chain-label { font-size: var(--sm); }\n.wallet-button { color: var(--text); min-height: 40px; border-color: #8ab7a0; background: #08110c; }\n.desktop-nav { flex: 1; flex-wrap: nowrap; align-items: center; justify-content: center; gap: 10px; padding: 0; margin: 0; border: 0; }\n.desktop-nav a, .desktop-nav summary { font-size: var(--body); padding: 12px 8px; color: #d1dbd5; white-space: nowrap; }\n.desktop-nav a[aria-current=page] { color: var(--mint); border-bottom: 2px solid var(--mint); }\n.more-nav { position: relative; }\n.more-nav summary { min-height: 44px; }\n.more-nav > div { position: absolute; right: 0; top: 100%; width: 220px; z-index: 8; padding: 8px; background: #09140e; border: 1px solid var(--metal); box-shadow: 0 16px 32px #000c; }\n.more-nav a { display: block; }\nmain { max-width: 1600px; padding: 20px 28px 40px; }\n.page-heading { align-items: center; margin: 0 0 20px; }\n.page-heading h1 { font-size: var(--amount); line-height: 1.3; }\n[data-screen=trade] > .page-heading { justify-content: center; position: relative; padding-top: 6px; }\n[data-screen=trade] > .page-heading h1 { text-align: center; padding-inline: 180px; color: #eee9cf; }\n[data-screen=trade] > .page-heading .read-status { position: absolute; right: 0; gap: 8px; }\n.read-status .small { padding: 5px 8px; }\n.system-message { margin-bottom: 12px; background: #14180e; }\n.panel { position: relative; border: 1px solid var(--metal); border-radius: 10px 3px 10px 3px; background: linear-gradient(135deg, #0b1510, #060b09 70%); padding: 24px; box-shadow: inset 0 0 0 3px #050907, inset 0 0 0 4px #343a2e; }\n.panel > h2 { margin-bottom: 20px; }\n.panel::after, .art-panel::after { content: \"\"; width: 22px; height: 2px; position: absolute; top: -1px; left: 15px; background: var(--amber); pointer-events: none; }\n.two-col { gap: 20px; align-items: start; }\n.metrics { gap: 20px; padding-block: 12px; border-block: 1px solid #2a3b30; }\n.stat-value { font-size: var(--input); }\n.notice { background: #15170d; border-color: var(--amber); }\n.notice.error { background: #211010; border-color: var(--error); }\n.table-wrap { max-width: 100%; overflow-x: auto; }\n.table-wrap table { min-width: 520px; }\n.table-wrap:focus-visible { outline-offset: -2px; }\nth,td { border-color: #293c31; }\nprogress { height: 7px; background: #172a20; border: 1px solid #385347; border-radius: 5px; }\nprogress::-webkit-progress-bar { background: #14271d; border-radius: 5px; }\nprogress::-webkit-progress-value { background: linear-gradient(90deg,#0f9f74,#5ef9b3); border-radius: 5px; }\nprogress::-moz-progress-bar { background: #39d99e; border-radius: 5px; }\n.gate-hall { position: relative; margin-bottom: 22px; }\n.gate-hall-heading { margin-bottom: 0; align-items: center; }\n.gate-hall-heading h2 { font-size: var(--sub); }\n.gate-hall-heading .contribute-link { display: inline-flex; justify-content: center; align-items: center; color: #e2fbed; font-size: var(--body); min-height: 40px; border: 1px solid #4b8d6d; padding: 7px 16px; text-decoration: none; background: #0b291b; border-radius: 3px; }\n.door-grid { display: grid; grid-template-columns: repeat(10,minmax(0,1fr)); gap: 0; padding: 10px 0 4px; border-bottom: 1px solid #5b6146; background: radial-gradient(ellipse at 50% 50%, #0b362127, transparent 80%); }\n.gate-door { isolation: isolate; position: relative; display: flex; flex-direction: column; align-items: center; min-width: 0; padding: 0 0 8px; color: var(--muted); background: transparent; border: 1px solid transparent; border-radius: 6px; }\n.gate-door:hover:enabled { background: #123024; border-color: #4b755c; }\n.gate-door[aria-pressed=true] { border-color: #88957a; background: #12241880; }\n.door-number { position: absolute; top: 9%; z-index: 2; border: 1px solid #8e8460; color: #eee7bd; background: #080e0bd9; border-radius: 50%; width: 29px; height: 29px; display: grid; place-items: center; font-size: var(--body); }\n.door-art { display: block; width: 100%; aspect-ratio: 1 / 1.18; pointer-events: none; }\n.door-art img { width: 100%; height: 100%; object-fit: contain; opacity: .65; filter: saturate(.65); mix-blend-mode: screen; }\n.gate-door strong { font-size: var(--input); line-height: 1.3; margin-top: -14px; z-index: 2; padding: 3px 12px; min-width: 68%; background: #07100e; border: 1px solid #5a6650; border-radius: 2px; color: var(--text); }\n.door-state { font-size: var(--sm); line-height: 1.6; margin-top: 3px; }\n.gate-door[data-state=Current] { color: var(--amber); }\n.gate-door[data-state=Current] .door-art img { opacity: 1; filter: none; }\n.gate-door[data-state=Current] strong { border-color: #d4ad5f; background: #241d0b; color: #fff0bd; }\n.gate-door[data-state=Open] { color: var(--mint); }\n.gate-door[data-state=Open] .door-art img { opacity: 1; filter: none; }\n.gate-door[data-state=Open] strong { border-color: #50aa82; color: var(--mint); }\n.gate-door[data-state=Unread] .door-art img { opacity: .42; filter: saturate(.3); }\n.gate-disclosure { padding: 14px 18px; border: 1px solid var(--metal); border-radius: 3px 10px 3px 10px; background: linear-gradient(90deg,#18201270,#070c09); margin-top: 10px; }\n.gate-disclosure h3 { font-family: var(--font-display); font-size: var(--sub); color: var(--amber); margin: 0; }\n.gate-disclosure > .spread { align-items: center; }\n.gate-disclosure > .spread > span { font-size: var(--sm); }\n.gate-three { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 26px; }\n.gate-three .progress-row { font-size: var(--sm); margin: 12px 0 0; min-width: 0; }\n.gate-three .spread { font-size: var(--sm); gap: 6px; flex-wrap: wrap; }\n.gate-three .spread > span { overflow-wrap: anywhere; }\n.gate-three .spread:last-child { margin-top: 4px; }\n.gate-three progress { margin: 6px 0; }\n.gate-observation { display: flex; gap: 20px; flex-wrap: wrap; border-top: 1px solid #2f3c2c; padding-top: 10px; margin-top: 14px; font-size: var(--sm); color: var(--muted); }\n.gate-observation strong { margin-left: 8px; font-weight: 400; color: var(--text); }\n.observation-note { margin-block: 8px 0; }\n.gate-disclosure > .helper:last-child { margin-block: 6px 0; }\n.gate-opening { padding: 10px 14px; color: var(--mint); border-left: 2px solid var(--mint); }\n.dashboard-grid { display: grid; grid-template-columns: minmax(0,1fr) minmax(340px, .45fr); gap: 18px; align-items: start; margin-bottom: 18px; }\n.dashboard-grid > .panel { margin-bottom: 0; }\n.dashboard-grid > .swap-panel { position: sticky; top: 18px; grid-column: auto; grid-row: auto; }\n.chart-panel { min-height: 680px; padding: 20px; }\n.market-heading { margin-bottom: 12px; flex-wrap: wrap; }\n.market-heading h2 { margin: 0; }\n.market-heading strong { font-family: var(--font-mono); font-size: var(--sm); color: var(--cyan); font-weight: 400; }\n.chart-panel > .spread { flex-wrap: wrap; }\n.chart-panel .segmented { margin: 10px 0; }\n.chart-panel .segmented button { padding-inline: 12px; }\n.chart-panel > p { font-size: var(--sm); color: var(--muted); }\n.market-canvas { height: 400px; }\n.chart-empty { min-height: 360px; margin-block: 10px; border: 1px solid #20362b; background: linear-gradient(#13362924 1px, transparent 1px),linear-gradient(90deg,#13362924 1px,transparent 1px); background-size: 56px 48px; }\n.chart-empty > strong { font-family: var(--font-display); font-size: var(--sub); max-width: 28ch; color: var(--amber); }\n.chart-empty p { max-width: 42ch; padding-inline: 18px; color: var(--muted); }\n.segmented { padding: 3px; border-radius: 5px; border: 1px solid #344d3d; background: #050b08; gap: 4px; }\n.segmented button { border-radius: 3px; padding: 7px; background: #0e1913; min-height: 36px; }\n.segmented button[aria-pressed=true] { box-shadow: inset 0 0 10px #28eb9833; border: 1px solid #35c58d; color: #ebfff6; background: #0c3222; }\n.swap-panel { padding: 20px; }\n.swap-panel > * + * { margin-top: 10px; }\n.swap-panel > .spread { flex-wrap: wrap; gap: 4px; }\n.swap-panel h2 { font-size: var(--heading); margin-bottom: 0; }\n.swap-panel > .spread > span { font-size: var(--sm); }\n.swap-panel .segmented { margin: 12px 0; }\n.swap-input-row > input { font-size: var(--heading); padding: 12px; }\n.swap-panel > label { font-size: var(--body); }\n.payment-choice { display: flex; align-items: center; gap: 8px; }\n.payment-choice > span { margin-right: auto; color: var(--muted); }\n.payment-choice button { min-width: 70px; min-height: 36px; padding-block: 6px; }\n.payment-choice button[aria-pressed=true] { color: #dcfff0; border-color: #40cb94; background: #0f3223; }\n.shortcuts { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 8px; }\n.shortcuts button { padding: 5px; font-size: var(--sm); min-height: 32px; }\n.receive-line { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0; border-block: 1px solid #2d3f32; }\n.receive-line strong { font-family: var(--font-mono); font-size: var(--sub); overflow-wrap: anywhere; }\n.slippage-setting { display: flex; align-items: center; justify-content: space-between; gap: 14px; }\n.slippage-setting input { width: 84px; min-height: 36px; padding-block: 5px; }\n.visible-route { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px; font-size: var(--sm); border-block: 1px solid #2d3f32; padding-block: 9px; }\n.visible-route span { color: var(--muted); }\n.visible-route strong { font-weight: 400; color: var(--cyan); font-family: var(--font-mono); }\n.swap-context { font-size: var(--sm); }\n.swap-context summary { padding-block: 6px; min-height: 32px; }\n.swap-context p { color: var(--muted); }\n.art-panels { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin: 0 0 24px; }\n.art-panel { position: relative; min-width: 0; display: grid; grid-template-columns: 44% 1fr; align-items: center; min-height: 230px; padding: 14px; border: 1px solid var(--metal); background: #060e0a; border-radius: 10px 3px 10px 3px; box-shadow: inset 0 0 0 3px #050907, inset 0 0 0 4px #343a2e; }\n.art-panel-content { z-index: 1; min-width: 0; padding: 8px 10px; }\n.art-panel h2 { font-size: var(--heading); text-align: center; padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid #6d6849; }\n.art-panel .compact-dl { gap: 8px; font-size: var(--sm); }\n.art-panel .compact-dl dt { color: #ccd6ce; }\n.art-panel .compact-dl dd { font-size: var(--sm); }\n.art-panel button { width: 100%; margin-top: 12px; }\n.art-caption { display: block; font-size: var(--sm); text-align: center; color: var(--muted); margin-top: 8px; }\n.vault-art-panel .art-panel-content > p { text-align: center; color: #d4cfba; font-size: var(--body); }\n.vault-lock { color: #f5d893 !important; padding-block: 8px; border-block: 1px solid #685a31; }\n.energy-scene { position: relative; isolation: isolate; height: 270px; display: grid; place-items: center; color: var(--mint); margin: 0; }\n.energy-art { max-width: 100%; width: 330px; height: 100%; object-fit: contain; mix-blend-mode: screen; pointer-events: none; }\n.energy-scene.gold { color: var(--amber); }\n.art-panel .energy-scene { height: 230px; }\n.core-light { position: absolute; inset: 25%; border-radius: 50%; box-shadow: 0 0 32px #23e99e1c; pointer-events: none; }\n.flow-particles { opacity: 0; position: absolute; left: 8%; top: 48%; display: flex; gap: 20px; pointer-events: none; }\n.flow-particles i { width: 6px; height: 6px; background: #aaffda; box-shadow: 0 0 7px #62ffb6; transform: rotate(45deg); }\n.energy-scene.confirmed .flow-particles { animation: token-flow 1.8s ease-out both; }\n.energy-scene.confirmed .core-light { animation: core-flash 1.8s ease-out both; }\n.burn-confirmation { display: flex; flex-direction: column; gap: 4px; color: var(--mint); border-left: 2px solid var(--mint); padding: 10px 14px; }\n.active-gate { display: block; padding: 0; border: 0; margin-top: 20px; scroll-margin-top: 16px; }\n.active-gate > .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 0; }\n.active-gate > .two-col > .panel { padding: 24px; border: 1px solid var(--metal); background: #08110c; }\n.home-burn-notes { padding: 24px; min-width: 0; }\n.home-burn-notes summary { color: var(--text); font-family: var(--font-display); font-size: var(--sub); }\n[data-screen=golden-vault] .two-col > .panel:first-child > h2 { color: var(--amber); }\n[data-screen=migrate] .two-col > .panel:first-child > h2, [data-screen=my-rights] .two-col > .panel:first-child > h2, [data-screen=weekly-imd-rewards] .two-col > .panel:first-child > h2 { padding-right: 48px; }\n[data-screen=migrate] .two-col > .panel:first-child::before, [data-screen=my-rights] .two-col > .panel:first-child::before, [data-screen=weekly-imd-rewards] .two-col > .panel:first-child::before { content: \"\u25c7\"; color: var(--amber); font-size: var(--amount); position: absolute; right: 24px; top: 16px; }\nfooter { max-width: 1600px; padding: 24px 28px; }\ndialog { border-color: #899178; border-radius: 8px; box-shadow: 0 20px 80px #000b; }\n@keyframes token-flow { 0% { opacity: 0; transform: translateX(-20px); } 20% { opacity: 1; } 75% { opacity: 1; } 100% { opacity: 0; transform: translateX(115px); } }\n@keyframes core-flash { 30% { opacity: 1; box-shadow: 0 0 60px #48fbbd77; } 100% { opacity: .4; } }\n@media (min-width: 761px) and (max-width: 1250px) {\n  .site-header { flex-wrap: wrap; gap: 8px 24px; padding-inline: 24px; }\n  .desktop-nav { flex-basis: 100%; order: 3; justify-content: flex-start; gap: 18px; }\n  .desktop-nav a { padding-block: 6px; }\n  .brand { margin-right: auto; }\n  [data-screen=trade] > .page-heading { justify-content: space-between; }\n  [data-screen=trade] > .page-heading h1 { padding: 0; font-size: var(--heading); }\n  [data-screen=trade] > .page-heading .read-status { position: static; }\n  .art-panel { grid-template-columns: 38% 1fr; }\n  .art-panel h2 { font-size: var(--sub); }\n}\n@media (max-width: 1050px) {\n  .dashboard-grid { grid-template-columns: minmax(0,1fr) 330px; gap: 16px; }\n  .gate-three { gap: 16px; }\n  .gate-three .spread { display: block; }\n  .gate-three .spread > span { display: block; }\n  .door-state { font-size: var(--sm); }\n  .gate-door strong { padding-inline: 4px; }\n}\n@media (max-width: 800px) {\n  .dashboard-grid { display: flex; flex-direction: column; }\n  .dashboard-grid > .panel { width: 100%; }\n  .dashboard-grid > .swap-panel { position: static; order: 0; }\n  .dashboard-grid > .chart-panel { order: 1; min-height: 0; }\n  .art-panels { grid-template-columns: 1fr; }\n}\n@media (max-width: 760px) {\n  .site-header { padding: 10px 16px; min-height: 64px; gap: 8px; }\n  .brand { font-size: var(--sub); gap: 6px; }\n  .brand img { width: 29px; height: 36px; }\n  .version { font-size: inherit; }\n  .chain-label, .desktop-nav { display: none; }\n  .wallet-button { font-size: var(--sm); min-height: 40px; padding: 8px 10px; }\n  .mobile-nav { padding: 0 16px 10px; gap: 10px; border-color: #4d5944; }\n  .mobile-nav select { font-size: var(--input); min-height: 40px; padding: 6px 10px; }\n  main { padding: 14px 14px 30px; }\n  .page-heading { align-items: flex-start; gap: 8px; margin: 0 0 14px; }\n  .page-heading h1, [data-screen=trade] > .page-heading h1 { font-size: var(--heading); text-align: left; padding: 0; }\n  [data-screen=trade] > .page-heading { padding: 0; }\n  [data-screen=trade] > .page-heading .read-status { position: static; }\n  .read-status { justify-content: space-between; width: 100%; }\n  .gate-hall { margin-bottom: 18px; }\n  .gate-hall-heading { flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }\n  .gate-hall-heading h2 { font-size: var(--sub); }\n  .gate-hall-heading .contribute-link { font-size: var(--sm); padding: 6px 10px; min-height: 36px; }\n  .door-grid { grid-template-columns: repeat(5,minmax(0,1fr)); gap: 4px 2px; padding-top: 0; }\n  .gate-door { padding: 0 0 5px; }\n  .door-art { aspect-ratio: 1 / 1.12; }\n  .door-number { top: 3%; font-size: var(--sm); height: 23px; width: 23px; }\n  .gate-door strong { font-size: var(--sm); margin-top: -8px; padding: 1px 4px; }\n  .door-state { font-size: var(--sm); margin-top: 2px; }\n  .gate-disclosure { padding: 12px; margin-top: 8px; }\n  .gate-disclosure > .spread { flex-wrap: wrap; gap: 3px; }\n  .gate-disclosure h3 { font-size: var(--input); }\n  .gate-three { grid-template-columns: 1fr; gap: 0; }\n  .gate-three .progress-row { margin-top: 10px; }\n  .gate-three .spread { display: flex; justify-content: space-between; }\n  .gate-three .spread > span { display: inline; }\n  .gate-three .spread:last-child > span:last-child { display: none; }\n  .gate-observation { display: block; margin-top: 10px; padding-top: 8px; }\n  .gate-observation > span { display: flex; justify-content: space-between; gap: 6px; margin-block: 4px; }\n  .gate-observation strong { margin-left: 0; }\n  .observation-note { font-size: var(--sm); }\n  .panel { padding: 18px; }\n  .panel > h2, .swap-panel h2 { font-size: var(--heading); }\n  .two-col { gap: 16px; }\n  .two-col > * { width: 100%; }\n  .swap-panel { padding: 18px; }\n  .swap-panel > * + * { margin-top: 10px; }\n  .shortcuts button { min-height: 40px; }\n  .payment-choice button, .segmented button { min-height: 40px; }\n  .dashboard-grid { gap: 16px; margin-bottom: 16px; }\n  .chart-panel { padding: 16px; }\n  .market-canvas { height: 340px; }\n  .chart-empty { min-height: 300px; }\n  .chart-panel .segmented button { padding-inline: 10px; }\n  .art-panels { gap: 16px; margin-bottom: 20px; }\n  .art-panel { grid-template-columns: 40% 1fr; padding: 10px; min-height: 230px; }\n  .art-panel-content { padding: 6px; }\n  .art-panel h2 { font-size: var(--sub); }\n  .art-panel .energy-scene { height: 210px; }\n  .art-panel .compact-dl { display: block; }\n  .art-panel .compact-dl dd { text-align: start; margin-bottom: 5px; }\n  .art-panel .art-caption { text-align: start; }\n  .art-panel button { font-size: var(--sm); padding: 8px; }\n  .art-panel .energy-art { width: 100%; }\n  .art-panel-content p { font-size: var(--sm); }\n  .active-gate > .two-col { display: flex; flex-direction: column; }\n  .active-gate > .two-col > .panel { padding: 18px; }\n  .home-burn-notes { padding: 12px 0; }\n  .energy-scene { height: 230px; }\n  footer { padding: 20px 16px; }\n}\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }\n  .energy-scene.confirmed .flow-particles { opacity: 0; }\n}\n.observation-details { margin-top: 8px; }\n.observation-details summary, .chart-provenance summary { font-size: var(--sm); min-height: 32px; padding: 5px 0; }\n.observation-details .gate-observation { margin-top: 0; }\n.chart-panel { min-height: 570px; }\n.market-canvas { height: 330px; }\n.chart-empty { min-height: 285px; }\n.chart-freshness { margin-block: 8px 0; }\n.energy-scene { overflow: hidden; }\n.energy-art { min-height: 0; align-self: stretch; }\n.art-panel .energy-scene { overflow: visible; }\n@media (max-width:760px) { .market-canvas { height:320px; } .chart-empty { min-height:270px; } }\nstrong, b { font-weight: 600; }\n.gate-hall > .sr-only { margin: 0; }\n.gate-hall-heading h3 { margin: 0; }\n.gate-hall-heading > .muted { margin-left: auto; }\n.gate-disclosure .contribute-link { margin-left: 10px; }\n@media (max-width:760px) {\n  .gate-hall-heading { display: grid; grid-template-columns: 1fr auto; gap: 5px; }\n  .gate-hall-heading > .muted { grid-row: 2; grid-column: 1 / -1; margin-left: 0; }\n  .gate-hall-heading .contribute-link { margin-left: 0; }\n}\n@media (prefers-reduced-motion: no-preference) {\n  .energy-scene:not(.gold):not(.confirmed) .core-light { animation: core-breathe 8s ease-in-out infinite alternate; }\n}\n@keyframes core-breathe { from { opacity: .35; } to { opacity: .8; } }\n.energy-scene { background: #08110c; }\n.art-panel .energy-scene { background: #060e0a; }\n.gate-door { background-color: #07110c; }\nlabel, dt, .stat > span, .chain-label, .read-status, .door-state, .field-heading, .payment-choice > span, .receive-line > span { font-family: var(--font-mono); }\n@media (max-width:760px) { .wallet-button, .mobile-nav select, .read-status .small { min-height:44px; } }\n/* Lightweight Charts owns its internal table layout; application tables must not pad it. */\n.market-canvas table { width:auto; table-layout:auto; border-collapse:separate; border-spacing:0; margin:0; }\n.market-canvas td, .market-canvas th { padding:0; border:0; overflow-wrap:normal; }\n\n/* Keep the approved desktop dashboard compact. Text and expanded disclosures\n   remain in normal flow, so error states and enlarged text can grow the panels. */\n.swap-input-row > input { margin-top: 10px; }\n.market-topbar > .spread { flex-wrap: wrap; }\n.market-footer { margin-top: 10px; }\n@media (min-width: 1251px) {\n  [data-screen=trade] { padding-top: 4px; }\n  [data-screen=trade] > .page-heading { padding-top: 0; margin-bottom: 4px; }\n  [data-screen=trade] .gate-hall { margin-bottom: 12px; }\n  [data-screen=trade] .door-grid { padding-block: 0; }\n  [data-screen=trade] .door-art { aspect-ratio: auto; height: 112px; }\n  [data-screen=trade] .gate-door { padding-bottom: 0; }\n  [data-screen=trade] .gate-door strong { margin-top: -14px; }\n  [data-screen=trade] .door-state { line-height: 1.4; margin-top: 0; }\n  [data-screen=trade] .door-number { top: 4%; }\n  [data-screen=trade] .gate-disclosure {\n    display: grid;\n    grid-template-columns: 180px minmax(0, 1fr) 180px;\n    gap: 0 18px;\n    padding: 5px 14px;\n    margin-top: 8px;\n    align-items: center;\n  }\n  [data-screen=trade] .gate-hall-heading { display: contents; }\n  [data-screen=trade] .gate-hall-heading h3 { grid-column: 1; grid-row: 1; }\n  [data-screen=trade] .gate-hall-heading > .muted { grid-column: 1; grid-row: 2; margin: 0; line-height: 1.4; }\n  [data-screen=trade] .gate-hall-heading .contribute-link { grid-column: 3; grid-row: 1; margin: 0; min-height: 32px; padding: 5px 8px; }\n  [data-screen=trade] .gate-three { grid-column: 2; grid-row: 1 / 3; gap: 18px; }\n  [data-screen=trade] .gate-three .progress-row { margin: 0; line-height: 1.4; }\n  [data-screen=trade] .gate-three .spread { gap: 2px 6px; }\n  [data-screen=trade] .gate-three .spread:last-child { margin-top: 0; }\n  [data-screen=trade] .gate-three .spread:last-child > span:last-child { display: none; }\n  [data-screen=trade] .gate-three progress { display: block; height: 5px; margin: 3px 0; }\n  [data-screen=trade] .observation-details { grid-column: 3; grid-row: 2; margin: 0; }\n  [data-screen=trade] .observation-details summary { min-height: 24px; padding: 2px 0; }\n  [data-screen=trade] .observation-details[open] { grid-column: 1 / -1; grid-row: auto; margin-top: 8px; }\n  [data-screen=trade] .gate-disclosure > .helper,\n  [data-screen=trade] .gate-disclosure > .history-error { grid-column: 1 / -1; }\n\n  .dashboard-grid { gap: 14px; margin-bottom: 14px; align-items: stretch; }\n  .dashboard-grid > .panel { padding: 10px 16px; min-height: 0; }\n  .swap-panel > * + * { margin-top: 3px; }\n  .swap-topbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; }\n  .swap-topbar h2 { font-size: var(--sub); white-space: nowrap; }\n  .swap-topbar .segmented { margin: 0; }\n  .swap-panel .segmented button { padding: 4px 8px; min-height: 28px; font-size: var(--sm); white-space: nowrap; }\n  .payment-choice button { min-height: 28px; padding-block: 3px; }\n  .swap-input-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; gap: 10px; }\n  .swap-input-row > .spread { display: block; font-size: var(--sm); line-height: 1.4; }\n  .swap-input-row > .spread > span { display: block; }\n  .swap-input-row > input { margin: 0; padding: 4px 10px; min-height: 36px; font-size: var(--input); }\n  .shortcuts { gap: 6px; }\n  .shortcuts button { min-height: 28px; padding: 3px; }\n  .receive-line { padding: 3px 0; font-size: var(--sm); }\n  .receive-line strong { font-size: var(--input); }\n  .swap-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n  .swap-settings-row .visible-route { display: block; border: 0; padding: 0; margin: 0; line-height: 1.4; }\n  .visible-route > * { display: block; }\n  .slippage-setting { gap: 8px; font-size: var(--sm); }\n  .slippage-setting input { width: 64px; min-height: 32px; padding: 4px 6px; }\n  .swap-panel > .action { min-height: 36px; padding: 6px 10px; }\n  .swap-panel > .helper { line-height: 1.4; margin-bottom: 0; }\n  .swap-context summary { min-height: 24px; padding-block: 2px; }\n  .swap-context .helper { margin-top: 8px; }\n\n  .market-topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; }\n  .market-heading { display: block; margin: 0; }\n  .market-heading h2 { font-size: var(--sub); }\n  .market-topbar > .spread { flex-wrap: nowrap; gap: 12px; }\n  .chart-panel .segmented { margin: 0; }\n  .chart-panel .segmented button { min-height: 28px; padding: 4px 10px; }\n  .chart-panel select { min-height: 32px; padding: 4px 8px; font-size: var(--sm); }\n  .chart-panel { display: flex; flex-direction: column; }\n  .market-chart { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }\n  .market-canvas { flex: 1 1 190px; height: auto; min-height: 190px; contain: size; }\n  .chart-empty { min-height: 180px; margin: 6px 0; }\n  .chart-empty > strong { max-width: 48ch; }\n  .chart-empty p { max-width: 65ch; margin-bottom: 0; }\n  .market-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; margin-top: 6px; }\n  .market-footer > button { min-height: 28px; padding: 4px 10px; font-size: var(--sm); }\n  .market-footer .chart-freshness { margin: 0; flex: 1; }\n  .market-footer .chart-provenance { flex-basis: 100%; }\n  .chart-provenance summary { min-height: 24px; padding-block: 2px; }\n  .art-panels { gap: 14px; }\n  .art-panel { padding: 10px; min-height: 220px; }\n  .art-panel .energy-scene { height: 205px; }\n  .art-panel-content { padding-block: 4px; }\n  .art-panel h2 { padding-bottom: 8px; margin-bottom: 8px; }\n  .art-panel .compact-dl { gap: 6px; margin-block: 8px; }\n  .art-panel button { margin-top: 8px; }\n  .active-gate > .two-col { grid-template-columns: minmax(0, 1fr); }\n  .active-gate > .two-col > .panel {\n    display: grid;\n    grid-template-columns: 300px minmax(0, 1fr);\n    column-gap: 24px;\n    align-content: start;\n  }\n  .active-gate > .two-col > .panel > * { grid-column: 2; min-width: 0; }\n  .active-gate > .two-col > .panel > h2 { grid-column: 1 / -1; }\n  .active-gate > .two-col > .panel > .energy-scene {\n    grid-column: 1;\n    grid-row: 2 / span 12;\n    align-self: start;\n  }\n}\n"
    }
  ],
  "validation": {
    "nodeTestFilesPassed": 3,
    "browserCheckGroupsPassed": 9,
    "routes": 9,
    "widths": [
      390,
      1440
    ],
    "chartHeightPixels": {
      "before": 190,
      "quote": 300.921875,
      "cleared": 190
    },
    "browserMode": "Synthetic RPC and wallet; no live signing or transactions",
    "typecheck": "Not run separately; offline production build passed"
  },
  "publishTarget": "https://unlock-v2.imd.site/",
  "previewOnly": true
}