{
  "type": "script",
  "enabled": true,
  "name": "topbar",
  "id": "7e6d5300-c037-44dd-9114-ffe3ad8cd607",
  "content": "if (!window.renriCleanEmpty) {\n    window.renriCleanEmpty = function(html) {\n        return html\n            .replace(/<p>\\s*(<br\\s*\\/?>)?\\s*<\\/p>/gi, '')\n            .replace(/^(\\s*<br\\s*\\/?>)+/gi, '')\n            .replace(/(\\s*<br\\s*\\/?>)+$/gi, '')\n            .replace(/(<br\\s*\\/?>(\\s*)){3,}/gi, '<br>');\n    };\n}\n\nif (!window.renriHide) {\n    window.renriHide = function($target) {\n        if (!$target || !$target.length) return;\n        let html = $target.html();\n        if (!html) return;\n        const orig = html;\n        html = html.replace(/\\[TPBR:[^\\]]*\\]/gi, '');\n        if (html !== orig) {\n            html = window.renriCleanEmpty ? window.renriCleanEmpty(html) : html;\n            $target.html(html.trim());\n        }\n    };\n}\n\n(function() {\n    const $ = window.jQuery;\n    const MOD = {\n        promptId: 'renri_topbar_system',\n        systemPrompt: 'Always begin your messages with a timestamp and location block in this exact format:\\n[TPBR: HH:MM | DD.MM.YYYY | current weather (in Celsius) | Country/City \\u2014 District \\u2014 Specific place]\\nExample: [TPBR: 02:14 | 14.02.2026 | Light snow, -5\\u00b0C | Moscow \\u2014 Arbat District \\u2014 \"Firefly\" Jazz Club]\\nLanguage for placeholders must match the language of the narrative.',\n        updatePrompt: '[System Note: Continue with TOPBAR at the START of your message. Previous: __LAST_TIME__ | __LAST_DATE__. Same [TPBR: ...] format.]',\n        cssId: 'renri-topbar-styles',\n        css: '@import url(\\'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;700&display=swap\\');.rtb{position:relative;border:1px solid rgba(255,255,255,0.10);font-family:\\'JetBrains Mono\\',\\'Courier New\\',monospace;overflow:hidden}.rtb-sky{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}.rtb-bar{position:absolute;bottom:0;left:0;height:1px;background:rgba(255,255,255,0.5);pointer-events:none;z-index:2}.rtb-inner{position:relative;z-index:1;display:grid;grid-template-columns:auto 1fr;align-items:center}.rtb-left{padding:10px 16px 11px 13px;border-right:1px solid rgba(255,255,255,0.10);display:flex;flex-direction:column;gap:3px;flex-shrink:0}.rtb-time{font-size:clamp(22px,4vw,30px);font-weight:700;line-height:1;letter-spacing:-0.03em;color:rgba(255,255,255,0.95);display:flex;align-items:baseline;text-shadow:0 1px 8px rgba(0,0,0,0.5)}.rtb-char{display:inline-block;animation:char-in 0.2s cubic-bezier(0.16,1,0.3,1) both}@keyframes char-in{from{opacity:0;transform:rotateX(-90deg)}to{opacity:1;transform:rotateX(0deg)}}.rtb-colon{margin:0 1px;font-weight:300;color:rgba(255,255,255,0.35);text-shadow:none}.rtb-date{font-size:10px;font-weight:300;letter-spacing:0.08em;color:rgba(255,255,255,0.45);animation:fade-in 0.4s ease 0.5s both;text-shadow:0 1px 4px rgba(0,0,0,0.4)}.rtb-right{padding:10px 14px 11px;display:flex;flex-direction:column;gap:4px;min-width:0}.rtb-weather{font-size:11px;font-weight:500;color:rgba(255,255,255,0.90);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;animation:fade-in 0.3s ease 0.35s both;text-shadow:0 1px 6px rgba(0,0,0,0.5)}.rtb-location{font-size:10px;font-weight:300;color:rgba(255,255,255,0.55);white-space:normal;overflow-wrap:anywhere;line-height:1.4;animation:fade-in 0.3s ease 0.42s both;text-shadow:0 1px 4px rgba(0,0,0,0.4)}@keyframes fade-in{from{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}@media(max-width:380px){.rtb-time{font-size:20px}.rtb-left{padding:8px 12px 9px 10px}.rtb-right{padding:8px 10px 9px}}',\n        mainRegex: /\\[TPBR:\\s*(\\d{1,2}:\\d{2})\\s*\\|\\s*([\\d.]+)\\s*\\|\\s*([^|\\]]+?)\\s*\\|\\s*([^\\]]+)\\]/i,\n        expirationDepth: 7,\n    };\n    let TH = null;\n    const _rendering = {};\n\n    function esc(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\n    function lerp(a,b,t){ return a+(b-a)*t; }\n    function lerpC(c1,c2,t){ return [0,1,2].map(function(i){ return Math.round(lerp(c1[i],c2[i],t)); }); }\n    function rgb(c,a){ a=a===undefined?1:a; return 'rgba('+c[0]+','+c[1]+','+c[2]+','+a+')'; }\n\n    var SKY_KEYS = [\n        [0,   [4,6,18],     [6,8,22]],\n        [4,   [6,8,22],     [8,10,28]],\n        [5,   [12,10,30],   [35,18,40]],\n        [6,   [60,30,60],   [120,60,50]],\n        [7,   [140,80,50],  [200,130,70]],\n        [8,   [160,110,80], [210,165,110]],\n        [9,   [100,140,200],[160,195,230]],\n        [11,  [70,120,190], [140,185,225]],\n        [13,  [55,110,185], [120,175,225]],\n        [15,  [65,120,190], [130,180,225]],\n        [17,  [100,130,185],[170,185,210]],\n        [18,  [160,110,70], [210,150,90]],\n        [19,  [200,100,50], [240,150,80]],\n        [20,  [160,70,40],  [200,100,60]],\n        [21,  [60,30,50],   [90,45,60]],\n        [22,  [15,12,35],   [20,15,40]],\n        [24,  [4,6,18],     [6,8,22]],\n    ];\n\n    function getSkyColors(hour, min) {\n        var t = hour + min / 60;\n        var i = 0;\n        for (var k = 0; k < SKY_KEYS.length - 1; k++) {\n            if (t >= SKY_KEYS[k][0] && t < SKY_KEYS[k+1][0]) { i = k; break; }\n        }\n        var span = SKY_KEYS[i+1][0] - SKY_KEYS[i][0];\n        var frac = (t - SKY_KEYS[i][0]) / span;\n        return { top: lerpC(SKY_KEYS[i][1], SKY_KEYS[i+1][1], frac), bot: lerpC(SKY_KEYS[i][2], SKY_KEYS[i+1][2], frac) };\n    }\n\n    function makeSky(time) {\n        var parts = time.split(':');\n        var hour = parseInt(parts[0], 10), min = parseInt(parts[1], 10);\n        var W = 600, H = 56;\n        var colors = getSkyColors(hour, min);\n        var top = colors.top, bot = colors.bot;\n        var dayT = Math.max(0, Math.min(1, (hour + min/60 - 6) / 14));\n        var sx = dayT * W;\n        var arc = Math.sin(dayT * Math.PI);\n        var sy = H * 0.92 - arc * H * 1.1;\n        var isDay = hour >= 6 && hour < 20;\n        var isDawn = hour >= 4 && hour < 7;\n        var uid = hour + '_' + min;\n\n        var out = '<svg class=\"rtb-sky\" viewBox=\"0 0 ' + W + ' ' + H + '\" preserveAspectRatio=\"xMidYMid slice\" xmlns=\"http://www.w3.org/2000/svg\">'\n            + '<defs><linearGradient id=\"sg' + uid + '\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">'\n            + '<stop offset=\"0%\" stop-color=\"' + rgb(top) + '\"/>'\n            + '<stop offset=\"100%\" stop-color=\"' + rgb(bot) + '\"/>'\n            + '</linearGradient></defs>'\n            + '<rect width=\"' + W + '\" height=\"' + H + '\" fill=\"url(#sg' + uid + ')\"/>';\n\n        // stars\n        if (!isDay || hour < 7 || hour >= 20) {\n            var starOp = hour < 6 ? 0.75 : hour < 7 ? 0.3 : hour >= 21 ? 0.6 : 0.2;\n            var stars = [[0.07,0.12,1.1],[0.15,0.38,0.8],[0.25,0.08,1.0],[0.34,0.30,0.7],\n                         [0.47,0.14,0.9],[0.58,0.25,1.1],[0.68,0.06,0.8],[0.76,0.32,0.9],\n                         [0.85,0.16,1.0],[0.93,0.28,0.7],[0.11,0.52,0.8],[0.42,0.46,0.9],\n                         [0.63,0.50,0.7],[0.80,0.48,1.0],[0.30,0.40,0.8],[0.54,0.35,0.6]];\n            stars.forEach(function(s) {\n                var x = s[0]*W, y = s[1]*H*0.85, r = s[2];\n                out += '<circle cx=\"' + x + '\" cy=\"' + y + '\" r=\"' + r + '\" fill=\"rgba(220,225,255,' + (starOp * 0.75) + ')\"/>';\n            });\n        }\n\n        // moon\n        if (hour < 5 || hour >= 21) {\n            var moonT = hour >= 21 ? (hour - 21 + min/60) / 9 : (hour + min/60 + 3) / 9;\n            var mx = moonT * W * 0.7 + W * 0.1, my = H * 0.25, mr = 8;\n            out += '<circle cx=\"' + mx + '\" cy=\"' + my + '\" r=\"' + mr + '\" fill=\"rgba(210,220,255,0.20)\"/>';\n            out += '<circle cx=\"' + (mx+3.5) + '\" cy=\"' + (my-1) + '\" r=\"' + (mr-1.5) + '\" fill=\"' + rgb(top) + '\"/>';\n        }\n\n        // dawn/dusk glow\n        if (isDawn || (hour >= 19 && hour < 22)) {\n            var gx = isDawn ? W * 0.12 : sx;\n            var gOp = isDawn ? 0.18 : 0.22;\n            var gCol = isDawn ? '255,150,100' : '255,100,40';\n            out += '<ellipse cx=\"' + gx + '\" cy=\"' + (H*0.95) + '\" rx=\"' + (W*0.3) + '\" ry=\"' + (H*0.7) + '\" fill=\"rgba(' + gCol + ',' + gOp + ')\"/>';\n            out += '<ellipse cx=\"' + gx + '\" cy=\"' + (H*0.95) + '\" rx=\"' + (W*0.12) + '\" ry=\"' + (H*0.4) + '\" fill=\"rgba(' + gCol + ',' + (gOp*0.8) + ')\"/>';\n        }\n\n        // sun\n        if (isDay) {\n            var edgeness = 1 - Math.abs(dayT - 0.5) * 2;\n            var glowR = lerp(40, 18, edgeness);\n            var glowA = lerp(0.18, 0.07, edgeness);\n            var sunR  = lerp(7, 10, edgeness);\n            var coreA = lerp(0.55, 0.30, edgeness);\n            var ringA = lerp(0.22, 0.10, edgeness);\n            out += '<circle cx=\"' + sx + '\" cy=\"' + sy + '\" r=\"' + glowR + '\" fill=\"rgba(255,240,180,' + glowA + ')\"/>';\n            out += '<circle cx=\"' + sx + '\" cy=\"' + sy + '\" r=\"' + (sunR+4) + '\" fill=\"rgba(255,230,150,' + ringA + ')\"/>';\n            out += '<circle cx=\"' + sx + '\" cy=\"' + sy + '\" r=\"' + sunR + '\" fill=\"rgba(255,245,200,' + coreA + ')\"/>';\n            if (edgeness < 0.6) {\n                for (var a = 0; a < 8; a++) {\n                    var ang = (a / 8) * Math.PI * 2;\n                    var r1 = sunR + 5, r2 = sunR + 11;\n                    var x1 = sx + Math.cos(ang)*r1, y1 = sy + Math.sin(ang)*r1;\n                    var x2 = sx + Math.cos(ang)*r2, y2 = sy + Math.sin(ang)*r2;\n                    out += '<line x1=\"' + x1 + '\" y1=\"' + y1 + '\" x2=\"' + x2 + '\" y2=\"' + y2 + '\" stroke=\"rgba(255,220,140,0.20)\" stroke-width=\"1.2\"/>';\n                }\n            }\n        }\n\n        // clouds\n        if (hour >= 8 && hour < 18) {\n            var cloudA = Math.min(0.12, (Math.min(hour, 17) - 8) / 9 * 0.12);\n            [[0.22,0.20],[0.55,0.14],[0.78,0.32]].forEach(function(p) {\n                var cx = p[0]*W, cy = p[1]*H;\n                out += '<ellipse cx=\"' + cx + '\" cy=\"' + cy + '\" rx=\"22\" ry=\"7\" fill=\"rgba(255,255,255,' + cloudA + ')\"/>';\n                out += '<ellipse cx=\"' + (cx-8) + '\" cy=\"' + (cy-4) + '\" rx=\"12\" ry=\"6\" fill=\"rgba(255,255,255,' + (cloudA*0.7) + ')\"/>';\n                out += '<ellipse cx=\"' + (cx+9) + '\" cy=\"' + (cy-3) + '\" rx=\"10\" ry=\"5\" fill=\"rgba(255,255,255,' + (cloudA*0.6) + ')\"/>';\n            });\n        }\n\n        out += '<line x1=\"0\" y1=\"' + (H*0.88) + '\" x2=\"' + W + '\" y2=\"' + (H*0.88) + '\" stroke=\"rgba(255,255,255,0.05)\" stroke-width=\"1\"/>';\n        out += '</svg>';\n        return out;\n    }\n\n    function makeTime(time) {\n        var p = time.split(':'), hh = p[0], mm = p[1];\n        return [hh[0], hh[1], null, mm[0], mm[1]].map(function(c, i) {\n            if (c === null) return '<span class=\"rtb-colon\">:</span>';\n            var d = (0.08 + i * 0.07).toFixed(2);\n            return '<span class=\"rtb-char\" style=\"animation-delay:' + d + 's\">' + c + '</span>';\n        }).join('');\n    }\n\n    function dayPct(time) {\n        var p = time.split(':');\n        return ((parseInt(p[0],10) * 60 + parseInt(p[1],10)) / 1440 * 100).toFixed(2);\n    }\n\n    function buildHtml(time, date, weather, location) {\n        return '<div class=\"rtb\">'\n            + makeSky(time)\n            + '<div class=\"rtb-bar\" style=\"width:' + dayPct(time) + '%\"></div>'\n            + '<div class=\"rtb-inner\">'\n            + '<div class=\"rtb-left\">'\n            + '<div class=\"rtb-time\">' + makeTime(time) + '</div>'\n            + '<div class=\"rtb-date\">' + esc(date) + '</div>'\n            + '</div>'\n            + '<div class=\"rtb-right\">'\n            + '<div class=\"rtb-weather\">' + esc(weather) + '</div>'\n            + '<div class=\"rtb-location\">' + esc(location) + '</div>'\n            + '</div></div></div>';\n    }\n\n    function getLastState() {\n        var lastId = TH.getLastMessageId();\n        if (lastId < 1) return null;\n        var msgs = TH.getChatMessages('0-' + (lastId - 1));\n        if (!msgs) return null;\n        for (var i = msgs.length - 1; i >= 0; i--) {\n            if (!msgs[i].message) continue;\n            var m = msgs[i].message.match(MOD.mainRegex);\n            if (m) return { time: m[1], date: m[2] };\n        }\n        return null;\n    }\n\n    function injectPrompt(ls) {\n        var content = ls\n            ? MOD.updatePrompt.replace(/__LAST_TIME__/g, ls.time).replace(/__LAST_DATE__/g, ls.date)\n            : MOD.systemPrompt;\n        TH.injectPrompts([{ id: MOD.promptId, position: 'in_chat', depth: 0, role: 'system', content: content, should_scan: false }], { once: true });\n    }\n\n    function renderVisual(messageId) {\n        if (!TH) return;\n        if (_rendering[messageId]) return;\n        _rendering[messageId] = true;\n        setTimeout(function() { delete _rendering[messageId]; }, 800);\n\n        var msgs = TH.getChatMessages(messageId);\n        if (!msgs || !msgs.length || !msgs[0].message) return;\n        var m = msgs[0].message.match(MOD.mainRegex);\n        if (!m) return;\n\n        var $mes = TH.retrieveDisplayedMessage(messageId);\n        if (!$mes || !$mes.length) return;\n\n        var $target = $mes.find('.mes_text').length ? $mes.find('.mes_text') : $mes;\n        var lastId = TH.getLastMessageId();\n        var isExpired = (lastId - messageId) >= MOD.expirationDepth;\n\n        $target.find('.rtb').remove();\n        if (window.renriHide) window.renriHide($target);\n\n        $target.find('p, div, li').each(function() {\n            var $el = $(this);\n            if ($el.closest('.rtb').length) return;\n            if (/\\[TPBR:/i.test($el.text())) $el.remove();\n        });\n\n        var html = $target.html();\n        html = window.renriCleanEmpty ? window.renriCleanEmpty(html) : html;\n        $target.html(html.trim());\n\n        if (!isExpired) {\n            $target.prepend(buildHtml(m[1], m[2], m[3].trim(), m[4].trim()));\n        }\n    }\n\n    function initializeScript() {\n        if (!window.TavernHelper || !window.tavern_events || typeof window.eventOn !== 'function') {\n            setTimeout(initializeScript, 200);\n            return;\n        }\n        TH = window.TavernHelper;\n        var ev = window.tavern_events;\n\n        if (!$('#' + MOD.cssId).length) $('head').append('<style id=\"' + MOD.cssId + '\">' + MOD.css + '</style>');\n\n        window.eventOn(ev.CHARACTER_MESSAGE_RENDERED, renderVisual);\n        window.eventOn(ev.USER_MESSAGE_RENDERED, renderVisual);\n        window.eventOn(ev.MESSAGE_EDITED, function(id) { setTimeout(function(){ renderVisual(id); }, 100); });\n        window.eventOn(ev.MESSAGE_UPDATED, function(id) { setTimeout(function(){ renderVisual(id); }, 100); });\n        window.eventOn(ev.MESSAGE_SWIPED, function(id) { setTimeout(function(){ renderVisual(id); }, 100); });\n\n        var scan = function() {\n            $('.mes').each(function() {\n                var id = $(this).attr('mesid');\n                if (id) renderVisual(Number(id));\n            });\n        };\n        scan();\n\n        window.eventOn(ev.CHAT_CHANGED, function(){ setTimeout(scan, 500); });\n        window.eventOn(ev.MORE_MESSAGES_LOADED, scan);\n        window.eventOn(ev.GENERATION_AFTER_COMMANDS, function(){ injectPrompt(getLastState()); });\n        window.eventOn(ev.GENERATION_ENDED, function(){ setTimeout(function(){ var $l=$('.mes').last(); var id=$l.attr('mesid'); if(id) renderVisual(Number(id)); }, 400); });\n        window.eventOn(ev.GENERATION_STOPPED, function(){ setTimeout(function(){ var $l=$('.mes').last(); var id=$l.attr('mesid'); if(id) renderVisual(Number(id)); }, 400); });\n\n        console.log('[RenriTopbar] init OK — sky edition');\n    }\n\n    $(document).ready(initializeScript);\n})();",
  "info": "Topbar: sky scene widget — time-of-day gradient, sun/moon arc, stars",
  "button": {
    "enabled": false,
    "buttons": []
  },
  "data": {}
}