You are adding a temporary announcement banner to my Super website (super.so) through the Super MCP server (https://mcp.super.so/mcp).
What to build: a dark pill fixed at the bottom centre of the screen with an icon, one line of text, a button to a page and a close (x). It must not be inserted into the page content, must survive page-to-page navigation, must remember per visitor when it was closed (localStorage), and must stop showing at an end date by itself.
My banner:
- Site: {{domain}}
- Text: {{desktop text, max ~60 characters}} / phone: {{max ~30 characters}}
- Button: {{label}} -> {{URL}}
- Pages: {{every page | list of paths}}, except the linked page itself
- Ends: {{date}} (convert to UTC ISO)
Steps:
1. find_site with my domain; stop if the site is on the Free plan (custom code is stored but not shown there).
2. get_code slot body; keep everything that is there.
3. patch_code slot body, find "" (append), with ONE block between <!--ANNOUNCEMENT BANNER <id>--> and <!--END ANNOUNCEMENT BANNER <id>-->: a <script> that runs on load (or at once if document.readyState is complete), appends the pill to document.body (never to article.notion-root), re-checks the path on the routeChangeComplete event and popstate, and does nothing after the end date. Give the pill width:max-content and max-width:calc(100vw - 32px) - a fixed element with left:50% otherwise shrinks to half the screen and cuts the text. On phones lift it to bottom:76px if the site shows the "Made with Super" badge.
4. Check the live site: curl the page with a ?x=random query until it contains the marker, then preview_site source "live" with waitFor ".sb-pill" at width 1280 and 390, then with clickPath on the link and on the close button.
5. Tell me what the script does and that restore_code undoes it. To remove it later, patch_code the whole marked block with an empty replace.
---
name: super-announcement-banner
description: Adds a temporary announcement banner to a Super (super.so) website through the Super MCP server - a floating pill at the bottom of the screen with a short message, a button to a page and a close button, shown on chosen pages or the whole site and switched off automatically at an end date. Writes it as one marked block in the site's custom code, checks it on the live site on desktop and phone, and removes it on request. Use when someone wants to announce something on their Super site for a limited time (a sale, an event, a new post, a status or security notice, a launch), wants a site-wide notice bar or toast, or wants to remove or change such a banner. Do not use for permanent navigation or footer changes, for cookie consent, for content that belongs in Notion, or for sites on the Free plan (custom code does not render there).
---
# Announcement banner for a Super site
The banner is a small piece of custom code: a dark pill fixed at the bottom centre of the screen, with an icon, one line of text, a button and a close (×). It never touches the page content, works with Super's page-to-page navigation, remembers when a visitor closed it, and stops showing itself at the end date even if nobody removes the code.
~~~
"Security update: Super is not affected by PHP2Shell [Read →] ×"
~~~
## Step 1 - Gather the facts
Ask only for what you cannot look up:
| Fact | How to get it |
|---|---|
| The site | Super MCP `find_site` with the domain, or `list_sites` |
| Plan | `get_site` / `find_site` (`tier`). Custom code renders on Personal and higher; on Free stop and say so |
| Text | Ask. Desktop text up to about 60 characters, phone text up to about 30. Suggest both |
| Link and button label | Ask. An absolute URL when the banner runs on more than one site |
| Where | Every page (`["*"]`) or a list of paths; a path also covers its subpages. Exclude the linked page itself |
| Until when | Ask for a date and convert it to UTC ISO (`2026-10-25T00:00:00Z`). Default: three days from now, midnight UTC |
| Colours | Default black pill with an amber button. To match the site, read `describe_design` and set the CSS variables in Step 3 |
Check whether the site shows the **Made with Super** badge (bottom left; `preview_site` at width 390 shows it). With the badge, phones need `BOTTOM_MOBILE` 76 so the pill sits above it; without it, 16.
## Step 2 - Fill in the template
Take `references/banner.html` and replace every `{{…}}`:
- `{{ID}}` - short id, e.g. `php2shell-2026-10`. Also the key under which a visitor's "closed" is remembered, so a new announcement needs a new id.
- `{{LABEL}}` - a few words for the marker comment.
- `{{END_ISO}}`, `{{LINK_URL}}`, `{{TEXT}}`, `{{SHORT_TEXT}}`, `{{CTA}}`.
- `{{PATHS}}` and `{{EXCLUDE}}` - JSON arrays, e.g. `["*"]` and `["/blog/my-post"]`.
- `{{BOTTOM_MOBILE}}` - `76` or `16`.
- `{{ICON_SVG_PATHS}}` - the inner `<path>` elements of a Lucide icon (https://lucide.dev, e.g. `shield-check`, `megaphone`, `calendar`, `sparkles`, `tag`). No emoji.
Escape `"` and `\` in the texts. No `{{` may remain.
## Step 3 - Write it to the site
1. `get_code` with `slot: "body"` and read what is there. Keep everything; if a block with the same `ANNOUNCEMENT BANNER <id>` marker exists, replace that block instead of adding a second one.
2. `patch_code` with `slot: "body"`, `find: ""` (append) and the filled block as `replace`. Use the site's body slot even for one page: the `PATHS` list decides where it shows, and the database pages of a site usually have no page-level code slot.
3. To match the site's colours, add one rule to the site's CSS (style slot or the stylesheet it links): `.sb-pill{--sb-bg:#…;--sb-fg:#…;--sb-accent:#…;--sb-accent-fg:#…}`.
Tell the user what the script does and that `restore_code` brings the previous version of the code back.
## Step 4 - Check it live
Code is live as soon as it is saved, but the CDN can serve the old HTML for a minute or two.
1. Poll the live page with a cache-busting query until it contains the marker: `curl -s "https://<domain>/<path>?x=$RANDOM" | grep -c "ANNOUNCEMENT BANNER <id>"`.
2. `preview_site` with `source: "live"`, a cache-busting query in `path` (e.g. `/pricing?v=1`), `waitFor: ".sb-pill"`, once at width 1280 and once at 390. The pill must be fully visible, the phone text not cut off, not covering the badge.
3. `preview_site` with `clickPath: [".sb-pill a"]` - the final URL must be the link. With `clickPath: [".sb-pill button"]` the pill must be gone.
4. One page that should not show it (an excluded path, or any page when the list is narrow): `waitFor: ".sb-pill"` must report it did not appear.
## Changing or removing it
- Change text, dates or pages: `patch_code` on the exact line inside the block (`var TEXT = …`, `var PATHS = …`). A new announcement gets a new `ID`.
- Remove it: `get_code` the body slot and `patch_code` the whole block, from `<!--ANNOUNCEMENT BANNER <id>` to `<!--END ANNOUNCEMENT BANNER <id>-->`, with an empty `replace`. After the end date the code does nothing, but it still belongs out of the site; offer to remove it then.
- Several sites (e.g. the main site and the docs): one block per site with an absolute `LINK_URL`. A visitor's close is remembered per domain.
## Pitfalls
- **Free plan:** the code is stored but not rendered. Do not promise a banner there.
- **The dashboard preview** runs under `/site/<id>/preview?path=…`; the template reads the page path from the query, so `preview_site` without `source: "live"` works too, but only the live check proves the published site serves it.
- **A fixed element with `left:50%`** shrinks to half the screen and cuts the text; the template's `width:max-content` prevents that. Keep it if you restyle.
- **Do not insert the banner into the article** (`article.notion-root`): it then looks like part of the page content. Append it to `document.body`.
- **Do not move or delete nodes Super renders**; add your own only.
- The dashboard **Code page saves all three slots** from when it was opened. If someone has it open and clicks Save, the banner disappears; `list_code_history` and `restore_code` bring it back.
<!--ANNOUNCEMENT BANNER {{ID}}: {{LABEL}}. Ends {{END_ISO}}; delete this block afterwards.-->
<script>
(function () {
var ID = "{{ID}}"; // short unique id, a-z 0-9 -
var END = Date.parse("{{END_ISO}}"); // e.g. "2026-10-25T00:00:00Z"; the banner disappears after this moment
var LINK = "{{LINK_URL}}"; // where the button goes
var TEXT = "{{TEXT}}"; // desktop text, about 60 characters at most
var SHORT = "{{SHORT_TEXT}}"; // phone text, about 30 characters at most
var CTA = "{{CTA}}"; // button label, e.g. "Read"
var PATHS = {{PATHS}}; // ["*"] for every page, or ["/pricing", "/blog"]; a path also covers its subpages
var EXCLUDE = {{EXCLUDE}}; // paths that never show it, e.g. the linked page itself
var BOTTOM_MOBILE = {{BOTTOM_MOBILE}}; // px from the bottom on phones; 76 clears the "Made with Super" badge, 16 without it
if (Date.now() >= END) return;
var KEY = "super-banner-closed-" + ID;
function currentPath() {
// The dashboard preview runs at /site/<id>/preview?path=<page>; read the page path from there.
var p = location.pathname;
if (/\/preview$/.test(p)) p = "/" + (new URLSearchParams(location.search).get("path") || "");
return p.replace(/\/$/, "") || "/";
}
function matches(list, p) {
return list.some(function (x) { return x === "*" || p === x || p.indexOf(x + "/") === 0; });
}
function closed() { try { return localStorage.getItem(KEY) === "1"; } catch (e) { return false; } }
function wanted() { var p = currentPath(); return matches(PATHS, p) && !matches(EXCLUDE, p) && !closed(); }
function addStyle() {
if (document.getElementById(ID + "-css")) return;
var s = document.createElement("style");
s.id = ID + "-css";
s.textContent =
".sb-pill{position:fixed;left:50%;bottom:24px;z-index:60;transform:translateX(-50%);display:flex;align-items:center;gap:4px;width:max-content;max-width:calc(100vw - 32px);box-sizing:border-box;padding:6px 6px 6px 14px;border-radius:999px;background:var(--sb-bg,#111);color:var(--sb-fg,#fff);font-size:14px;line-height:1.3;box-shadow:0 10px 30px rgba(0,0,0,.25);animation:sb-in .35s ease-out}" +
".sb-pill a{display:flex;align-items:center;gap:8px;color:inherit!important;text-decoration:none!important;min-width:0}" +
".sb-pill svg{width:16px;height:16px;flex:none}" +
".sb-pill__text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}" +
".sb-pill__short{display:none}" +
".sb-pill__cta{flex:none;margin-left:4px;padding:5px 12px;border-radius:999px;background:var(--sb-accent,#FFC34D);color:var(--sb-accent-fg,#111);font-weight:600}" +
".sb-pill a:hover .sb-pill__cta{filter:brightness(1.08)}" +
".sb-pill button{flex:none;width:28px;height:28px;border:0;border-radius:50%;background:transparent;color:inherit;opacity:.6;font-size:18px;line-height:1;cursor:pointer}" +
".sb-pill button:hover{opacity:1}" +
"@keyframes sb-in{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}" +
"@media (prefers-reduced-motion:reduce){.sb-pill{animation:none}}" +
"@media (max-width:640px){.sb-pill{bottom:" + BOTTOM_MOBILE + "px;font-size:13px;padding-left:12px}.sb-pill__long{display:none}.sb-pill__short{display:inline}.sb-pill__cta{padding:5px 10px}.sb-pill button{width:24px}}";
document.head.appendChild(s);
}
function mount() {
var pill = document.getElementById(ID);
if (Date.now() >= END || !wanted()) { if (pill) pill.remove(); return; }
if (pill) return;
addStyle();
pill = document.createElement("div");
pill.id = ID;
pill.className = "sb-pill";
pill.setAttribute("role", "status");
var a = document.createElement("a");
a.href = LINK;
a.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">{{ICON_SVG_PATHS}}</svg>';
[["sb-pill__text sb-pill__long", TEXT], ["sb-pill__text sb-pill__short", SHORT], ["sb-pill__cta", CTA + " →"]].forEach(function (x) {
var span = document.createElement("span");
span.className = x[0];
span.textContent = x[1];
a.appendChild(span);
});
var close = document.createElement("button");
close.type = "button";
close.setAttribute("aria-label", "Close");
close.textContent = "×";
close.addEventListener("click", function () {
try { localStorage.setItem(KEY, "1"); } catch (e) {}
pill.remove();
});
pill.appendChild(a);
pill.appendChild(close);
document.body.appendChild(pill); // outside .super-root, so it survives page-to-page navigation
}
function start() {
mount();
document.addEventListener("routeChangeComplete", function () { requestAnimationFrame(mount); });
window.addEventListener("popstate", function () { requestAnimationFrame(mount); });
}
if (document.readyState === "complete") start();
else window.addEventListener("load", start, { once: true });
})();
</script>
<!--END ANNOUNCEMENT BANNER {{ID}}-->