Files
VoxVera/site/nostr-client.css
T
thePR0M3TH3ANandClaude Opus 4.8 c97624a5e0 Board "My flyers" filter + author display names; editor wrong-PIN lockout
Three quick-ish wins:

- "My flyers" board filter: a checkbox that shows only the connected user's own
  flyers (overrides the trust filter, since your own may not be in your follow
  graph). New mine_only string × 14.
- Author display names: the board fetches authors' kind-0 metadata and shows
  the display name (falling back to the npub) instead of a bare key. Names are
  untrusted — escaped at render and length-capped. (NIP-05 verification is still
  future.)
- Wrong-PIN lockout: after 3 bad PINs the editor refuses to unlock a remembered
  nsec for a 30s cooldown, persisted across reloads so it isn't reset by a
  refresh. A deterrent against casual on-device guessing (a leaked blob is still
  offline-brute-forceable). New too_many_attempts string × 14.

Adds e2e for each; the i18n guard enforces the new strings in all 14 languages.
294 e2e across all six engines; roadmap updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 10:20:16 -04:00

1628 lines
28 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
color-scheme: light;
--ink: #111;
--muted: #5b5b5b;
--line: #c9c9c9;
--soft: #f3f3f1;
--accent: #b80000;
--ok: #1d6b3a;
--warn: #8a4d00;
/* Printable sheet dimensions. Switched by the paper-* class on <html>
(applyPaperSize in nostr-client.js). The tear-off column (3.75in) and QR
codes (1.25in) stay fixed physical sizes; the content column flexes. */
--sheet-w: 8.5in;
--sheet-h: 11in;
}
html.paper-letter {
--sheet-w: 8.5in;
--sheet-h: 11in;
}
html.paper-a4 {
--sheet-w: 210mm;
--sheet-h: 297mm;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--ink);
background: #fff;
font-family: "Courier New", Courier, monospace;
line-height: 1.45;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
min-height: 40px;
padding: 8px 14px;
border: 1px solid #000;
background: #000;
color: #fff;
cursor: pointer;
}
button:hover {
background: #333;
}
.secondary-button {
background: #fff;
color: #000;
}
.secondary-button:hover {
background: #eee;
}
.topbar {
display: flex;
gap: 24px;
justify-content: space-between;
align-items: flex-end;
padding: 20px clamp(16px, 4vw, 42px);
border-bottom: 2px solid #000;
}
.topbar h1,
.topbar p,
.section-title h2,
.preview-toolbar h2 {
margin: 0;
}
.topbar h1 {
font-size: 24px;
}
.topbar p {
color: var(--muted);
}
.mode-tabs {
display: inline-flex;
border: 1px solid #000;
}
.topbar-controls {
display: flex;
gap: 10px;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
}
.topbar-controls select {
width: auto;
min-height: 40px;
max-width: 220px;
}
/* Wider so the full paper size (e.g. "US Letter (8.5 × 11 in)") is readable. */
#topbar-paper,
#viewer-paper {
min-width: 210px;
max-width: 260px;
}
.tab {
border: 0;
border-right: 1px solid #000;
background: #fff;
color: #000;
}
.tab:last-child {
border-right: 0;
}
.tab.active {
background: #000;
color: #fff;
}
main {
padding: 18px clamp(16px, 4vw, 42px) 36px;
}
.panel {
display: none;
}
.panel.active {
display: block;
}
.viewer-floating {
display: none;
}
.workspace {
display: grid;
grid-template-columns: minmax(320px, 1.15fr) minmax(280px, 0.85fr);
gap: 24px;
align-items: start;
}
.editor-form,
.result-panel {
min-width: 0;
}
.section-title,
.preview-toolbar {
display: flex;
gap: 16px;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.state-pill {
display: inline-block;
padding: 4px 8px;
border: 1px solid var(--line);
color: var(--muted);
font-size: 12px;
}
.identity-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
margin: 0 0 12px;
}
.identity-row .secondary-button {
min-height: 32px;
padding: 5px 10px;
font-size: 12px;
}
/* Identity mode controls (anonymous / NIP-07 / imported nsec). */
.identity-controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0 0 10px;
}
.identity-controls .secondary-button {
min-height: 32px;
padding: 5px 10px;
font-size: 12px;
}
/* Vertical stack so the input, the "remember" checkbox, the PIN field, and the
action button each get their own line (a wrapping row collided the label and
button). */
/* The `hidden` attribute must win over the display rule below (otherwise the
nsec-import box and the locked-identity row show on every load). */
.identity-import[hidden] {
display: none;
}
.identity-import {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
margin: 0 0 12px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--soft);
}
.identity-import input[type="password"] {
width: 100%;
box-sizing: border-box;
font: inherit;
padding: 6px 8px;
border: 1px solid var(--line);
border-radius: 6px;
}
.identity-import .secondary-button {
align-self: flex-start;
min-height: 32px;
padding: 5px 12px;
font-size: 12px;
}
.identity-import .nsec-remember-row {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--muted);
}
.identity-import .nsec-remember-row input {
width: auto;
flex: 0 0 auto;
margin: 0;
}
/* The locked row keeps the note on its own line, then PIN + buttons below. */
#identity-locked-row {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
}
#identity-locked-note {
font-size: 12px;
color: var(--muted);
flex: 1 1 100%;
}
#identity-locked-row input[type="password"] {
width: auto;
flex: 1 1 160px;
}
.editor-form label {
display: grid;
gap: 5px;
margin-bottom: 12px;
font-size: 13px;
font-weight: 700;
}
input,
select,
textarea {
width: 100%;
border: 1px solid #000;
border-radius: 0;
background: #fff;
color: #000;
padding: 8px;
}
textarea {
resize: vertical;
}
.field-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) 120px;
gap: 12px;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 14px;
}
.result-list {
display: grid;
grid-template-columns: 90px minmax(0, 1fr);
gap: 8px 12px;
margin: 0 0 12px;
}
.result-list dt {
font-weight: 700;
}
.result-list dd {
margin: 0;
min-width: 0;
}
.copy-value {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
align-items: start;
}
.copy-button {
min-height: 28px;
padding: 4px 8px;
font-size: 11px;
}
.relay-results {
display: grid;
gap: 4px;
}
.relay-ok {
color: var(--ok);
font-weight: 700;
}
.relay-error {
color: var(--warn);
}
code {
display: block;
overflow-wrap: anywhere;
white-space: normal;
}
.status-line {
min-height: 22px;
color: var(--muted);
}
.field-error {
margin: 0;
color: var(--warn);
font-size: 12px;
font-weight: 700;
}
.preview-band {
margin-top: 26px;
border-top: 2px solid #000;
padding-top: 18px;
}
.flyer-preview {
overflow-x: auto;
padding-bottom: 8px;
}
.flyer-sheet {
display: flex;
width: var(--sheet-w);
height: var(--sheet-h);
border: 1px solid #000;
background: #fff;
color: #000;
overflow: hidden;
}
.flyer-sheet.no-tear-offs .tear-offs {
display: none;
}
.flyer-sheet.no-tear-offs .flyer-content {
width: 100%;
}
.tear-offs {
width: 3.75in;
padding: 8px;
border-right: 1px dashed #000;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 5px;
}
.tear-off {
display: flex;
gap: 12px;
height: 1in;
padding: 5px;
border: 1px dashed #000;
font-size: 10px;
overflow: hidden;
}
.qr-box {
flex: 0 0 0.72in;
width: 0.72in;
height: 0.72in;
border: 1px solid #000;
display: grid;
place-items: center;
font-size: 9px;
text-align: center;
}
.flyer-content {
width: 4.75in;
padding: 0.28in;
display: flex;
flex-direction: column;
justify-content: flex-start;
text-align: center;
}
.flyer-content h1 {
margin: 0 0 0.18in;
font-size: 34px;
line-height: 1.05;
}
.flyer-content .subtitle {
margin-bottom: 0.16in;
color: var(--accent);
font-weight: 700;
font-size: 21px;
}
.message {
margin-top: 0.12in;
text-align: left;
font-size: 15px;
white-space: pre-wrap;
}
.main-qr {
align-self: center;
margin-top: 0.18in;
width: 1.25in;
height: 1.25in;
}
.footer {
margin-top: auto;
font-size: 11px;
overflow-wrap: anywhere;
}
a {
color: var(--accent);
}
.container {
display: flex;
flex-direction: row;
width: var(--sheet-w);
height: var(--sheet-h);
border: 1px solid #000;
background: #fff;
color: #000;
box-sizing: border-box;
overflow: hidden;
page-break-inside: avoid;
/* Per-script letter-spacing, set by the ls-* policy class (see
LETTER_SPACING_POLICY in nostr-client.js). Defaults below match the
historical design and act as a safe fallback if no policy class is set. */
--ls-display: 2px;
--ls-subtitle: 6px;
--ls-body: 0;
--ls-url: 0;
--ls-footer: 0;
}
/* Latin, Cyrillic, Hebrew: tighten tracking to reclaim line space. */
.container.ls-full {
--ls-display: 0.5px;
--ls-subtitle: 2px;
--ls-body: -0.3px;
--ls-url: -0.3px;
--ls-footer: 0;
}
/* CJK: tracking is safe but must never go negative (glyphs would overlap). */
.container.ls-cjk {
--ls-display: 0;
--ls-subtitle: 0;
--ls-body: 0;
--ls-url: 0;
--ls-footer: 0;
}
/* Arabic, Persian, Hindi: letter-spacing breaks shaping — turn it off. */
.container.ls-none {
--ls-display: normal;
--ls-subtitle: normal;
--ls-body: normal;
--ls-url: normal;
--ls-footer: normal;
}
.container.no-tear-offs .left-tear-offs {
display: none;
}
.container.no-tear-offs .content {
width: 100%;
}
.left-tear-offs {
width: 3.75in;
padding: 8px;
border-right: 1px dashed #000;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 5px;
box-sizing: border-box;
}
.tear-off-text {
flex: 1;
min-width: 0;
padding-top: 1px;
overflow-wrap: anywhere;
word-break: normal;
hyphens: none;
display: flex;
flex-direction: column;
}
/* Tear-off title labels each tab. Its font-size is set by fitTabTitles() in JS
to be as large as fits; this caps the height so a long title shrinks (and at
worst clips) instead of pushing the link/event-id out of the 1in tab. */
.tear-off-title {
font-weight: 700;
line-height: 1.05;
text-align: center;
letter-spacing: 0;
max-height: 0.42in;
overflow: hidden;
margin-bottom: 2px;
}
/* The clickable action line under the title (also clickable in a printed PDF). */
.tear-off-link {
display: block;
font-size: 10px;
line-height: 1.15;
text-align: center;
}
.tear-off-event-id {
margin-top: auto;
padding-top: 4px;
font-size: 8px;
line-height: 1.2;
word-break: break-all;
overflow-wrap: anywhere;
}
.tear-off-event-id-value {
display: block;
font-family: "Courier New", monospace;
font-size: 12px;
line-height: 1.1;
letter-spacing: -0.2px;
word-break: break-all;
}
.qr-code {
width: 80px;
height: 80px;
flex-shrink: 0;
}
.qr-code svg {
display: block;
width: 100%;
height: 100%;
}
.qr-error {
display: grid;
width: 100%;
height: 100%;
place-items: center;
border: 1px solid #000;
font-size: 9px;
text-align: center;
}
.qr-placeholder {
width: 80px;
height: 80px;
flex-shrink: 0;
background:
linear-gradient(90deg, #000 8px, transparent 8px 16px, #000 16px 24px, transparent 24px 32px),
linear-gradient(#000 8px, transparent 8px 16px, #000 16px 24px, transparent 24px 32px);
background-color: #fff;
background-size: 32px 32px;
border: 8px solid #000;
}
.content {
width: calc(var(--sheet-w) - 3.75in);
padding: 15px 20px;
text-align: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: flex-start;
page-break-inside: avoid;
overflow-wrap: break-word;
}
.flyer-status-message {
margin: auto;
padding: 0 10px;
font-size: 20px;
font-weight: 700;
line-height: 1.3;
text-align: center;
letter-spacing: var(--ls-display);
}
.content h1 {
margin: 0;
padding: 0;
font-size: 24px;
line-height: 1.2;
letter-spacing: var(--ls-display);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.distribute {
font-size: 18px;
line-height: 1.2;
letter-spacing: var(--ls-subtitle);
margin: 2px 0;
padding: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.container .message {
flex: 1;
margin-top: 4px;
font-size: 14px;
line-height: 1.4;
letter-spacing: var(--ls-body);
text-align: left;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
white-space: pre-wrap;
box-sizing: border-box;
position: relative;
}
.qr-code-body {
width: 100%;
height: auto;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
margin-top: 10px;
padding: 0;
box-sizing: border-box;
}
.qr-code-body .main-qr {
width: 80px;
height: 80px;
}
.qr-code-url {
font-size: 12px;
letter-spacing: var(--ls-url);
text-align: left;
overflow-wrap: anywhere;
word-break: normal;
hyphens: none;
flex: 1 1 auto;
min-width: 0;
padding-left: 4px;
}
hr {
border: none;
border-top: 1px solid #000;
width: 100%;
margin: 4px 0;
}
.container .footer {
font-size: 10px;
overflow-wrap: break-word;
text-align: center;
align-self: center;
width: 100%;
padding: 10px 0;
box-sizing: border-box;
border-top: 1px solid #000;
}
.container .footer p {
margin: 0;
padding: 0;
}
.container .footer .binary {
margin-bottom: 8px;
margin-top: 10px;
letter-spacing: var(--ls-footer);
}
.container .footer .credit {
font-size: 12px;
margin-top: 0;
}
/* Screen-only link to the bulletin board, shown below the flyer preview
(outside the printed sheet). Hidden in print (see @media print). */
.preview-band > .board-link {
text-align: center;
font-size: 13px;
margin: 12px 0;
}
.board-link a {
color: var(--accent);
}
body.mode-viewer {
background: #fff;
}
body.mode-viewer .topbar,
body.mode-viewer #editor-panel,
body.mode-viewer .preview-toolbar {
display: none;
}
body.mode-viewer main {
padding: 0;
}
body.mode-viewer .preview-band {
margin: 0;
padding: 0;
border: 0;
}
body.mode-viewer .flyer-preview {
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding: 0 0 20px;
overflow: auto;
}
body.mode-viewer .viewer-floating {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
width: 100%;
padding: 12px;
background: #fff;
}
body.mode-viewer .viewer-floating button {
min-height: 34px;
padding: 6px 10px;
font-size: 12px;
}
body.mode-viewer .viewer-floating select {
width: auto;
min-height: 34px;
max-width: 180px;
padding: 6px 8px;
font-size: 12px;
background: #fff;
}
body.mode-viewer #viewer-panel {
display: none;
}
body.mode-viewer #viewer-panel.drawer-open {
position: fixed;
top: 56px;
right: 12px;
bottom: 12px;
z-index: 19;
display: block;
width: min(680px, calc(100vw - 24px));
overflow: auto;
padding: 16px;
border: 2px solid #000;
background: #fff;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
body.mode-viewer #viewer-panel.drawer-open .workspace {
display: block;
}
body.mode-viewer #viewer-panel.drawer-open .result-panel {
margin-top: 18px;
}
@media (max-width: 900px) {
body {
font-size: 16px;
}
.topbar {
display: block;
padding: 14px;
}
.topbar h1 {
font-size: 20px;
}
.topbar p {
margin-top: 4px;
font-size: 13px;
}
.topbar-controls {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 8px;
margin-top: 12px;
}
.topbar-controls select {
width: 100%;
max-width: none;
}
.mode-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
width: 100%;
margin-top: 0;
}
.tab {
min-height: 44px;
}
main {
padding: 12px;
}
.workspace,
.field-grid {
display: block;
}
.section-title,
.preview-toolbar {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 8px;
}
.identity-row {
align-items: stretch;
}
.identity-row .state-pill,
.identity-row .secondary-button {
width: 100%;
}
input,
select,
textarea {
min-height: 44px;
padding: 10px;
font-size: 16px;
}
textarea {
min-height: 120px;
}
.actions {
display: grid;
grid-template-columns: minmax(0, 1fr);
}
.actions button,
.preview-toolbar button {
width: 100%;
min-height: 44px;
}
.result-panel {
margin-top: 18px;
}
.result-list {
display: block;
}
.result-list dt {
margin-top: 10px;
}
.copy-value {
grid-template-columns: minmax(0, 1fr);
}
.copy-button {
width: 100%;
min-height: 36px;
}
.preview-band {
margin-top: 18px;
padding-top: 12px;
}
.flyer-preview {
--mobile-flyer-scale: var(--flyer-preview-scale, 0.58);
width: 100%;
min-height: var(--flyer-preview-height, calc(1056px * var(--mobile-flyer-scale)));
overflow: hidden;
padding: 0;
}
.flyer-sheet,
.container {
/* Keep the sheet's true print width so the scale math (availableWidth /
sheetWidth) is right. In viewer mode .flyer-preview is a flexbox, and
without flex:none the sheet shrinks to the viewport, making the scale
compute to 1 and the full-width content clip instead of shrinking. */
flex: 0 0 auto;
transform: scale(var(--mobile-flyer-scale));
transform-origin: top left;
margin-bottom: var(--flyer-preview-collapse, calc((1056px * var(--mobile-flyer-scale)) - 1056px));
}
body.mode-viewer .viewer-floating {
position: sticky;
top: 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 6px;
padding: 8px;
}
body.mode-viewer .viewer-floating button,
body.mode-viewer .viewer-floating select {
width: 100%;
max-width: none;
min-height: 40px;
font-size: 12px;
}
body.mode-viewer .viewer-floating select {
grid-column: 1 / -1;
}
body.mode-viewer .flyer-preview {
justify-content: flex-start;
min-height: 0;
padding: 0;
overflow: hidden;
}
body.mode-viewer #viewer-panel.drawer-open {
top: 96px;
right: 8px;
bottom: 8px;
width: calc(100vw - 16px);
padding: 12px;
}
}
@media print {
/* @page size is injected by applyPaperSize() in nostr-client.js (Letter or
A4) since @page cannot read CSS custom properties across engines. This
rule is the margin fallback and the default size before JS runs. */
@page {
size: Letter portrait;
margin: 0;
}
html,
body {
width: var(--sheet-w);
height: var(--sheet-h);
margin: 0;
padding: 0;
overflow: hidden;
}
body > header,
body > .viewer-floating,
.viewer-floating,
.viewer-floating *,
.board-link,
.modal-overlay,
#open-viewer-controls,
#viewer-print,
#viewer-editor,
#viewer-lang,
body > main > .panel,
.preview-toolbar,
.viewer-drawer {
display: none !important;
}
main {
display: block;
width: var(--sheet-w);
height: var(--sheet-h);
padding: 0;
margin: 0;
}
.preview-band {
display: block;
width: var(--sheet-w);
height: var(--sheet-h);
border: 0;
margin: 0;
padding: 0;
}
.flyer-preview {
display: block;
width: var(--sheet-w);
height: var(--sheet-h);
overflow: visible;
padding: 0;
margin: 0;
}
.flyer-sheet,
.container {
position: absolute;
inset: 0 auto auto 0;
width: var(--sheet-w);
height: var(--sheet-h);
transform: none;
margin: 0;
border: 0;
}
}
/* ---- Bulletin board page (board.html) ---- */
.board-main {
max-width: 1000px;
margin: 0 auto;
padding: 24px 20px 48px;
}
.board-back {
color: var(--accent);
text-decoration: none;
font-weight: 700;
}
#board-status {
color: var(--muted);
margin: 8px 0 16px;
}
.board-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.board-table th,
.board-table td {
text-align: left;
padding: 8px 10px;
border-bottom: 1px solid var(--line);
vertical-align: top;
}
.board-table thead th {
position: sticky;
top: 0;
background: var(--soft);
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.board-table thead th .sort-indicator {
color: var(--muted);
font-size: 11px;
}
.board-table tbody tr:hover {
background: #faf7f7;
}
.board-table .board-title {
font-weight: 700;
overflow-wrap: anywhere;
}
.board-table .board-npub {
font-family: "Courier New", monospace;
font-size: 12px;
white-space: nowrap;
}
.board-table .board-author-name {
font-weight: 700;
overflow-wrap: anywhere;
}
.board-table .board-date {
white-space: nowrap;
color: var(--muted);
}
.board-table .board-eventid {
font-family: "Courier New", monospace;
font-size: 12px;
white-space: nowrap;
}
.board-table .board-copy {
margin-inline-start: 6px;
font: inherit;
font-size: 11px;
padding: 2px 8px;
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--accent);
cursor: pointer;
white-space: nowrap;
}
/* Per-row management actions (edit / re-broadcast / delete / block) + the
"blocked" note in the filter bar. */
.board-action {
display: inline-block;
font: inherit;
font-size: 11px;
padding: 2px 8px;
margin: 0 2px;
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--accent);
text-decoration: none;
cursor: pointer;
white-space: nowrap;
}
.board-action.danger:hover {
background: var(--accent);
color: #fff;
}
.board-blocked-note {
font-size: 12px;
color: var(--muted);
}
/* Per-row ⋯ actions menu. */
.board-table td:last-child { text-align: right; }
.board-menu-wrap {
position: relative;
display: inline-block;
}
.board-menu-btn {
font: inherit;
font-size: 16px;
line-height: 1;
padding: 2px 8px;
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--muted);
cursor: pointer;
}
.board-menu-btn:hover {
color: var(--accent);
border-color: var(--accent);
}
.board-menu {
position: absolute;
top: calc(100% + 4px);
inset-inline-end: 0;
z-index: 100;
min-width: 150px;
padding: 4px;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);
display: flex;
flex-direction: column;
text-align: start;
}
.board-menu[hidden] { display: none; }
.board-menu-item {
display: block;
width: 100%;
box-sizing: border-box;
font: inherit;
font-size: 13px;
text-align: start;
padding: 7px 10px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--ink, #1a1a1a);
text-decoration: none;
cursor: pointer;
white-space: nowrap;
}
.board-menu-item:hover {
background: var(--soft);
}
.board-menu-item.danger {
color: var(--accent);
}
/* The mobile "Details" disclosure toggle is hidden on desktop (the secondary
fields are their own columns there). */
.board-details-toggle {
display: none;
}
/* Narrow screens: the 7-column table becomes a stacked card per flyer. Title +
the ⋯ menu are always shown; the rest sits behind a per-row "Details" toggle. */
@media (max-width: 640px) {
.board-table thead { display: none; }
.board-table,
.board-table tbody,
.board-table tr,
.board-table td { display: block; width: auto; }
.board-table tr {
position: relative;
border: 1px solid var(--line);
border-radius: 8px;
margin: 0 0 10px;
padding: 10px 44px 10px 12px;
}
.board-table td {
border: none;
padding: 2px 0;
white-space: normal;
}
.board-table td[data-col="title"] { padding-top: 0; }
.board-table td[data-col="title"] .board-title { font-size: 15px; }
.board-table td[data-col="actions"] {
position: absolute;
top: 8px;
inset-inline-end: 8px;
padding: 0;
}
.board-details-toggle {
display: block;
width: fit-content;
margin-top: 4px;
padding: 2px 0;
font: inherit;
font-size: 12px;
background: transparent;
border: none;
color: var(--accent);
cursor: pointer;
user-select: none;
}
/* Secondary fields collapsed until the row is expanded. */
.board-table td[data-col="link"],
.board-table td[data-col="author"],
.board-table td[data-col="language"],
.board-table td[data-col="date"],
.board-table td[data-col="id"] {
display: none;
font-size: 13px;
}
.board-table tr.expanded td[data-col="link"],
.board-table tr.expanded td[data-col="author"],
.board-table tr.expanded td[data-col="language"],
.board-table tr.expanded td[data-col="date"],
.board-table tr.expanded td[data-col="id"] {
display: block;
}
/* Label each revealed field with its column name. */
.board-table tr.expanded td[data-col="link"]::before,
.board-table tr.expanded td[data-col="author"]::before,
.board-table tr.expanded td[data-col="language"]::before,
.board-table tr.expanded td[data-col="date"]::before,
.board-table tr.expanded td[data-col="id"]::before {
content: attr(data-label) ": ";
color: var(--muted);
font-weight: 700;
}
}
.board-table a {
color: var(--accent);
}
/* ---- Bulletin board login gate (NIP-07) ---- */
.board-gate {
max-width: 520px;
margin: 48px auto;
text-align: center;
}
.board-gate h2 {
margin: 0 0 8px;
font-size: 22px;
}
.board-gate p {
color: var(--muted);
margin: 0 0 20px;
line-height: 1.4;
}
.board-connect {
font: inherit;
font-weight: 700;
padding: 10px 24px;
border: none;
border-radius: 6px;
background: var(--accent);
color: #fff;
cursor: pointer;
}
.board-connect:disabled {
opacity: 0.6;
cursor: default;
}
.board-gate-error {
color: #8a4d00;
margin: 16px 0 0;
}
.board-connect-methods {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.board-connect-alt {
font: inherit;
font-size: 14px;
padding: 8px 18px;
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--accent);
cursor: pointer;
}
.board-nsec-row,
.board-generated {
max-width: 420px;
margin: 18px auto 0;
text-align: left;
}
.board-nsec-row input,
.board-key-field input {
width: 100%;
box-sizing: border-box;
font: inherit;
padding: 8px 10px;
border: 1px solid var(--line);
border-radius: 6px;
}
.board-nsec-row .board-connect {
margin-top: 8px;
}
.board-key-note {
color: var(--muted);
font-size: 12px;
margin: 8px 0 0;
}
.board-key-warning {
color: #8a4d00;
font-size: 13px;
margin: 0 0 12px;
line-height: 1.4;
}
.board-key-field {
display: block;
font-size: 12px;
color: var(--muted);
margin: 0 0 10px;
}
.board-key-field input {
margin-top: 4px;
font-family: "Courier New", monospace;
}
.board-key-secret {
display: flex;
gap: 6px;
align-items: stretch;
}
.board-key-secret input {
flex: 1;
}
.board-identity {
display: inline-flex;
align-items: center;
gap: 8px;
}
/* The `hidden` attribute must win over the display rule above. */
.board-identity[hidden] {
display: none;
}
.board-identity .board-npub {
font-family: "Courier New", monospace;
font-size: 12px;
color: var(--muted);
}
.board-disconnect {
font: inherit;
font-size: 13px;
padding: 4px 10px;
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--accent);
cursor: pointer;
}
/* Web-of-trust filter bar above the board table. */
.board-filter {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin: 4px 0 12px;
}
.board-filter-note {
color: var(--muted);
font-size: 13px;
}
.board-show-all {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
cursor: pointer;
white-space: nowrap;
}
/* ---- Publish confirmation modal ---- */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(0, 0, 0, 0.5);
}
.modal-overlay[hidden] {
display: none;
}
.modal {
background: #fff;
color: var(--ink);
width: 100%;
max-width: 440px;
padding: 24px;
border-radius: 8px;
border: 1px solid var(--line);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}
.modal h2 {
margin: 0 0 8px;
font-size: 20px;
}
.modal p {
margin: 0 0 14px;
color: var(--muted);
}
.modal-url-row {
display: flex;
gap: 8px;
margin-bottom: 18px;
}
.modal-url-row input {
flex: 1;
min-width: 0;
padding: 8px 10px;
font-family: "Courier New", monospace;
font-size: 13px;
border: 1px solid var(--line);
border-radius: 4px;
}
.modal-url-row button {
white-space: nowrap;
}
.modal-relays {
margin: 10px 0 0;
font-size: 13px;
color: var(--muted);
}
.modal-relays.modal-relays-warn {
color: var(--accent);
font-weight: 700;
}
.modal-close {
display: block;
margin-left: auto;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
}
/* Destructive action (delete). */
.secondary-button.danger {
border-color: var(--accent);
color: var(--accent);
}
.secondary-button.danger:hover {
background: var(--accent);
color: #fff;
}
#loaded-flyer-status {
color: var(--muted);
}