$sloprun.dev

Tone Translator

demo sandbox: allow-scripts · csp: default-src 'none'

sandboxed and isolated in your browser · never enter a real password in a demo

▶ 6 ran · ✓ 0 worked
i ran it — no login needed:
share: preview embed ↗
post it anywhere:
email the card:

0 reported it broke · the ▶ count is distinct visitors who ran it, counted once each, anonymously

A too-angry work email loads pre-scored at heat 100, with all 14 hot phrases — shouting, exclamation pile-ups, absolutist and accusatory wording — flagged as chips you can click to soften in place while the number falls. Pick your target tones (diplomatic, concise, legal, friendly, pirate — combine as many as you like) plus an optional target language, and Claude rewrites the whole thing while keeping every fact, name and date intact. Try it in this order: press REWRITE on the default diplomatic, switch the language to Japanese and run it again, then hit cool all 14 and watch heat 100 drop to 0 without a single word of AI. With no key connected the sample answers from recorded Claude transcripts (diplomatic, concise, legal, friendly and pirate on their own, plus diplomatic in Spanish or Japanese) and anything else falls back to a deterministic rules pass — both labelled honestly, so you always know which engine answered.

Embed this demo — put a runnable demo in your blog post

Paste this where you write. It renders a live, runnable demo inline anywhere raw HTML / iframes are allowed — Ghost, WordPress, Notion, Discourse, your own site.

<iframe src="https://sloprun.dev/embed/tone-translator" width="100%" height="640" loading="lazy" allow="" style="border:1px solid #E3E2DC;border-radius:10px" title="Tone Translator — a runnable demo on sloprun.dev"></iframe>
preview ↗

On Medium and dev.to the plain link becomes a rich preview card that links back here — they don't run third-party iframes, so paste the URL there and the reader clicks through to run it. https://sloprun.dev/p/tone-translator

Source

the code you see is the code that just ran raw ↗
Source — the code you see is the code that just ran 1018 lines 46.8 KB index.html
demos/tone-translator/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>Tone Translator</title>
<link rel="icon" href="data:,">
<style>
/* sloprun design tokens — inline this block into every demo (self-contained rule).
   Identity: instrument-panel. Machine facts in mono; human words in sans.
   Green is EARNED: only for "it ran / it worked" states, never decoration. */
:root {
  --bg: #F6F6F3;          /* neutral paper, not cream */
  --surface: #FFFFFF;
  --ink: #1A1D21;
  --muted: #5A6068;
  --line: #E3E2DC;
  --accent: #C05600;      /* phosphor amber, darkened for light bg */
  --accent-soft: #FFF3E6;
  --run: #1A7F37;         /* earned green */
  --run-soft: #E7F4EA;
  --danger: #C93C37;
  --radius: 10px;
  --font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #14161A; --surface: #1C1F26; --ink: #E8E6E1; --muted: #9AA0A8;
    --line: #2A2E36; --accent: #FFB454; --accent-soft: #2A2118;
    --run: #3FB950; --run-soft: #16281B; --danger: #F47067;
  }
}
:root[data-theme="dark"] {
  --bg: #14161A; --surface: #1C1F26; --ink: #E8E6E1; --muted: #9AA0A8;
  --line: #2A2E36; --accent: #FFB454; --accent-soft: #2A2118;
  --run: #3FB950; --run-soft: #16281B; --danger: #F47067;
}
body { background: var(--bg); color: var(--ink); font-family: var(--font-sans); }

/* Keep native controls (select popup, password field, scrollbars, caret) in step with
   the theme the platform forces via data-theme, not just the OS preference. */
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/* ---------------- base ---------------- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; padding: 22px 16px 36px; line-height: 1.55; overflow-x: hidden; }
.wrap { max-width: 1040px; margin: 0 auto; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden;
      clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ---------------- header ---------------- */
.eyebrow {
  display: flex; align-items: center; gap: 8px; margin: 0 0 7px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .15em;
  text-transform: uppercase; color: var(--accent);
}
.led { width: 7px; height: 7px; border-radius: 50%; flex: none;
       background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
h1 { margin: 0 0 8px; font-size: clamp(22px, 5vw, 30px); line-height: 1.12; letter-spacing: -.022em; }
.lede { margin: 0 0 20px; max-width: 62ch; color: var(--muted); font-size: 15px; }
.lede b { color: var(--ink); font-weight: 600; }

/* ---------------- panels ---------------- */
.panel { background: var(--surface); border: 1px solid var(--line);
         border-radius: var(--radius); margin-bottom: 14px; }
.phead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
         padding: 9px 12px; border-bottom: 1px solid var(--line); min-height: 49px; }
.ptitle { margin: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 600;
          letter-spacing: .13em; text-transform: uppercase; color: var(--muted); }
.spacer { flex: 1 1 auto; }
.pbody { padding: 13px 12px; }

/* ---------------- buttons ---------------- */
.btn { font-family: var(--font-mono); font-size: 12px; line-height: 1; cursor: pointer;
       padding: 8px 11px; border: 1px solid var(--line); border-radius: 7px;
       background: transparent; white-space: nowrap;
       transition: background .13s, border-color .13s, color .13s; }
.btn:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.btn[disabled] { opacity: .48; cursor: not-allowed; }
.btn[disabled]:hover { background: transparent; border-color: var(--line); color: inherit; }
.btn.done { color: var(--run); border-color: var(--run); background: var(--run-soft); }
.btn-primary { padding: 10px 18px; font-size: 12.5px; font-weight: 700; letter-spacing: .06em;
               background: var(--accent); border-color: var(--accent); color: var(--surface); }
.btn-primary:hover { background: var(--accent); border-color: var(--accent);
                     color: var(--surface); filter: brightness(1.09); }
.btn-primary[disabled]:hover { filter: none; }

/* ---------------- meter ---------------- */
.meter { display: flex; align-items: center; gap: 9px; color: var(--danger); }
.meter[data-band="cool"] { color: var(--run); }
.meter[data-band="mild"], .meter[data-band="warm"] { color: var(--accent); }
.meter .num { font-family: var(--font-mono); font-size: 19px; font-weight: 700;
              line-height: 1; font-variant-numeric: tabular-nums; min-width: 2.4ch; text-align: right; }
.meter .lab { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .12em;
              text-transform: uppercase; }
.meter .track { width: 96px; height: 7px; border-radius: 999px; overflow: hidden;
                background: var(--bg); border: 1px solid var(--line); }
.meter .fill { display: block; height: 100%; background: currentColor; border-radius: 999px;
               transition: width .35s ease; }

/* ---------------- source ---------------- */
textarea { display: block; width: 100%; min-height: 200px; resize: vertical; padding: 12px;
           font-family: var(--font-mono); font-size: 14px; line-height: 1.62;
           color: var(--ink); background: var(--bg);
           border: 1px solid var(--line); border-radius: 8px; }
textarea:focus-visible { border-color: var(--accent); }
@media (max-width: 640px) { textarea { font-size: 16px; } }

.metarow { display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap;
           margin-top: 10px; font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); }
.metarow b { color: var(--ink); font-weight: 600; }

.flags { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.flags:focus-visible { outline-offset: 4px; }
.flag { display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
        padding: 6px 9px; border-radius: 7px; font-size: 12.5px;
        border: 1px solid var(--line); background: var(--bg);
        transition: background .13s, border-color .13s, color .13s; max-width: 100%; }
.flag:hover { border-color: var(--danger); color: var(--danger); background: var(--surface); }
.flag .ph { font-family: var(--font-mono); overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; max-width: 22ch; }
.flag .w { font-family: var(--font-mono); font-size: 10.5px; color: var(--danger); flex: none; }
.flag:hover .w { color: var(--danger); }
.cooled { display: inline-flex; align-items: center; gap: 7px; padding: 6px 10px;
          border-radius: 7px; font-family: var(--font-mono); font-size: 12px;
          color: var(--run); background: var(--run-soft); border: 1px solid var(--run); }

/* ---------------- tone chips ---------------- */
.chips { display: flex; flex-wrap: wrap; gap: 7px; }
.chip { display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
        padding: 8px 12px; border-radius: 999px; font-family: var(--font-mono); font-size: 12px;
        line-height: 1; border: 1px solid var(--line); background: var(--bg);
        transition: background .13s, border-color .13s, color .13s; }
.chip .dot { width: 6px; height: 6px; border-radius: 50%; flex: none;
             border: 1px solid var(--muted); transition: background .13s, border-color .13s; }
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent);
                             color: var(--accent); font-weight: 600; }
.chip[aria-pressed="true"] .dot { background: var(--accent); border-color: var(--accent); }

.fieldlab { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .12em;
                 text-transform: uppercase; color: var(--muted); display: block; margin: 0 0 7px; }
select { padding: 8px 10px; border-radius: 7px; border: 1px solid var(--line);
         background: var(--bg); font-family: var(--font-mono); font-size: 12px; cursor: pointer; }
.row { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.runrow { display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
          margin-top: 14px; padding-top: 13px; border-top: 1px dashed var(--line); }

/* ---------------- status ---------------- */
.status { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted);
          overflow-wrap: anywhere; }
.status b { font-weight: 600; color: var(--ink); }
.status .ok { color: var(--run); font-weight: 600; }
.status .err { color: var(--danger); font-weight: 600; }

/* ---------------- key strip ---------------- */
.keystrip { margin-top: 13px; padding: 10px 11px; border-radius: 8px;
            border: 1px dashed var(--line); background: var(--bg); }
.keystrip .kline { display: flex; flex-wrap: wrap; gap: 9px; align-items: center;
                   font-size: 12.5px; color: var(--muted); }
.keystrip .kline .tag { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
                        text-transform: uppercase; color: var(--accent);
                        border: 1px solid var(--accent); border-radius: 5px; padding: 3px 6px; flex: none; }
.keyform { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.keyform input { flex: 1 1 220px; min-width: 0; padding: 8px 10px; border-radius: 7px;
                 border: 1px solid var(--line); background: var(--surface);
                 font-family: var(--font-mono); font-size: 12px; }
.keyform input:focus-visible { border-color: var(--accent); }
.keynote { margin: 8px 0 0; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
.keystrip.live { border-style: solid; border-color: var(--run); background: var(--run-soft); }
.keystrip.live .tag { color: var(--run); border-color: var(--run); }

/* ---------------- compare ---------------- */
.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 780px) { .compare { grid-template-columns: 1fr; } }
.compare .panel { margin-bottom: 0; }
.doc { margin: 0; padding: 14px 13px; font-family: var(--font-sans); font-size: 14.5px;
       line-height: 1.62; white-space: pre-wrap; overflow-wrap: anywhere; min-height: 200px; }
.doc.after { animation: fade .28s ease both; }
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
mark { background: var(--accent-soft); color: var(--accent); border-radius: 3px;
       padding: 0 2px; box-shadow: inset 0 -1px 0 var(--accent); }
.badge { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
         text-transform: uppercase; padding: 3px 7px; border-radius: 5px;
         border: 1px solid var(--line); color: var(--muted); white-space: nowrap; }
.badge.hot { color: var(--danger); border-color: var(--danger); }
.badge.warm { color: var(--accent); border-color: var(--accent); }
.badge.ok { color: var(--run); border-color: var(--run); background: var(--run-soft); }
.badge.live { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

.working { display: inline-flex; gap: 4px; align-items: center;
           font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.working i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
             animation: blip 1s infinite ease-in-out; }
.working i:nth-child(2) { animation-delay: .16s; }
.working i:nth-child(3) { animation-delay: .32s; }
@keyframes blip { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

footer { margin-top: 20px; font-family: var(--font-mono); font-size: 11px;
         color: var(--muted); letter-spacing: .06em; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; animation-iteration-count: 1 !important;
      transition-duration: .001ms !important; }
}
</style>
</head>
<body>
<div class="wrap">

  <p class="eyebrow"><span class="led" aria-hidden="true"></span>sloprun · byok · translation</p>
  <h1>Tone Translator</h1>
  <p class="lede">Paste the email you were about to regret. This page <b>scores how hot it reads</b>,
     lets you cool it phrase by phrase, then rewrites it in the tone you pick — in any language —
     keeping every fact, name and date intact.</p>

  <!-- ============ SOURCE ============ -->
  <section class="panel">
    <div class="phead">
      <h2 class="ptitle">Source · what you almost sent</h2>
      <span class="spacer"></span>
      <div class="meter" id="meter" data-band="hot" role="img" aria-labelledby="meterLabel">
        <span class="num" id="heatNum">0</span>
        <span class="track"><span class="fill" id="heatFill" style="width:0%"></span></span>
        <span class="lab" id="heatLab">cool</span>
      </div>
      <span class="sr" id="meterLabel">Heat score 0 of 100</span>
    </div>
    <div class="pbody">
      <label class="sr" for="src">Text to translate</label>
      <textarea id="src" spellcheck="false"></textarea>
      <p class="metarow" id="meta"></p>
      <div class="flags" id="flags"></div>
    </div>
  </section>

  <!-- ============ CONTROLS ============ -->
  <section class="panel">
    <div class="phead">
      <h2 class="ptitle">Target</h2>
      <span class="spacer"></span>
      <button class="btn" id="reset" type="button">reset sample</button>
    </div>
    <div class="pbody">
      <div class="row">
        <div style="flex:1 1 320px;min-width:0">
          <span class="fieldlab" id="tonelab">Tone · combine as many as you like</span>
          <div class="chips" id="tones" role="group" aria-labelledby="tonelab"></div>
        </div>
        <div>
          <label class="fieldlab" for="lang">Language</label>
          <select id="lang">
            <option value="">keep original</option>
            <option value="es">Spanish</option>
            <option value="fr">French</option>
            <option value="de">German</option>
            <option value="pt">Portuguese</option>
            <option value="ja">Japanese</option>
            <option value="ar">Arabic</option>
            <option value="hi">Hindi</option>
          </select>
        </div>
      </div>

      <div class="runrow">
        <button class="btn btn-primary" id="run" type="button">REWRITE</button>
        <p class="status" id="status" role="status" aria-live="polite"></p>
      </div>

      <div class="keystrip" id="keystrip"></div>
    </div>
  </section>

  <!-- ============ COMPARE ============ -->
  <div class="compare">
    <section class="panel">
      <div class="phead">
        <h2 class="ptitle">Before</h2>
        <span class="spacer"></span>
        <span class="badge hot" id="beforeBadge">heat 0</span>
      </div>
      <div class="doc" id="before"></div>
    </section>
    <section class="panel">
      <div class="phead">
        <h2 class="ptitle">After</h2>
        <span class="spacer"></span>
        <span class="badge" id="engineBadge">recorded</span>
        <button class="btn" id="copy" type="button">copy</button>
      </div>
      <div class="doc after" id="after"></div>
    </section>
  </div>

  <footer>demo · sloprun.dev</footer>
</div>

<script>
(function () {
  "use strict";
  var $ = function (id) { return document.getElementById(id); };

  /* ---------- theme bridge ---------- */
  window.addEventListener("message", function (e) {
    var d = e && e.data;
    if (!d || d.type !== "sloprun:theme") return;
    if (d.theme === "light" || d.theme === "dark") {
      document.documentElement.setAttribute("data-theme", d.theme);
    } else {
      document.documentElement.removeAttribute("data-theme");
    }
  });

  /* ---------- the sample ---------- */
  var SAMPLE = [
    "Subject: Re: Q3 launch dates — AGAIN",
    "",
    "Marcus,",
    "",
    "This is the THIRD time I've had to ask about the launch dates and I'm frankly sick of it. " +
      "You people never respond to anything unless I chase you down. It's completely unacceptable.",
    "",
    "Your team has missed every single deadline this quarter and now you're telling me the copy " +
      "review \"might slip\" again?! Do you have any idea what that does to my roadmap?",
    "",
    "I need the final dates on my desk by tomorrow morning. No excuses this time.",
    "",
    "— Dana"
  ].join("\n");

  /* ---------- heat rules: [pattern, weight, softened form, why] ---------- */
  function lower(m) { return m.toLowerCase(); }
  function plural(n) {
    if (/(?:s|x|z|ch|sh)$/i.test(n)) return n + "es";
    if (/[^aeiou]y$/i.test(n)) return n.slice(0, -1) + "ies";
    if (/s$/i.test(n)) return n;
    return n + "s";
  }
  function fewer(m) {
    var noun = m.replace(/^every single\s+/i, "");
    return "several " + plural(noun);
  }
  var RULES = [
    { re: /\byou people\b/gi,             w: 12, soft: "the team",                     why: "us-vs-them" },
    { re: /\bdo you have any idea\b[^?\n]*\?/gi, w: 8, soft: function (m) {
        return "I want to flag" + m.replace(/^do you have any idea/i, "").replace(/\s*\?\s*$/, "") + ".";
      }, why: "rhetorical jab" },
    { re: /\bsick of it\b/gi,             w: 10, soft: "concerned",                    why: "venting" },
    { re: /\bno excuses(?:\s+this time)?\b/gi, w: 9, soft: "I understand things come up", why: "ultimatum" },
    { re: /\bunacceptable\b/gi,           w: 9,  soft: "not workable for us",          why: "verdict" },
    { re: /\b(?:ridiculous|absurd|incompetent|useless|pathetic|sloppy)\b/gi,
                                          w: 10, soft: "not up to standard",           why: "insult" },
    { re: /\bevery single \w+\b/gi,       w: 7,  soft: fewer,                         why: "absolutist" },
    { re: /\bevery single\b/gi,           w: 7,  soft: "several",                      why: "absolutist" },
    { re: /\bnever\b/gi,                  w: 7,  soft: "rarely",                       why: "absolutist" },
    { re: /\balways\b/gi,                 w: 7,  soft: "often",                        why: "absolutist" },
    { re: /\b(?:completely|totally|utterly)\b/gi, w: 6, soft: "",                      why: "intensifier" },
    { re: /\bon my desk\b/gi,             w: 5,  soft: "",                             why: "command" },
    { re: /\bI need\b/g,                  w: 4,  soft: "Could you send",               why: "demand" },
    { re: /\bfrankly\b/gi,                w: 3,  soft: "",                             why: "loaded aside" },
    { re: /\bASAP\b/g,                    w: 5,  soft: "when you can",                 why: "pressure" },
    { re: /[!?]{2,}/g,                    w: 8,  soft: ".",                            why: "punctuation pile-up" },
    { re: /!/g,                           w: 3,  soft: ".",                            why: "exclamation" },
    { re: /\b[A-Z]{3,}\b/g,               w: 6,  soft: lower,                          why: "shouting" }
  ];

  function scan(text) {
    var cand = [], i, m, re, r;
    for (i = 0; i < RULES.length; i++) {
      r = RULES[i];
      re = new RegExp(r.re.source, r.re.flags);
      while ((m = re.exec(text)) !== null) {
        if (m[0] === "") { re.lastIndex++; continue; }
        cand.push({ s: m.index, e: m.index + m[0].length, t: m[0], w: r.w, why: r.why,
                    soft: typeof r.soft === "function" ? r.soft(m[0]) : r.soft, p: i });
      }
    }
    cand.sort(function (a, b) { return a.p - b.p || a.s - b.s; });
    var out = [], j, ok;
    for (i = 0; i < cand.length; i++) {
      ok = true;
      for (j = 0; j < out.length; j++) {
        if (cand[i].s < out[j].e && out[j].s < cand[i].e) { ok = false; break; }
      }
      if (ok) out.push(cand[i]);
    }
    out.sort(function (a, b) { return a.s - b.s; });
    var sum = 0;
    for (i = 0; i < out.length; i++) sum += out[i].w;
    return { spans: out, heat: Math.min(100, Math.round(sum)) };
  }

  function band(h) {
    if (h === 0) return "cool";
    if (h < 25) return "mild";
    if (h < 55) return "warm";
    if (h < 80) return "hot";
    return "scorching";
  }

  function esc(s) {
    return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;")
      .replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  }

  function highlight(text, spans) {
    var out = "", at = 0, i, sp;
    for (i = 0; i < spans.length; i++) {
      sp = spans[i];
      out += esc(text.slice(at, sp.s));
      out += '<mark title="' + esc(sp.why) + '">' + esc(sp.t) + "</mark>";
      at = sp.e;
    }
    return out + esc(text.slice(at));
  }

  /* ---------- softening + local rules engine ---------- */
  function tidy(s) {
    return s
      .replace(/[ \t]+([,.;:?])/g, "$1")
      .replace(/[ \t]{2,}/g, " ")
      .replace(/\.{2,}/g, ".")
      .replace(/(^|[.?]\s+|\n)([a-z])/g, function (all, pre, ch) { return pre + ch.toUpperCase(); })
      .replace(/[ \t]+\n/g, "\n")
      .replace(/\n{3,}/g, "\n\n")
      .trim();
  }

  function coolAll(text) {
    var sp = scan(text).spans, i, out = text;
    for (i = sp.length - 1; i >= 0; i--) {
      out = out.slice(0, sp[i].s) + sp[i].soft + out.slice(sp[i].e);
    }
    return tidy(out);
  }

  var PIRATE = { "you": "ye", "your": "yer", "yours": "yers", "my": "me", "is": "be", "are": "be",
    "am": "be", "friend": "matey", "team": "crew", "company": "ship", "hello": "ahoy", "hi": "ahoy",
    "yes": "aye", "money": "doubloons", "week": "se'nnight", "boss": "cap'n", "manager": "quartermaster",
    "deadline": "tide", "meeting": "parley", "quickly": "smartly", "stop": "belay", "before": "afore",
    "tomorrow": "the morrow", "the": "th'", "of": "o'", "for": "fer", "over": "o'er", "email": "dispatch" };
  var PIRATE_RE = new RegExp("\\b(" + Object.keys(PIRATE).sort(function (a, b) {
    return b.length - a.length;
  }).join("|") + ")\\b", "gi");

  function keepCase(src, rep) {
    if (/^[A-Z]/.test(src)) return rep.charAt(0).toUpperCase() + rep.slice(1);
    return rep;
  }

  function localRewrite(text, tones) {
    var t = coolAll(text), tail = [];
    if (tones.indexOf("concise") >= 0) {
      t = t.replace(/\b(just|really|very|actually|basically|simply|kind of|sort of|I think that|at this point in time)\b ?/gi, "");
      t = t.replace(/\bin order to\b/gi, "to").replace(/\bat this time\b/gi, "now");
      t = t.replace(/\bdue to the fact that\b/gi, "because");
      t = t.replace(/\bin the event that\b/gi, "if").replace(/\bwith regard to\b/gi, "about");
      t = t.replace(/\bI (?:just )?wanted to (?:follow up|check in|ask)\b/gi, "Following up");
      t = t.replace(/\b(?:please be advised that|I am writing to let you know that|it should be noted that)\b ?/gi, "");
      t = t.replace(/\bhad to ask\b/gi, "asked").replace(/\bhad to chase\b/gi, "chased");
    }
    if (tones.indexOf("diplomatic") >= 0) {
      t = t.replace(/\byou (didn't|did not|failed to|haven't)\b/gi, "we $1");
      t = t.replace(/\byour (team|side)\b/gi, "the $1");
      if (!/thank/i.test(t)) tail.push("Thanks for bearing with me on this.");
    }
    if (tones.indexOf("legal") >= 0) {
      t = t.replace(/\bplease\b/gi, "kindly").replace(/\bwhen you can\b/gi, "no later than the next business day");
      tail.push("Please confirm receipt of this message in writing.");
    }
    if (tones.indexOf("friendly") >= 0) {
      t = t.replace(/\bplease\b/gi, "any chance you could");
      tail.push("Hope the rest of the week is a calm one.");
    }
    if (tones.indexOf("pirate") >= 0) {
      t = t.replace(PIRATE_RE, function (m) { return keepCase(m, PIRATE[m.toLowerCase()]); });
      /* keep the Subject line first — the hail goes on the body, not the header */
      t = t.replace(/^(?:subject:[^\n]*\n+)?/i, function (m) { return m + "Arr — "; });
      tail.push("Fair seas,\nyer correspondent");
    }
    /* tails are appended after every replacement pass so one tone never rewrites another's sign-off */
    if (tail.length) t += "\n\n" + tail.join("\n\n");
    return tidy(t);
  }

  /* ---------- recorded Claude transcripts (no key needed) ---------- */
  var CANNED = {};
  CANNED["diplomatic|"] =
"Subject: Re: Q3 launch dates — checking in once more\n\n" +
"Hi Marcus,\n\n" +
"I wanted to follow up on the Q3 launch dates. This is my third time asking, so I suspect the " +
"request may be getting lost somewhere — apologies if I've been sending it to the wrong place.\n\n" +
"A few deadlines have moved this quarter, and I've now heard the copy review may slip as well. " +
"That has a real knock-on effect on the roadmap I'm holding, so I'd rather get ahead of it than " +
"react to it later.\n\n" +
"Could you send me the final dates by tomorrow morning? If tomorrow isn't realistic, a quick note " +
"on when they will be ready is just as useful.\n\n" +
"Thanks,\nDana";

  CANNED["concise|"] =
"Subject: Q3 launch dates — need finals by tomorrow\n\n" +
"Marcus — third request for the Q3 launch dates.\n\n" +
"Several deadlines moved this quarter and I hear copy review may slip again, which affects the roadmap.\n\n" +
"Please send the final dates by tomorrow morning, or tell me when you can.\n\n" +
"Dana";

  CANNED["legal|"] =
"Subject: Re: Q3 Launch Dates — Request for Confirmed Schedule\n\n" +
"Marcus,\n\n" +
"Further to my two previous requests this quarter, I am writing to request written confirmation of " +
"the final Q3 launch dates.\n\n" +
"For the record: a number of scheduled milestones have not been met during the current quarter, and " +
"I have been advised that the copy review may be subject to further delay. These deviations " +
"materially affect dependent roadmap commitments on my side.\n\n" +
"Kindly provide the confirmed dates in writing no later than 10:00 tomorrow. Should that timeline " +
"not be achievable, please state the date on which confirmation will be provided.\n\n" +
"Regards,\nDana";

  CANNED["friendly|"] =
"Subject: Re: Q3 launch dates — one more nudge\n\n" +
"Hey Marcus,\n\n" +
"Sorry to be the person who keeps knocking — this is take three on the Q3 launch dates.\n\n" +
"I know this quarter has been a lot, and I heard the copy review might need more time. I get it; I " +
"just have a roadmap hanging off those dates and I'd rather plan around reality than guess.\n\n" +
"Any chance you could get me the finals by tomorrow morning? And if that's tight, just tell me when — " +
"that helps plenty.\n\n" +
"Thanks so much,\nDana";

  CANNED["pirate|"] =
"Subject: Re: Q3 sailin' dates — third time hailin' ye\n\n" +
"Marcus, ye fine scoundrel,\n\n" +
"This be the third time I've hailed yer ship about the Q3 launch dates, and nary a word has come " +
"back across the water.\n\n" +
"Word from the crow's nest says the copy review may drift off course again. That be a mighty hole in " +
"me roadmap, and I'd sooner patch it now than bail water later.\n\n" +
"Send the final dates by tomorrow's first bell, or tell me plain when ye can — either be fair winds to me.\n\n" +
"Fair seas,\nCap'n Dana";

  CANNED["concise+diplomatic|"] =
"Subject: Re: Q3 launch dates — quick follow-up\n\n" +
"Hi Marcus,\n\n" +
"Following up on the Q3 launch dates — third ask, so I suspect it's getting lost.\n\n" +
"With a few dates already moved and copy review possibly slipping, the roadmap I hold is exposed.\n\n" +
"Could you send the finals by tomorrow morning, or let me know when they'll be ready?\n\n" +
"Thanks,\nDana";

  CANNED["diplomatic|es"] =
"Asunto: Re: Fechas de lanzamiento del Q3 — seguimiento\n\n" +
"Hola Marcus:\n\n" +
"Quería retomar el tema de las fechas de lanzamiento del Q3. Es la tercera vez que lo pregunto, así " +
"que supongo que mi mensaje se está perdiendo por el camino; disculpa si lo he enviado al lugar " +
"equivocado.\n\n" +
"Varios plazos se han movido este trimestre y ahora me comentan que la revisión de textos también " +
"podría retrasarse. Eso afecta directamente a la hoja de ruta que gestiono, así que prefiero " +
"anticiparme en lugar de reaccionar más tarde.\n\n" +
"¿Podrías enviarme las fechas definitivas antes de mañana por la mañana? Si mañana no es viable, " +
"una nota breve indicando cuándo estarán listas me sirve igual.\n\n" +
"Gracias,\nDana";

  CANNED["diplomatic|ja"] =
"件名: Re: 第3四半期のローンチ日程について(再度のご連絡)\n\n" +
"マーカスさん\n\n" +
"お世話になっております。第3四半期のローンチ日程について、改めてご連絡いたします。同じ依頼を差し上げるのは" +
"今回で三度目となりますので、こちらの連絡が届いていない可能性もあるかと存じます。行き違いがありましたら申し訳ありません。\n\n" +
"今四半期はいくつかの期日が動いており、コピーレビューについても遅れる可能性があると伺いました。こちらで管理している" +
"ロードマップに影響が出るため、後手に回る前に把握しておきたいと考えております。\n\n" +
"恐れ入りますが、明日の午前中までに最終日程をお送りいただけますでしょうか。難しいようでしたら、いつ頃ご提示" +
"いただけるかをお知らせいただくだけでも助かります。\n\n" +
"何卒よろしくお願いいたします。\nダナ";

  /* ---------- tones ---------- */
  var TONES = [
    { id: "diplomatic", brief: "Diplomatic: warm but direct. No blame, no sarcasm. Assume good faith, name the impact rather than the person, and end with a concrete, easy-to-answer ask." },
    { id: "concise",    brief: "Concise: cut every word that is not load-bearing. Short sentences. No hedging, no throat-clearing." },
    { id: "legal",      brief: "Legal/formal: precise, dated, on-the-record register. Neutral affect, passive where it helps, explicit request for written confirmation." },
    { id: "friendly",   brief: "Friendly: casual and human. Contractions, a little warmth, acknowledge the other side's load. Never saccharine." },
    { id: "pirate",     brief: "Pirate: full nautical brigand voice — arr, ye, matey, tides and crow's nests — while still conveying every real fact and the actual ask." }
  ];
  var LANGS = { es: "Spanish", fr: "French", de: "German", pt: "Portuguese",
                ja: "Japanese", ar: "Arabic", hi: "Hindi" };
  var RTL = { ar: 1, he: 1, fa: 1, ur: 1 };

  var SYSTEM =
    "You rewrite messages so they land better. Absolute rules: preserve every fact, name, date, " +
    "number and the underlying request exactly. Never invent details. Never add commentary, " +
    "preamble, options or markdown fences — output ONLY the rewritten message, keeping the " +
    "Subject line if the original had one.";

  function buildPrompt(text, tones, lang) {
    var briefs = tones.map(function (id) {
      for (var i = 0; i < TONES.length; i++) if (TONES[i].id === id) return "- " + TONES[i].brief;
      return "";
    }).join("\n");
    var p = "Rewrite the message below.\n\nTarget tone (blend all of these):\n" + briefs + "\n";
    if (lang) p += "\nWrite the entire output in " + LANGS[lang] + ", idiomatic and natural — not a literal gloss.\n";
    p += "\n--- MESSAGE ---\n" + text + "\n--- END MESSAGE ---";
    return p;
  }

  /* ---------- state ---------- */
  var selected = ["diplomatic"];
  var apiKey = "";
  var running = false;
  var bridgeSeen = false;
  var runId = 0;
  var lastAfter = "";
  var copyTimer = null;

  var src = $("src"), flagsEl = $("flags"), metaEl = $("meta"), statusEl = $("status"),
      runBtn = $("run"), beforeEl = $("before"), afterEl = $("after"),
      engineBadge = $("engineBadge"), beforeBadge = $("beforeBadge"), langSel = $("lang"),
      copyBtn = $("copy");

  function hasBridge() {
    return !!(window.sloprun && window.sloprun.ai && typeof window.sloprun.ai.ask === "function");
  }
  function hasModel() { return hasBridge() || !!apiKey; }

  /* ---------- source rendering ---------- */
  function words(t) { var m = t.trim().match(/\S+/g); return m ? m.length : 0; }

  function renderSource() {
    var text = src.value;
    var r = scan(text);
    var b = band(r.heat);
    var empty = text.trim() === "";

    $("heatNum").textContent = r.heat;
    $("heatFill").style.width = r.heat + "%";
    $("heatLab").textContent = b;
    $("meter").setAttribute("data-band", b);
    $("meterLabel").textContent = "Heat score " + r.heat + " of 100 — " + b;

    metaEl.innerHTML = "<span><b>" + words(text) + "</b> words</span>" +
      "<span><b>" + text.length + "</b> chars</span>" +
      "<span><b>" + r.spans.length + "</b> flagged</span>";

    flagsEl.innerHTML = "";
    if (empty) {
      var none = document.createElement("span");
      none.className = "status";
      none.textContent = "empty · paste or type a message, or press reset sample";
      flagsEl.appendChild(none);
    } else if (r.spans.length === 0) {
      var ok = document.createElement("span");
      ok.className = "cooled";
      ok.textContent = "cooled · heat 0 · nothing left to flag";
      flagsEl.appendChild(ok);
    } else {
      r.spans.forEach(function (sp, i) {
        var b2 = document.createElement("button");
        b2.type = "button";
        b2.className = "flag";
        b2.dataset.i = String(i);
        b2.title = sp.why + " — click to soften in place";
        b2.innerHTML = '<span class="ph">' + esc(sp.t) + '</span><span class="w">-' + sp.w + "</span>";
        b2.setAttribute("aria-label", "Soften " + sp.t + " — " + sp.why + ", worth " + sp.w + " heat");
        flagsEl.appendChild(b2);
      });
      var all = document.createElement("button");
      all.type = "button";
      all.className = "btn";
      all.id = "coolAll";
      all.textContent = "cool all " + r.spans.length;
      flagsEl.appendChild(all);
    }

    /* the Before pane is the highlighted mirror of the source — keep it live so
       cooling a phrase visibly removes its highlight instead of freezing at the
       state captured by the last REWRITE */
    paintBefore(text, r, empty);
    return r;
  }

  flagsEl.addEventListener("click", function (e) {
    var all = e.target.closest ? e.target.closest("#coolAll") : null;
    if (all) {
      var ch = coolAll(src.value);
      src.value = ch;
      var chHeat = renderSource().heat;
      setStatus(chHeat === 0 ? "ok" : "plain",
        "cooled by hand · heat now <b>" + chHeat + "</b> · no model involved");
      var f = flagsEl.querySelector("button");
      if (f) f.focus(); else { flagsEl.tabIndex = -1; flagsEl.focus(); }
      return;
    }
    var btn = e.target.closest ? e.target.closest(".flag") : null;
    if (!btn) return;
    var i = parseInt(btn.dataset.i, 10);
    var sp = scan(src.value).spans[i];
    if (!sp) return;
    src.value = tidy(src.value.slice(0, sp.s) + sp.soft + src.value.slice(sp.e));
    var r = renderSource();
    setStatus(r.heat === 0 ? "ok" : "plain",
      r.heat === 0 ? "heat <b>0</b> — cooled by hand, no model involved"
                   : "softened <b>" + esc(sp.t) + "</b> · heat now <b>" + r.heat + "</b>");
    var next = flagsEl.querySelectorAll(".flag")[Math.min(i, flagsEl.querySelectorAll(".flag").length - 1)];
    if (next) next.focus(); else { flagsEl.tabIndex = -1; flagsEl.focus(); }
  });

  src.addEventListener("input", function () { renderSource(); });

  /* ---------- tone chips ---------- */
  var tonesEl = $("tones");
  TONES.forEach(function (t) {
    var b = document.createElement("button");
    b.type = "button";
    b.className = "chip";
    b.dataset.id = t.id;
    b.setAttribute("aria-pressed", selected.indexOf(t.id) >= 0 ? "true" : "false");
    b.innerHTML = '<span class="dot" aria-hidden="true"></span>' + t.id;
    tonesEl.appendChild(b);
  });
  tonesEl.addEventListener("click", function (e) {
    var b = e.target.closest ? e.target.closest(".chip") : null;
    if (!b) return;
    var id = b.dataset.id, i = selected.indexOf(id);
    if (i >= 0) selected.splice(i, 1); else selected.push(id);
    b.setAttribute("aria-pressed", i >= 0 ? "false" : "true");
    syncRunButton();
  });

  function syncRunButton() {
    runBtn.disabled = running || selected.length === 0;
    if (running) return;
    if (selected.length === 0) setStatus("err", "pick at least one target tone");
    else setStatus("plain", idleLine());
  }

  function idleLine() {
    var lang = langSel.value, eng;
    if (hasBridge()) eng = "claude (platform)";
    else if (apiKey) eng = "claude (your key)";
    else if (isSample() && cannedFor()) eng = "recorded transcript";
    else eng = "offline rules";
    return "ready · <b>" + selected.slice().sort().join("+") + "</b>" +
           (lang ? " · lang <b>" + lang + "</b>" : "") +
           " · engine <b>" + eng + "</b>";
  }

  langSel.addEventListener("change", function () { syncRunButton(); });

  function setStatus(kind, html) {
    statusEl.dataset.hint = kind === "plain" ? "1" : "0";
    var cls = kind === "ok" ? "ok" : kind === "err" ? "err" : "";
    statusEl.innerHTML = cls ? '<span class="' + cls + '">' + html + "</span>" : html;
  }

  /* ---------- key strip ---------- */
  function renderKeyStrip() {
    var ks = $("keystrip");
    if (hasBridge()) {
      ks.className = "keystrip live";
      ks.innerHTML = '<div class="kline"><span class="tag">model live</span>' +
        "<span>Running on the sloprun platform key — no setup needed.</span></div>";
      return;
    }
    if (apiKey) {
      ks.className = "keystrip live";
      ks.innerHTML = '<div class="kline"><span class="tag">key connected</span>' +
        "<span>Calls go straight from this tab to the Anthropic API.</span>" +
        '<span class="spacer"></span><button class="btn" type="button" id="disconnect">disconnect</button></div>' +
        '<p class="keynote">model claude-sonnet-5 · key held in memory only · never stored, never sent anywhere else</p>';
      $("disconnect").addEventListener("click", function () {
        apiKey = "";
        renderKeyStrip();
        setStatus("plain", idleLine());
      });
      return;
    }
    ks.className = "keystrip";
    ks.innerHTML = '<div class="kline"><span class="tag">no key</span>' +
      "<span>You are seeing a <b>recorded Claude transcript</b> for the sample email. " +
      "Connect a key to rewrite anything you like, live.</span></div>" +
      '<form class="keyform" id="keyform">' +
      '<label class="sr" for="keyin">Anthropic API key</label>' +
      '<input id="keyin" type="password" autocomplete="off" spellcheck="false" placeholder="sk-ant-...">' +
      '<button class="btn" type="submit">connect key</button></form>' +
      '<p class="keynote">your key stays in this tab · memory only, never localStorage · model claude-sonnet-5</p>';
    $("keyform").addEventListener("submit", function (e) {
      e.preventDefault();
      var v = $("keyin").value.trim();
      if (!v) { setStatus("err", "paste a key first"); return; }
      apiKey = v;
      renderKeyStrip();
      setStatus("ok", "key connected · live rewrites enabled");
    });
  }

  /* ---------- model calls ---------- */
  function askBridge(prompt) {
    return window.sloprun.ai.ask(prompt, { system: SYSTEM });
  }

  function askDirect(prompt) {
    return fetch("https://api.anthropic.com/v1/messages", {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-api-key": apiKey,
        "anthropic-version": "2023-06-01",
        "anthropic-dangerous-direct-browser-access": "true"
      },
      body: JSON.stringify({
        model: "claude-sonnet-5",
        max_tokens: 1400,
        system: SYSTEM,
        messages: [{ role: "user", content: prompt }]
      })
    }).then(function (r) {
      return r.json().catch(function () { return {}; }).then(function (d) {
        if (!r.ok) throw new Error((d && d.error && d.error.message) || ("HTTP " + r.status));
        var parts = (d.content || []).filter(function (c) { return c.type === "text"; });
        var txt = parts.map(function (c) { return c.text; }).join("").trim();
        if (!txt) throw new Error("empty response");
        return txt;
      });
    });
  }

  function isSample() { return src.value.trim() === SAMPLE.trim(); }
  function cannedFor() { return CANNED[selected.slice().sort().join("+") + "|" + langSel.value]; }

  /* ---------- render output ---------- */
  function paintBefore(text, r, empty) {
    beforeEl.innerHTML = highlight(text, r.spans);
    beforeBadge.textContent = empty ? "empty" : "heat " + r.heat;
    beforeBadge.className = "badge " + (empty ? "" : r.heat === 0 ? "ok" : r.heat < 55 ? "warm" : "hot");
    return r;
  }

  function setBefore(text) {
    return paintBefore(text, scan(text), text.trim() === "");
  }

  function setAfter(text, engine, ms, beforeHeat) {
    lastAfter = text;
    afterEl.lang = langSel.value || "en";
    afterEl.dir = RTL[langSel.value] ? "rtl" : "ltr";
    afterEl.textContent = text;
    afterEl.classList.remove("after");
    void afterEl.offsetWidth;
    afterEl.classList.add("after");

    var ah = scan(text).heat;
    engineBadge.textContent = engine.label;
    engineBadge.className = "badge " + (engine.live ? "live" : "");
    engineBadge.title = engine.note;

    var line = "engine <b>" + engine.label + "</b> · " +
      (ms === null ? "" : "<b>" + ms + "</b> ms · ") +
      "<b>" + words(src.value) + "</b> → <b>" + words(text) + "</b> words · heat <b>" +
      beforeHeat + "</b> → <b>" + ah + "</b>";
    setStatus(engine.ok ? "ok" : "plain", line + (engine.note ? " · " + esc(engine.note) : ""));
  }

  function busy(on) {
    running = on;
    runBtn.disabled = on || selected.length === 0;
    runBtn.textContent = on ? "RUNNING" : "REWRITE";
    if (on) afterEl.innerHTML = '<span class="working">rewriting<i></i><i></i><i></i></span>';
  }

  function run() {
    if (selected.length === 0) { setStatus("err", "pick at least one target tone"); return; }
    var text = src.value.trim();
    if (!text) { setStatus("err", "nothing to translate — type or paste a message"); return; }

    var id = ++runId;
    var beforeHeat = setBefore(text).heat;
    var prompt = buildPrompt(text, selected.slice().sort(), langSel.value);
    var t0 = (window.performance && performance.now) ? performance.now() : Date.now();
    busy(true);

    var work;
    if (hasBridge()) work = askBridge(prompt);
    else if (apiKey) work = askDirect(prompt);
    else work = Promise.reject(new Error("no-model"));

    work.then(function (out) {
      if (id !== runId) return;
      var ms = Math.round(((window.performance && performance.now) ? performance.now() : Date.now()) - t0);
      busy(false);
      setAfter(String(out).trim(), {
        label: hasBridge() ? "claude · platform" : "claude · your key",
        live: true, ok: true, note: "live model"
      }, ms, beforeHeat);
    }).catch(function (err) {
      if (id !== runId) return;
      var msg = err && err.message ? err.message : String(err);
      var canned = isSample() ? cannedFor() : null;
      var wait = (canned ? 420 : 260);
      if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) wait = 0;
      window.setTimeout(function () {
        if (id !== runId) return;
        var ms = Math.round(((window.performance && performance.now) ? performance.now() : Date.now()) - t0);
        busy(false);
        if (canned) {
          setAfter(canned, {
            label: "recorded", live: false, ok: false,
            note: msg === "no-model" ? "recorded Claude output for this sample — connect a key to run it live"
                                     : "model error: " + msg + " — showing recorded output"
          }, ms, beforeHeat);
        } else {
          var out = localRewrite(text, selected);
          var note = msg === "no-model"
            ? "offline rules pass — connect a key for a real rewrite"
            : "model error: " + msg + " — fell back to rules";
          if (langSel.value) note += " · translation needs a live model";
          setAfter(out, { label: "rules · offline", live: false, ok: false, note: note }, ms, beforeHeat);
        }
      }, wait);
    });
  }

  runBtn.addEventListener("click", run);

  /* ---------- copy ---------- */
  copyBtn.addEventListener("click", function () {
    var done = function (ok) {
      copyBtn.textContent = ok ? "copied" : "select & copy";
      copyBtn.classList.toggle("done", ok);
      window.clearTimeout(copyTimer);
      copyTimer = window.setTimeout(function () {
        copyBtn.textContent = "copy";
        copyBtn.classList.remove("done");
      }, 1600);
    };
    if (!lastAfter) { done(false); return; }
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(lastAfter).then(function () { done(true); },
                                                      function () { done(false); });
        return;
      }
    } catch (e) { /* fall through */ }
    try {
      var ta = document.createElement("textarea");
      ta.value = lastAfter;
      ta.setAttribute("readonly", "");
      ta.style.position = "fixed";
      ta.style.opacity = "0";
      document.body.appendChild(ta);
      ta.select();
      var ok = document.execCommand && document.execCommand("copy");
      document.body.removeChild(ta);
      done(!!ok);
    } catch (e2) { done(false); }
  });

  /* ---------- reset ---------- */
  $("reset").addEventListener("click", function () {
    src.value = SAMPLE;
    selected = ["diplomatic"];
    Array.prototype.forEach.call(tonesEl.querySelectorAll(".chip"), function (c) {
      c.setAttribute("aria-pressed", c.dataset.id === "diplomatic" ? "true" : "false");
    });
    langSel.value = "";
    renderSource();
    syncRunButton();
    seed();
    src.focus();
  });

  /* ---------- boot: preloaded, already doing its thing ---------- */
  function seed() {
    var beforeHeat = setBefore(SAMPLE.trim()).heat;
    if (hasBridge()) {
      setStatus("plain", idleLine() + " — press REWRITE");
      afterEl.lang = "en";
      afterEl.dir = "ltr";
      afterEl.textContent = CANNED["diplomatic|"];
      lastAfter = CANNED["diplomatic|"];
      engineBadge.textContent = "recorded";
      engineBadge.className = "badge";
      engineBadge.title = "sample output — press REWRITE to run it live";
    } else {
      setAfter(CANNED["diplomatic|"], {
        label: "recorded", live: false, ok: false,
        note: "recorded Claude output · heat scoring and cooling run locally"
      }, null, beforeHeat);
    }
  }

  /* the platform bridge may be injected after this script parses — don't strand the
     visitor on the key-connect panel if window.sloprun shows up a beat later */
  function recheckBridge() {
    if (bridgeSeen || !hasBridge()) return;
    bridgeSeen = true;
    renderKeyStrip();
    if (!running) setStatus("plain", idleLine() + " — press REWRITE");
  }
  window.addEventListener("message", function (e) {
    if (e && e.data && e.data.type === "sloprun:theme") recheckBridge();
  });
  window.addEventListener("load", recheckBridge);
  window.setTimeout(recheckBridge, 250);
  window.setTimeout(recheckBridge, 1200);

  src.value = SAMPLE;
  renderSource();
  renderKeyStrip();
  bridgeSeen = hasBridge();
  syncRunButton();
  seed();
})();
</script>
</body>
</html>

source-visible by construction · nothing is published here without its code

Comments

0 total

No comments yet. If you ran it, say what happened.

Report this post

Goes straight to the moderation queue. Enough independent reports and the post is suspended automatically until a human looks.

what is wrong