mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Authors can now manage a published flyer from the editor when it's loaded from an event (author-only — the active signing key must match the flyer's author): - Edit: change fields and publish again; the same d-tag replaces in place. - Re-publish (keep-alive): re-sign + re-broadcast the current content so relays that prune app-data don't age the flyer out. Partially addresses roadmap #4. - Delete: belt-and-suspenders across relay behaviors — 1) a replaceable TOMBSTONE (same kind+d, payload deleted:true + a ["deleted"] tag) that overwrites the flyer's content, so the board/viewer hide it even on relays that ignore deletions, and 2) a NIP-09 deletion request (kind 5) for the addressable a-tag + event id. Confirmed via a modal; framed as best-effort, not a guarantee. Tombstone recognition: the board's parseFlyers keeps the newest event per (pubkey, d) and skips it when that newest version is a tombstone (so a delete can't be reversed by an older copy arriving later); the viewer renders a localized "This flyer was removed." state. Adds the loaded-flyer actions UI + a delete-confirm modal, 11 new strings × 14 languages, a Python schema test for the tombstone payload, and e2e for the delete flow, board hiding, and the viewer removed state. 216 e2e across all six engines; docs (spec/roadmap/README) updated. Co-Authored-By: Claude Opus 4.8 <[email protected]>
1402 lines
23 KiB
CSS
1402 lines
23 KiB
CSS
: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). */
|
||
.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-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 {
|
||
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-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;
|
||
}
|
||
|
||
.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-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);
|
||
}
|