Files
archy/docker/dojobay/assets/css/styles.css
T

394 lines
28 KiB
CSS

/* Self-hosted variable fonts (latin subset). No external CDN. */
@font-face{font-family:'Archivo';font-style:normal;font-weight:100 900;font-display:swap;src:url('../fonts/archivo.woff2') format('woff2')}
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:100 900;font-display:swap;src:url('../fonts/hanken-grotesk.woff2') format('woff2')}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:100 800;font-display:swap;src:url('../fonts/jetbrains-mono.woff2') format('woff2')}
:root{
--bg:#0a0a0a; --panel:#141414; --panel2:#1c1c1c; --line:#2a2a2a; --line-soft:#1c1c1c;
--text:#f4f4f3; --muted:#a0a0a0; --faint:#6b6b6b;
--accent:#b5302a; --accent-2:#d6534a; --accent-bg:rgba(181,48,42,.12); --accent-line:rgba(181,48,42,.34);
--btc:#f7931a; --btc-text:#1a1206; --grey-sel:#8a8a8a;
--up:#3fb950; --up-bg:rgba(63,185,80,.14); --down:#d6584f; --down-dim:#5a3330;
/* Every use of this was written as var(--warn,#e0a020) against a token that
was never declared, so the fallback always won. Declared here so the
amber is adjustable in one place; --mid is the 90-day middle band, which
was a bare hex literal for the same reason. */
--warn:#e0a020; --mid:#b9a13a;
/* Same class of bug, found by auditing every var() reference against the
declarations: .admin-row asked for --card and had been taking #0e0e10 by
fallback since it was written. Declared at that value rather than at
--panel, so nothing changes appearance; whether the admin rows were meant
to sit a shade darker than the cards they resemble is a separate
question, and not one to answer by accident a second time. */
--card:#0e0e10;
--code-bg:#070707; --code-fg:#e6a39b;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{background:var(--bg);color:var(--text)}
body{font-family:'Hanken Grotesk',system-ui,sans-serif;line-height:1.5;-webkit-font-smoothing:antialiased}
.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
.disp{font-family:'Archivo',sans-serif}
a{color:inherit;text-decoration:none}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
.wrap{max-width:1120px;margin:0 auto;padding:0 22px}
.eyebrow{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
header{border-bottom:1px solid var(--line-soft);position:sticky;top:0;background:rgba(11,11,12,.86);backdrop-filter:blur(8px);z-index:20}
header .wrap{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:16px 22px}
.brand{display:flex;align-items:center;gap:12px}
.brand .name{font-weight:800;font-size:18px;letter-spacing:-.01em}
.brand .sub{font-size:10.5px;color:var(--faint);margin-top:1px;letter-spacing:.04em}
nav{display:flex;gap:6px;align-items:center}
nav .lnk{color:var(--muted);font-size:14px;padding:7px 11px;border-radius:7px;transition:color .15s,background .15s}
nav .lnk:hover{color:var(--text);background:var(--panel)}
.onion-pill{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--accent);border:1px solid var(--accent-line);background:var(--accent-bg);padding:6px 10px;border-radius:7px;margin-left:4px}
.onion-pill:hover{background:rgba(247,147,26,.16)}
.burger{display:none;background:none;border:0;color:var(--text);padding:6px;cursor:pointer;border-radius:7px}
/* The Auth47 challenge, shown under its QR. Wraps anywhere because it is one
unbroken token, and carries its own copy button for the same-device case. */
/* Column, always, at every width. Side by side the button lands wherever the
URI happens to stop wrapping, so it sits mid-line on one screen and below
on another, and on the narrow case it crowds the text it belongs to. The
URI is a single unbroken token that has to wrap anyway, so there is no
width at which a row reads better. */
.a47-uri{display:flex;flex-direction:column;align-items:center;gap:10px;
margin-top:10px;text-align:left}
.a47-uri code{font-size:10.5px;color:var(--faint);word-break:break-all;line-height:1.5;
max-width:44ch;width:100%}
.a47-uri .copybtn{align-self:center}
.upd-line{margin:6px 0}
/* Self-update has never completed a run on real hardware. The badge is not
decoration: a maintainer clicking Update from GitHub is the first person
who will find out whether it works, and should know that before clicking. */
.upd-exp{display:inline-block;font-size:10px;letter-spacing:.08em;text-transform:uppercase;
font-family:'JetBrains Mono',monospace;color:var(--warn,#e0a020);
border:1px solid rgba(224,160,32,.45);background:rgba(224,160,32,.10);
border-radius:5px;padding:1px 6px;margin-left:8px;vertical-align:1px}
/* Full width. It was capped at 62ch, which is right for prose a reader is
settling into and wrong for a warning beside the control it warns about:
it left the paragraph as a narrow column against a wide panel, and the
ragged right edge read as a layout fault rather than as deliberate
measure. */
.upd-exp-note{font-size:11.5px;color:var(--faint);line-height:1.55;margin:8px 0 0;width:100%}
.upd-none{font-size:11.5px;color:var(--faint);margin:6px 0 0}
/* An update that did not finish. Warning-coloured rather than faint, because
the failure it describes is invisible everywhere else: the code is on disk,
the footer already shows the new build, and only the process serving the
page is stale. */
.upd-warn{font-size:12px;line-height:1.55;margin:8px 0 0;padding:9px 11px;border-radius:7px;
color:#e9d6d2;background:rgba(214,88,79,.10);border:1px solid rgba(214,88,79,.45)}
.upd-warn b{color:var(--down)}
.upd-controls{display:flex;gap:8px;align-items:center;margin-top:6px;flex-wrap:wrap}
.upd-bar{height:8px;border-radius:6px;background:var(--panel2);overflow:hidden;margin:8px 0}
.upd-bar-fill{height:100%;transition:width .4s ease;border-radius:6px}
.upd-log{font-size:11px;color:var(--faint);background:var(--panel2);border-radius:8px;padding:8px 10px;margin:6px 0;white-space:pre-wrap;line-height:1.5;max-height:120px;overflow:auto}
/* The import plan. Refused rows are coloured rather than hidden: a directory
publishing listings this instance will not accept is the most informative
thing on the table, and collapsing it to a count would bury it. */
table.imp{width:100%;border-collapse:collapse;font-size:12px;margin:8px 0}
table.imp th{text-align:left;font-weight:600;color:var(--faint);font-size:11px;
padding:4px 8px 4px 0;border-bottom:1px solid var(--line-soft)}
table.imp td{padding:5px 8px 5px 0;border-bottom:1px solid var(--line-soft);vertical-align:top}
tr.imp-merge td{color:var(--muted)}
tr.imp-refuse td{color:var(--down)}
.op-avatar{width:20px;height:20px;border-radius:50%;object-fit:cover;
border:1px solid var(--line-soft);display:inline-block;vertical-align:middle}
/* PayNym avatar centred on the pairing QR (QR is generated at EC level H,
so the ~5% of symbol area the avatar covers is well within recovery) */
.tile{position:relative}
.qr-avatar{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
width:21%;height:21%;object-fit:cover;border-radius:8px;
border:3px solid #fff;background:#fff}
/* payment code chip on cards: truncated, click copies the full code */
/* The payment code owns its own line and spans the card, so it reads as the
identity of the listing rather than one chip among several. The verified
domain and its verify button sit on the line beneath. */
.pcode{display:block;width:100%;text-align:left;margin:2px 0 8px;padding:5px 11px;font-size:11.5px;
letter-spacing:.02em;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
background:var(--panel2);border:1px solid var(--line-soft);border-radius:8px;cursor:pointer}
.pcode:hover{color:var(--text);border-color:var(--accent)}
.pcode.done{color:var(--up)}
/* inline display-field editor (Manage rows and admin rows) */
.medit{margin-top:10px;padding-top:10px;border-top:1px solid var(--line-soft);display:flex;flex-direction:column;gap:8px}
.medit label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--muted)}
.medit input{background:var(--panel);border:1px solid var(--line-soft);border-radius:7px;color:var(--text);
padding:7px 9px;font-size:13px;font-family:inherit}
.medit input:focus{outline:none;border-color:var(--accent)}
.medit-actions{display:flex;gap:8px;align-items:center}
.copybtn[disabled],.abtn[disabled]{opacity:.4;cursor:default}
.burger:hover{background:var(--panel)}
.controls{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;padding:30px 22px 18px}
.seg{display:inline-flex;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:3px}
.seg button{font-family:'JetBrains Mono',monospace;font-size:12px;padding:8px 18px;border-radius:7px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);transition:background .15s,color .15s}
.seg button.on{color:#0a0a0a;font-weight:700}
.seg button[data-net="mainnet"].on{background:var(--btc);color:var(--btc-text)}
.seg button[data-net="testnet"].on{background:var(--grey-sel);color:#0a0a0a}
.fresh{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--muted);display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.fresh .dot{width:7px;height:7px;border-radius:99px;background:var(--up);display:inline-block;box-shadow:0 0 0 3px var(--up-bg)}
.fresh b{color:var(--text);font-weight:700}
.fresh .sep{color:var(--faint)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px}
.card{background:var(--panel);border:1px solid var(--line-soft);border-radius:12px;padding:18px;transition:border-color .18s,transform .18s}
.card:hover{border-color:var(--line);transform:translateY(-2px)}
.card.inactive{opacity:.74}
.ctop{display:flex;align-items:center;gap:10px}
.ctop .sd{width:9px;height:9px;border-radius:99px;flex-shrink:0}
.sd.active{background:var(--up);box-shadow:0 0 0 3px var(--up-bg)}
.sd.inactive{background:var(--down);box-shadow:0 0 0 3px rgba(214,88,79,.14)}
.cname{font-family:'Archivo',sans-serif;font-weight:700;font-size:16px;letter-spacing:-.01em;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
a.cname{transition:color .15s}
a.cname:hover{color:var(--accent)}
a.cname .ext{font-size:11px;color:var(--faint);vertical-align:middle}
a.cname:hover .ext{color:var(--accent)}
.cbadge{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;padding:3px 8px;border-radius:5px;font-weight:700;flex-shrink:0}
/* Stale data: the updater has not refreshed dojos.json for several intervals,
so we stop asserting status. Badges go neutral rather than green or red,
because "unknown" is the honest answer, not "down". */
/* The empty directory. Deliberately quiet: a bordered panel rather than a
warning colour, because an instance with nothing published yet is usually
new rather than broken. */
.empty{border:1px dashed var(--line);border-radius:10px;padding:26px 22px;text-align:center;
color:var(--muted);font-size:14px;line-height:1.65;margin:0 0 18px}
.empty b{color:var(--text)}
.empty-cta{margin-top:8px;font-size:13px;color:var(--faint)}
/* The Tor port picker. Two presets, because there are two answers in practice
and a free-text field would invite typos into the one value that has to be
right for any of the commands below it to work. */
.portpick{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:12px 0 14px}
.portpick .k{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.06em;
text-transform:uppercase;color:var(--faint)}
.pbtn{font:inherit;font-family:'JetBrains Mono',monospace;font-size:13px;padding:6px 11px;
border-radius:7px;border:1px solid var(--line);background:transparent;color:var(--muted);cursor:pointer}
.pbtn .w{display:block;font-family:'Hanken Grotesk',sans-serif;font-size:10.5px;color:var(--faint);
letter-spacing:0;text-transform:none;margin-top:1px}
.pbtn:hover{border-color:var(--line-soft);color:var(--text)}
.pbtn.on{border-color:var(--accent);color:var(--accent-2);background:rgba(181,48,42,.10)}
.pbtn.on .w{color:var(--accent-2)}
.stale-banner{margin:0 0 18px;padding:12px 14px;border-radius:8px;font-size:13.5px;line-height:1.6;
color:var(--text);background:rgba(214,88,79,.10);border:1px solid rgba(214,88,79,.35)}
.stale-banner b{color:var(--down)}
.grid.stale .sd.active,.grid.stale .sd.inactive{background:var(--faint);box-shadow:0 0 0 3px rgba(139,148,158,.12)}
.grid.stale .cbadge.active,.grid.stale .cbadge.inactive{color:var(--faint);background:var(--panel2)}
.grid.stale .card{opacity:.92}
.fresh.stale .dot{background:var(--faint);box-shadow:0 0 0 3px rgba(139,148,158,.12)}
.cbadge.active{color:var(--up);background:var(--up-bg)}
.cbadge.inactive{color:var(--down);background:rgba(214,88,79,.12)}
.csub{display:flex;align-items:center;gap:8px;margin:9px 0 2px;font-size:13px;flex-wrap:wrap}
.csub .pn{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--accent)}
.csub .pn:hover{text-decoration:underline}
.csub .jur{color:var(--muted);display:inline-flex;align-items:center;gap:5px}
.csub .flag{font-size:14px;line-height:1}
.csub .nopn{color:var(--faint);font-style:italic;font-size:12.5px}
.rel{margin:15px 0 4px}
.rel-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px}
.rel-head .pct{font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700}
.rel-head .pct .n{color:var(--faint);font-weight:400}
.rel-bars{display:flex;gap:2px;align-items:stretch;height:26px}
.rel-bars .b{flex:1;min-width:2px;border-radius:1px;background:var(--down-dim)}
.rel-bars .b.up{background:var(--up)}
.rel-bars .b.down{background:var(--down)}
.rel-axis{display:flex;justify-content:space-between;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--faint)}
.meta{display:grid;grid-template-columns:1fr 1fr;gap:11px 16px;margin:14px 0 4px}
.meta .full{grid-column:1/-1}
.meta .v{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--text);margin-top:2px;word-break:break-word}
.reveal{width:100%;padding:11px;border-radius:8px;background:var(--accent-bg);border:1px solid var(--accent-line);color:var(--accent-2);font-weight:600;font-size:13.5px;margin-top:14px;transition:background .15s}
.reveal:hover{background:rgba(247,147,26,.16)}
.reveal.open{color:var(--muted);border-color:var(--line)}
/* Secondary action under the primary one: same shape, quieter, so pairing
stays the obvious thing to click. */
.reveal.secondary{background:var(--panel2);border-color:var(--line);color:var(--muted);
font-weight:500;font-size:12.5px;padding:9px;margin-top:8px}
.reveal.secondary:hover{color:var(--text);border-color:var(--line-soft);background:var(--panel2)}
.pair{margin-top:14px;animation:rise .25s ease}
@keyframes rise{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}
.qr{display:flex;flex-direction:column;align-items:center;gap:7px;margin-bottom:14px}
.qr .tile{background:#fff;border:1px solid var(--accent-line);border-radius:10px;padding:12px;line-height:0}
.qr .tile svg{display:block;border-radius:2px}
.qr .cap{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.box{margin-bottom:12px}
.box .lbl,.modal-body>.lbl{display:flex;justify-content:space-between;align-items:center;margin-bottom:7px}
.modal-body>.lbl{margin:18px 0 8px;gap:12px}
.box .lbl .t,.modal-body>.lbl .t{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.box pre{font-family:'JetBrains Mono',monospace;font-size:10.5px;line-height:1.55;background:var(--code-bg);color:var(--code-fg);border:1px solid var(--line);border-radius:8px;padding:13px;white-space:pre-wrap;word-break:break-all;max-height:240px;overflow:auto}
.box.signed pre{color:#cdd6e4;font-size:10px}
.copybtn{font-family:'JetBrains Mono',monospace;font-size:11px;padding:5px 11px;border:1px solid var(--accent-line);border-radius:6px;color:var(--accent);background:var(--accent-bg);transition:background .15s}
.copybtn:hover{background:rgba(247,147,26,.18)}
.copybtn.done{color:var(--up);border-color:rgba(63,185,80,.4);background:var(--up-bg)}
.eps{margin-top:4px;display:flex;flex-direction:column;gap:8px}
.card-eps{margin-top:14px;display:flex;flex-direction:column;gap:7px}
.ep{display:flex;align-items:center;gap:9px}
.ep .k{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);min-width:62px}
.ep .u{font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--muted);background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:5px 8px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Verified operator domain: a quiet badge, not a trust mark. It sits beside the
payment-code chip and attests to control of the domain only. */
.vdomain{display:inline-flex;align-items:center;gap:5px;font-family:'JetBrains Mono',monospace;
font-size:11px;padding:4px 8px;border-radius:6px;text-decoration:none;
color:var(--up);border:1px solid rgba(63,185,80,.35);background:var(--up-bg);white-space:nowrap;
max-width:190px;overflow:hidden;text-overflow:ellipsis}
.vdomain:hover{border-color:rgba(63,185,80,.6)}
/* "For the machines among us": an unobtrusive way to interrogate the badge. */
/* Domain and its verify button, on the line below the payment code. The
domain takes the free space so a long one truncates instead of pushing the
button off the card. */
.vrow{display:flex;align-items:center;gap:8px;margin:0 0 8px;flex-wrap:nowrap}
.vrow .vdomain{flex:1 1 auto;min-width:0;max-width:none}
.vrow .vproof{flex:0 0 auto}
.vproof{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.06em;
padding:4px 7px;border-radius:6px;color:var(--faint);border:1px solid var(--line);
background:var(--panel2);cursor:pointer}
.vproof:hover{color:var(--muted);border-color:var(--line-soft)}
/* A warning that must not be skimmed past: the XPUB advice is the one place
where following the page carelessly could cost a reader their privacy. */
.warnbox{border:1px solid rgba(214,88,79,.45);background:rgba(214,88,79,.10);
border-radius:8px;padding:11px 13px;margin:0 0 12px;font-size:13px;line-height:1.6}
.warnbox b{color:var(--down)}
.proofblk{margin:0 0 12px}
.proofblk .k{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.08em;
text-transform:uppercase;color:var(--faint);margin-bottom:4px}
.proofblk pre{margin:0 0 6px;padding:9px 10px;background:var(--panel2);border:1px solid var(--line);
border-radius:6px;font-size:11.5px;white-space:pre-wrap;word-break:break-all;color:var(--muted)}
/* Verified-domain setup box in Manage. */
.dbox{border:1px solid var(--line);border-radius:8px;padding:12px 13px;background:var(--panel2);margin-bottom:12px}
.dbox p{margin:0 0 8px}
.dbox .dnote{font-size:12.5px;color:var(--muted)}
.dbox .dmsg{font-size:12.5px;color:var(--accent);margin-top:8px;
overflow-wrap:anywhere;word-break:break-word;line-height:1.5}
/* Anything that can contain a payment code, an onion or a TXT value. */
.dbox .dnote,.dbox .dwrap{overflow-wrap:anywhere;word-break:break-word}
.dbox{overflow:hidden}
.dbox .ok-tick{color:var(--up)}
.dbox .ep{margin-bottom:6px}
.dsign{font-size:11.5px;background:var(--panel);border:1px solid var(--line);border-radius:6px;
padding:9px 10px;white-space:pre-wrap;word-break:break-all;color:var(--muted);margin:0 0 8px}
.dbox textarea{width:100%;font-family:'JetBrains Mono',monospace;font-size:11.5px}
.ep .copybtn{flex-shrink:0}
/* An endpoint the node does not publish. The field keeps the same box as the
other endpoints so the rows line up; only the text colour marks it as not
being a value. The copy button stays in place, inert, so the row does not
change width or lose its right-hand column. */
.ep .u.na{color:var(--faint)}
.copybtn[disabled]{opacity:.4;cursor:default;color:var(--faint);border-color:var(--line);background:var(--panel2)}
.copybtn[disabled]:hover{background:var(--panel2)}
.note{margin:30px 0 8px;font-size:13.5px;color:var(--muted);line-height:1.65}
.note a{color:var(--accent);font-weight:600}
.note a:hover{text-decoration:underline}
footer{border-top:1px solid var(--line-soft);padding:24px 0;margin-top:18px}
footer .wrap{display:flex;justify-content:center}
footer .gh{color:var(--faint);display:inline-flex;align-items:center;transition:color .15s}
footer .gh:hover{color:var(--text)}
footer .gh svg{display:block}
.ov{position:fixed;inset:0;background:rgba(4,4,5,.72);backdrop-filter:blur(3px);display:none;align-items:flex-start;justify-content:center;padding:6vh 18px;z-index:50;overflow:hidden}
.ov.show{display:flex}
.modal{background:var(--panel);border:1px solid var(--line);border-radius:14px;max-width:700px;width:100%;padding:0;box-shadow:0 24px 60px rgba(0,0,0,.5);display:flex;flex-direction:column;max-height:88vh;min-height:0;overflow:hidden}
.modal-head{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--line-soft);background:var(--panel);border-radius:14px 14px 0 0;flex:0 0 auto}
.modal-head h2{font-family:'Archivo',sans-serif;font-size:19px;font-weight:700}
.modal-head .x{font-size:22px;color:var(--muted);line-height:1;padding:2px 8px;border-radius:6px}
.modal-head .x:hover{background:var(--panel2);color:var(--text)}
.modal-body{padding:22px 24px 26px;flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}
.modal-body p{font-size:14px;color:#d7d7d4;line-height:1.7;margin-bottom:13px}
.modal-body h2{font-family:'Archivo',sans-serif;font-size:13px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--accent);margin:26px 0 12px;padding-bottom:7px;border-bottom:1px solid var(--line-soft)}
.modal-body h2:first-child{margin-top:0}
.modal-body h3{font-family:'Archivo',sans-serif;font-weight:700;color:var(--text);font-size:14.5px;margin:18px 0 4px}
.modal-body strong{color:var(--text)}
.modal-body a{color:var(--accent);font-weight:600;word-break:break-word}
.modal-body a:hover{text-decoration:underline}
.modal-body ul{margin:0 0 13px 2px;padding:0;list-style:none}
.modal-body li{font-size:14px;color:#d7d7d4;line-height:1.6;margin-bottom:6px;padding-left:2px}
.modal-body code{font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--accent);background:var(--panel2);border:1px solid var(--line);border-radius:5px;padding:2px 6px}
.modal-body blockquote{background:var(--panel2);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:8px;padding:14px 16px;margin:0 0 16px}
.modal-body blockquote p{font-size:13.5px;margin-bottom:9px}
.modal-body blockquote p:last-child{margin-bottom:0}
.modal-body blockquote code{display:inline-block;color:var(--accent);font-size:14px}
.modal-body .loading{color:var(--faint);font-family:'JetBrains Mono',monospace;font-size:13px}
@media (max-width:560px){
.meta{grid-template-columns:1fr}
/* The network toggle and the freshness line sit at opposite ends of one row
on a wide screen, which is what space-between is for. On a narrow one they
wrap onto separate lines, and space-between then puts a lone item at the
start of its line, so both ended up hard against the left edge under a
centred header. Centre them instead: the toggle is the page's primary
control and reads as a control rather than a stray pair of words when it
is centred under the title.
.fresh is itself a flex container whose own content wraps, so it needs
centring too, or its second line ("re-checks every 10 min") hangs left
under a centred first line, which looks like a mistake rather than a
wrap. text-align covers any inline content that is not a flex item. */
.controls{justify-content:center;gap:12px;padding:22px 18px 14px}
.fresh{justify-content:center;text-align:center}
header .wrap{padding:14px 18px;position:relative;justify-content:flex-start}
.burger{display:block;z-index:2}
/* Centre the title while the hamburger is in use. Only then: taking .brand
out of flow leaves the burger as the header's only in-flow child, and on
a page that has no burger the header collapses to its padding, so the
brand overlaps whatever is beneath it. That is what the operator console
looked like on a phone, its title clipped over the Moderation heading,
and its one nav link was unreachable as well because the nav becomes a
dropdown with nothing to open it. */
header:not(.no-menu) .brand{position:absolute;left:50%;transform:translateX(-50%)}
header.no-menu .wrap{justify-content:space-between;gap:10px}
header.no-menu nav{display:flex;position:static;flex-direction:row;background:none;
backdrop-filter:none;border:0;padding:0}
header.no-menu nav .lnk{padding:8px 10px;font-size:14px;white-space:nowrap}
header.no-menu .brand .name{font-size:16px}
/* the nav becomes a full-width dropdown under the header */
nav{display:none;position:absolute;top:100%;left:0;right:0;flex-direction:column;align-items:stretch;gap:2px;
background:rgba(11,11,12,.97);backdrop-filter:blur(8px);border-bottom:1px solid var(--line-soft);padding:8px 14px 12px}
nav.open{display:flex}
nav .lnk{display:block;text-align:center;padding:12px;font-size:15px}
nav .onion-pill{text-align:center;margin:6px 0 0}
/* Less chrome around the dialog on a small screen, so the body gets the
height. The scrolling still happens inside .modal-body. */
.ov{padding:3vh 10px}
.modal{max-height:94vh}
.modal-head{padding:16px 18px}
.modal-body{padding:18px 18px 22px}
}
@media (prefers-reduced-motion:reduce){.card:hover{transform:none}.pair{animation:none}}
/* Manage my Dojo form */
.mform{display:flex;flex-direction:column;gap:12px}
.mform label{display:flex;flex-direction:column;gap:5px;font-size:12.5px;color:var(--muted)}
.mform input,.mform select,.mform textarea{background:var(--bg);color:var(--text);border:1px solid var(--line);border-radius:7px;padding:9px 10px;font-family:'JetBrains Mono',monospace;font-size:12.5px;width:100%}
.mform textarea{resize:vertical;line-height:1.5}
.mform input:focus,.mform select:focus,.mform textarea:focus{outline:none;border-color:var(--accent-line)}
/* 90-day daily history (on the card, below the 24h strip) */
.hist90{margin-top:12px}
.d90strip{display:flex;gap:1px;align-items:flex-end;height:22px;margin:8px 0 6px}
.d90{flex:1 1 0;min-width:1px;height:100%;border-radius:1px;background:var(--line)}
.d90.up{background:var(--up)} .d90.mid{background:var(--mid)} .d90.down{background:var(--down)} .d90.na{background:var(--line)}
.d90foot{display:flex;justify-content:space-between;font-size:11px;font-family:'JetBrains Mono',monospace}
.spark{display:block;margin-top:6px;opacity:.9}
footer .ver{margin-left:12px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--faint)}
footer .ver a{color:var(--faint)} footer .ver a:hover{color:var(--text)}
/* footer verify link */
footer .wrap{display:flex;align-items:center;gap:10px}
.foot-spacer{flex:1}
.verify-pre{background:var(--panel,#111);border:1px solid var(--line);border-radius:8px;padding:12px;font-family:'JetBrains Mono',monospace;font-size:11px;white-space:pre-wrap;word-break:break-all;color:var(--text);margin-top:6px}
/* admin console */
.admin-row{border:1px solid var(--line);border-radius:10px;padding:14px;margin:10px 0;background:var(--card,#0e0e10)}
.admin-head{display:flex;align-items:center;gap:8px;margin-bottom:2px}
.abadge{font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:2px 7px;border-radius:20px;border:1px solid var(--line);color:var(--muted)}
.abadge.pending{color:#b9a13a;border-color:#b9a13a}
.abadge.approved{color:var(--up);border-color:var(--up)}
.abadge.rejected{color:var(--down);border-color:var(--down)}
.admin-actions{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
.abtn{font:inherit;font-size:13px;padding:7px 14px;border-radius:8px;border:1px solid var(--line);background:transparent;color:var(--text);cursor:pointer}
.abtn.ok{border-color:var(--up);color:var(--up)}
.abtn.danger{border-color:var(--down);color:var(--down)}
.abtn:disabled{opacity:.5}