mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Three mobile/responsive fixes (diagnosed at phone widths):
- Flyer no longer side-scrolls/clips on phones. In viewer mode .flyer-preview is
a flexbox, so the sheet (a flex item) was shrunk to the viewport, making the
fit math compute scale = 1 and the full-width content clip. Add flex: 0 0 auto
so the sheet keeps its true print width and the transform scales it to fully
fit the screen.
- Bulletin board collapses to a card per flyer on ≤640px: title + the ⋯ menu
stay visible; the secondary columns (link, posted-by, language, date, event
id) move behind a per-row "Details" disclosure (labeled via data-label). New
details_label string × 14 languages.
- Fix a real (not mobile-only) bug: the editor's nsec-import box and
"locked identity" row showed on every load because .identity-import's
display:flex overrode the hidden attribute. Add .identity-import[hidden]
{ display:none } (same class of bug we hit on the board identity chip).
Adds e2e: flyer fits width with no horizontal overflow, identity controls hidden
until chosen, and the board's narrow-screen card + Details toggle. The desktop
"sortable columns" test pins a desktop viewport (the header is hidden in card
mode). 258 e2e across all six engines; docs updated.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
1612 lines
28 KiB
CSS
1612 lines
28 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). */
|
||
/* 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-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-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);
|
||
}
|