Drop the "You are reading content from Scuttlebutt" banner

Upstream ssb-viewer printed it above every page, aimed at people who had
arrived at a stray scuttlebutt message and needed context. On cust.ooo the
context is the site itself, so it was explaining the wrong thing.

Removes the function, all four call sites and the now-dead .top-tip CSS.

Adds a "Changing how pages look" section to the README, since the real
question was where this lives. There is no template directory - pages are
built as strings in render.js with one <style> block - so the answer is
"grep for the text you can see", and the table maps each visible piece to the
function that emits it. It also records the two things that will bite an
editor: hyperscript silently drops attributes it mistakes for DOM properties
(why loading="lazy" uses setAttribute), and only text passed through h() is
escaped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192zBTNZKZn5svyJ5HTnYds
This commit is contained in:
2026-08-22 20:39:11 -04:00
co-authored by Claude Opus 5
parent fc9539a618
commit 09798f7146
2 changed files with 64 additions and 20 deletions
+46
View File
@@ -42,6 +42,52 @@ Two things to know before writing a query against this:
Messages are published by the kiosks (`/home/trav/custodisco-kiosk/ssb-post.sh`), not by Messages are published by the kiosks (`/home/trav/custodisco-kiosk/ssb-post.sh`), not by
this viewer. The viewer is read-only. this viewer. The viewer is read-only.
## Changing how pages look
Everything visual is in **`render.js`**. There is no template directory and no CSS
files to hunt through — pages are built as strings and there is exactly one `<style>`
block, near the middle of the file.
Find things by searching for the text you can see on the page. `grep -n` on a phrase
from the rendered page lands you on the line that produces it:
```bash
grep -n "Join Scuttlebutt" render.js
```
The pieces, in the order you meet them on a page:
| What you see | Where |
|---|---|
| `<title>` and the whole `<head>` | `wrapPage()` |
| "custo items", the count, the search box | `itemsHeader()` |
| an item card: photo, caption, kiosk, time | `renderItemCard()` |
| "older items" / "show everything" | `itemsFooter()` |
| the "Join Scuttlebutt now" button | `callToAction()` |
| licence, repo link, deployed commit | the `footer` constant |
| all colours, spacing, the grid itself | the `styles` template string |
The grid rules are the `.item-*` selectors in `styles`. Card width is one number —
`minmax(190px, 1fr)` in `main.item-grid` — raise it for fewer, larger cards.
Two gotchas worth knowing before you edit:
- **Attributes set through `h()` can vanish.** hyperscript assigns anything that looks
like a DOM property rather than an attribute, and `outerHTML` then drops it. That is
why `loading="lazy"` is applied with `media.setAttribute(...)` instead. If an
attribute you added does not appear in the output, this is why.
- **Text passed to `h()` is escaped; strings concatenated into `wrap()` are not.**
Anything derived from a message must go through `h()`.
To see a change: edit, then from the ops repo
```bash
bin/deploy-viewer.sh --yes
```
which syncs, restarts the viewer and checks `/items` answers before it returns. Commit
and push separately — the deploy only moves files.
## Running it ## Running it
`bin.js` connects to a local `ssb-server` and serves on `conf.viewer.port` (8807). `bin.js` connects to a local `ssb-server` and serves on `conf.viewer.port` (8807).
+18 -20
View File
@@ -120,24 +120,31 @@ function wrap(before, after) {
} }
} }
// ---------------------------------------------------------------------------
// Page furniture. These are the bits of chrome wrapped around every page, and
// they are the things most likely to want changing.
//
// callToAction() the "Join Scuttlebutt now" button at the foot of a page
// footer just below it: licence, repo name, deployed commit
// styles the single <style> block; .item-* rules are the grid
// itemsHeader() the /items title, count and search box
//
// Removed already: toolTipTop(), which printed "You are reading content from
// Scuttlebutt" above every page. It is in the git history if it is ever wanted
// back.
// ---------------------------------------------------------------------------
function callToAction() { function callToAction() {
return h('a.call-to-action', return h('a.call-to-action',
{ href: 'https://www.scuttlebutt.nz' }, { href: 'https://www.scuttlebutt.nz' },
'Join Scuttlebutt now').outerHTML 'Join Scuttlebutt now').outerHTML
} }
function toolTipTop() {
return h('span.top-tip',
'You are reading content from ',
h('a', { href: 'https://www.scuttlebutt.nz' },
'Scuttlebutt')).outerHTML
}
function renderAbout(opts, about, showAllHTML = "") { function renderAbout(opts, about, showAllHTML = "") {
if (about.publicWebHosting === false || (about.publicWebHosting == null && opts.requireOptIn)) { if (about.publicWebHosting === false || (about.publicWebHosting == null && opts.requireOptIn)) {
return pull( return pull(
pull.map(renderMsg.bind(this, opts, '')), pull.map(renderMsg.bind(this, opts, '')),
wrap(toolTipTop() + '<main>', '</main>' + callToAction()) wrap('<main>', '</main>' + callToAction())
) )
} }
@@ -145,7 +152,7 @@ function renderAbout(opts, about, showAllHTML = "") {
figCaption.innerHTML = 'Feed of ' + escape(about.name) + '<br>' + marked(String(about.description || ''), opts.marked) figCaption.innerHTML = 'Feed of ' + escape(about.name) + '<br>' + marked(String(about.description || ''), opts.marked)
return pull( return pull(
pull.map(renderMsg.bind(this, opts, '')), pull.map(renderMsg.bind(this, opts, '')),
wrap(toolTipTop() + '<main>' + wrap('<main>' +
h('article', h('article',
h('header', h('header',
h('figure', h('figure',
@@ -162,7 +169,7 @@ function renderAbout(opts, about, showAllHTML = "") {
function renderThread(opts, id, showAllHTML = "") { function renderThread(opts, id, showAllHTML = "") {
return pull( return pull(
pull.map(renderMsg.bind(this, opts, id)), pull.map(renderMsg.bind(this, opts, id)),
wrap(toolTipTop() + '<main>', wrap('<main>',
showAllHTML + '</main>' + callToAction()) showAllHTML + '</main>' + callToAction())
) )
} }
@@ -239,15 +246,6 @@ var styles = `
} }
a { color: #364fc7; } a { color: #364fc7; }
.top-tip, .top-tip a {
color: #868e96;
}
.top-tip {
text-align: center;
display: block;
margin-bottom: 10px;
font-size: 14px;
}
main { margin: 0 auto; max-width: 40rem; } main { margin: 0 auto; max-width: 40rem; }
main article:first-child { border-radius: 3px 3px 0 0; } main article:first-child { border-radius: 3px 3px 0 0; }
main article:last-child { border-radius: 0 0 3px 3px; } main article:last-child { border-radius: 0 0 3px 3px; }
@@ -717,7 +715,7 @@ function renderShowAll(showAll, url) {
function renderItemGrid(opts, meta) { function renderItemGrid(opts, meta) {
return pull( return pull(
pull.map(renderItemCard.bind(this, opts)), pull.map(renderItemCard.bind(this, opts)),
wrap(toolTipTop() + itemsHeader(opts, meta) + '<main class="item-grid">', wrap(itemsHeader(opts, meta) + '<main class="item-grid">',
'</main>' + itemsFooter(opts, meta) + callToAction()) '</main>' + itemsFooter(opts, meta) + callToAction())
) )
} }