None of these could fail a unit test, and three of them break the feature for the operator rather than for the code. **The uploads acknowledgement was a one-way door.** A settings form sends every field it owns, so once `teams_forum_images` was `uploads`, every later save re-sent `uploads` — and the gate fired on the VALUE being present rather than on the mode being SELECTED. The operator could never change a forum setting again, and the thing they would reach for in a hurry, switching the forum off, was exactly what came back 400. The gate now passes when an acknowledgement for the version in force is already on record AND uploads is already the stored mode: there is no new consent to take. A transition INTO uploads still asks, and a reworded notice is still caught by assertSettingsWritable. **An uploaded image could never become a picture.** `uploads` mode hands the composer `/uploads/<name>.png`, the composer puts it in the body as text — the author never writes markup, which is the whole design — and the renderer only rewrites ANCHORS. The linkifier matched absolute http(s) URLs only, so the write path could not produce the anchor the read path looks for, even though `isEmbeddableImageUrl` had accepted those paths since the first commit. The two halves disagreed and only a real upload showed it. **The embed sat beside its link, not beneath it**, because an <img> is inline, and nothing capped a remote image to the column — one post from a host serving a 4000px file would have blown the layout out. Core now emits `class="forum-embed"` and the stylesheet owns both. A class rather than an inline style because the style would then have to survive the client's DOMPurify pass, and its CSS sanitiser is a larger thing to reason about than one class name. **The panel's buttons had no button styling.** `btn-ghost` is a MODIFIER — every other call site in this codebase pairs it with the base `btn` — so alone it contributed colours and no geometry, and the controls rendered as bare boxes. Small inline actions use `pill`, which is what the rest of the admin surface uses for exactly these. Same class of mistake as the Material one in the Android M12 phase: the modifier carries no base. Also: the post body now re-sanitises client-side like every other body-HTML surface on this site, with `ADD_ATTR: ['referrerpolicy']`. That argument is load-bearing — DOMPurify's default allowlist carries `loading` but not `referrerpolicy`, so a plain sanitize() call silently strips the one attribute limiting what a remote embed leaks to the host serving it, which is the privacy property the admin help text promises. Co-Authored-By: Claude <noreply@anthropic.com>
1127 lines
23 KiB
CSS
1127 lines
23 KiB
CSS
/* ===== Runic Gateway design tokens (from the Claude Design handoff) ===== */
|
|
:root {
|
|
--bg: #0e1318;
|
|
--bg-deep: #0b0f14;
|
|
--panel-a: #192231;
|
|
--panel-b: #141a21;
|
|
--panel-flat: #11161d;
|
|
--line: #2a3544;
|
|
--line-soft: #1d2733;
|
|
--accent: #7f99bd;
|
|
--accent-bright: #cdd9e8;
|
|
--ink: #eef3f8;
|
|
--head: #e6edf6;
|
|
--text: #c4cdd8;
|
|
--muted: #aeb8c4;
|
|
--dim: #6f7d8e;
|
|
--blue: #13243c;
|
|
--mode-live: #5fb98a;
|
|
--mode-maint: #e6c26a;
|
|
|
|
--serif: Georgia, "Times New Roman", serif;
|
|
--display: Cinzel, Georgia, serif;
|
|
--sans: "Helvetica Neue", Arial, sans-serif;
|
|
|
|
--shadow-card: 0 14px 34px rgba(0, 0, 0, 0.3);
|
|
--panel-grad: linear-gradient(180deg, var(--panel-a), var(--panel-b));
|
|
|
|
/* Corner radius, by the kind of surface rather than by the pixel value, so a
|
|
theme preset can restyle all of them at once (see
|
|
docs/website/THEMING_AND_NAV.md §4.7). Seeded at the values already in use
|
|
— this promotion is a no-op, and every existing instance must keep looking
|
|
exactly as it does today.
|
|
|
|
Deliberately four tokens, not three: .card/.panel are 10px and .panel-flat
|
|
is 12px, so collapsing them would have restyled every card on every
|
|
install. The 7px (.rte-btn) and 6px (.rte-linkmenu-item) values stay
|
|
literals — interior editor chrome, not brand surface — as do the 50%
|
|
circles, which are shapes rather than radii. */
|
|
--radius-pill: 999px;
|
|
--radius-panel: 12px;
|
|
--radius-card: 10px;
|
|
--radius-input: 8px;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html,
|
|
body {
|
|
margin: 0;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
font-family: var(--serif);
|
|
line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
a {
|
|
color: inherit;
|
|
}
|
|
|
|
/* ===== Typography helpers ===== */
|
|
.display {
|
|
font-family: var(--display);
|
|
font-weight: 600;
|
|
color: var(--head);
|
|
}
|
|
.sans {
|
|
font-family: var(--sans);
|
|
}
|
|
.eyebrow {
|
|
margin: 0 0 14px;
|
|
color: var(--accent);
|
|
font-family: var(--sans);
|
|
font-size: 0.74rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.18em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* ===== Layout shells ===== */
|
|
.shell {
|
|
width: min(1080px, calc(100% - 32px));
|
|
margin: 0 auto;
|
|
}
|
|
.shell-mid {
|
|
width: min(880px, calc(100% - 32px));
|
|
margin: 0 auto;
|
|
}
|
|
.shell-narrow {
|
|
width: min(760px, calc(100% - 32px));
|
|
margin: 0 auto;
|
|
}
|
|
.shell-wide {
|
|
width: min(1280px, calc(100% - 32px));
|
|
margin: 0 auto;
|
|
}
|
|
.page {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.page-body {
|
|
flex: 1;
|
|
padding: 56px 0 64px;
|
|
}
|
|
|
|
/* ===== Cards / panels ===== */
|
|
.card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 24px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-card);
|
|
text-decoration: none;
|
|
color: var(--ink);
|
|
background: var(--panel-grad);
|
|
box-shadow: var(--shadow-card);
|
|
transition: border-color 0.18s, transform 0.18s;
|
|
}
|
|
a.card:hover,
|
|
a.card:focus-visible {
|
|
border-color: var(--accent);
|
|
transform: translateY(-3px);
|
|
outline: none;
|
|
}
|
|
.card-kicker {
|
|
color: var(--accent);
|
|
font-family: var(--sans);
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.15em;
|
|
text-transform: uppercase;
|
|
margin-bottom: 12px;
|
|
}
|
|
.panel {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-card);
|
|
background: var(--panel-grad);
|
|
}
|
|
.panel-flat {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-panel);
|
|
overflow: hidden;
|
|
background: var(--panel-flat);
|
|
}
|
|
.note {
|
|
border: 1px solid var(--line);
|
|
border-left: 3px solid var(--accent);
|
|
border-radius: var(--radius-input);
|
|
background: rgba(19, 36, 60, 0.4);
|
|
padding: 18px 22px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* ===== Grids ===== */
|
|
.grid-2 {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 18px;
|
|
}
|
|
.grid-3 {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 18px;
|
|
}
|
|
.grid-4 {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 16px;
|
|
}
|
|
@media (max-width: 860px) {
|
|
.grid-2,
|
|
.grid-3,
|
|
.grid-4 {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* ===== Pills / buttons ===== */
|
|
.pill {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-pill);
|
|
padding: 7px 14px;
|
|
color: var(--muted);
|
|
background: rgba(11, 22, 48, 0.5);
|
|
font-family: var(--sans);
|
|
font-size: 0.86rem;
|
|
/* Stated, not inherited. A <button class="pill"> would otherwise take the UA
|
|
stylesheet's `line-height: normal` — form controls do not inherit it from
|
|
body — and come out ~7px shorter than an <a class="pill"> beside it. Every
|
|
other property here is already explicit for the same reason; this was the
|
|
one gap, and it only became visible once the public header put a button
|
|
pill (a dropdown trigger) on the same row as the link pills. Matches
|
|
body's 1.6, so no link pill changes. */
|
|
line-height: 1.6;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
|
}
|
|
.pill:hover,
|
|
.pill:focus-visible {
|
|
border-color: var(--accent);
|
|
background: var(--blue);
|
|
color: var(--ink);
|
|
outline: none;
|
|
}
|
|
.btn {
|
|
border-radius: var(--radius-pill);
|
|
padding: 12px 26px;
|
|
font-family: var(--sans);
|
|
font-size: 0.92rem;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
display: inline-block;
|
|
border: 1px solid var(--accent);
|
|
transition: background 0.16s, border-color 0.16s, transform 0.16s;
|
|
}
|
|
.btn-primary {
|
|
background: var(--accent-bright);
|
|
color: var(--bg-deep);
|
|
}
|
|
.btn-primary:hover {
|
|
background: var(--ink);
|
|
}
|
|
.btn-ghost {
|
|
border-color: rgba(200, 208, 216, 0.4);
|
|
color: var(--ink);
|
|
background: rgba(11, 22, 48, 0.45);
|
|
}
|
|
.btn-ghost:hover {
|
|
border-color: var(--accent);
|
|
background: var(--blue);
|
|
}
|
|
.btn-sq {
|
|
border-radius: var(--radius-input);
|
|
padding: 10px 18px;
|
|
font-size: 0.85rem;
|
|
}
|
|
.btn[disabled],
|
|
button[disabled] {
|
|
opacity: 0.55;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* ===== Forms ===== */
|
|
.input,
|
|
.textarea,
|
|
.select {
|
|
width: 100%;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
padding: 11px 14px;
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
font-family: var(--sans);
|
|
font-size: 0.95rem;
|
|
}
|
|
.textarea {
|
|
min-height: 200px;
|
|
resize: vertical;
|
|
line-height: 1.5;
|
|
}
|
|
.input:focus,
|
|
.textarea:focus,
|
|
.select:focus {
|
|
border-color: var(--accent);
|
|
outline: none;
|
|
}
|
|
.field-label {
|
|
display: block;
|
|
margin-bottom: 7px;
|
|
color: var(--muted);
|
|
font-family: var(--sans);
|
|
font-size: 0.78rem;
|
|
letter-spacing: 0.05em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* ===== Headings used in prose ===== */
|
|
.h1 {
|
|
margin: 0;
|
|
font-family: var(--display);
|
|
font-weight: 600;
|
|
font-size: clamp(2.4rem, 5.5vw, 3.6rem);
|
|
line-height: 1.04;
|
|
color: var(--head);
|
|
}
|
|
.lead {
|
|
margin: 16px 0 0;
|
|
color: var(--muted);
|
|
font-size: 1.08rem;
|
|
}
|
|
|
|
/* ===== Rich prose (wiki / newsletter body) ===== */
|
|
.forum-embed {
|
|
/* The image a Team-forum post's URL renders as, in `remote`/`uploads` mode.
|
|
Emitted by the server (utils/forumHtml.js), never by an author — which is
|
|
what makes the operator's image policy enforceable. Block, so it sits
|
|
beneath its link rather than beside it; capped, because a remote image is
|
|
whatever size its host decided and one post must not blow out the column. */
|
|
display: block;
|
|
margin-top: 8px;
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: var(--radius-input);
|
|
}
|
|
.prose {
|
|
color: var(--text);
|
|
font-size: 1.06rem;
|
|
}
|
|
.prose h2 {
|
|
margin: 32px 0 12px;
|
|
font-family: var(--display);
|
|
font-weight: 600;
|
|
font-size: 1.6rem;
|
|
color: var(--head);
|
|
scroll-margin-top: 90px;
|
|
}
|
|
.prose h3 {
|
|
margin: 24px 0 10px;
|
|
font-family: var(--display);
|
|
font-weight: 600;
|
|
font-size: 1.25rem;
|
|
color: var(--head);
|
|
}
|
|
.prose p {
|
|
margin: 0 0 18px;
|
|
}
|
|
.prose ul,
|
|
.prose ol {
|
|
margin: 0 0 18px;
|
|
padding-left: 22px;
|
|
}
|
|
.prose li {
|
|
margin-bottom: 8px;
|
|
}
|
|
.prose a {
|
|
color: var(--accent);
|
|
}
|
|
.prose strong {
|
|
color: #dbe2ea;
|
|
}
|
|
.prose img {
|
|
max-width: 100%;
|
|
border-radius: var(--radius-input);
|
|
border: 1px solid var(--line);
|
|
}
|
|
|
|
/* ===== Rich text editor (TipTap) ===== */
|
|
.rte {
|
|
position: relative;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
background: var(--bg);
|
|
}
|
|
.rte:focus-within {
|
|
border-color: var(--accent);
|
|
}
|
|
.rte-toolbar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 10px 12px;
|
|
border-bottom: 1px solid var(--line);
|
|
background: var(--panel-flat);
|
|
}
|
|
.rte-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 38px;
|
|
height: 38px;
|
|
padding: 0 11px;
|
|
border: 1px solid transparent;
|
|
border-radius: 7px;
|
|
background: transparent;
|
|
color: var(--muted);
|
|
font-family: var(--sans);
|
|
font-size: 1rem;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
|
}
|
|
/* Icon (glyph) buttons render larger so picture/link/etc. read clearly. */
|
|
.rte-btn[title="Link"],
|
|
.rte-btn[title="Insert image"],
|
|
.rte-btn[title="Link to another wiki page"],
|
|
.rte-btn[title="Quote"],
|
|
.rte-btn[title="Divider"],
|
|
.rte-btn[title="Undo"],
|
|
.rte-btn[title="Redo"] {
|
|
font-size: 1.25rem;
|
|
}
|
|
.rte-btn:hover:not([disabled]) {
|
|
background: var(--blue);
|
|
color: var(--ink);
|
|
}
|
|
.rte-btn.is-active {
|
|
background: var(--blue);
|
|
border-color: var(--accent);
|
|
color: var(--accent-bright);
|
|
}
|
|
.rte-btn[disabled] {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
.rte-sep {
|
|
width: 1px;
|
|
align-self: stretch;
|
|
margin: 2px 4px;
|
|
background: var(--line);
|
|
}
|
|
.rte-content {
|
|
padding: 16px 18px;
|
|
max-height: min(640px, 65vh);
|
|
/* Scroll both vertically and sideways (wide images, code blocks, tables). */
|
|
overflow: auto;
|
|
}
|
|
.rte-content .ProseMirror {
|
|
min-height: 320px;
|
|
outline: none;
|
|
}
|
|
.rte-content .ProseMirror p.is-editor-empty:first-child::before {
|
|
content: attr(data-placeholder);
|
|
float: left;
|
|
height: 0;
|
|
color: var(--dim);
|
|
pointer-events: none;
|
|
}
|
|
/* Internal-link picker popover */
|
|
.rte-linkmenu {
|
|
position: absolute;
|
|
z-index: 20;
|
|
top: 60px;
|
|
left: 12px;
|
|
width: min(360px, calc(100% - 20px));
|
|
padding: 10px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
background: var(--panel-a);
|
|
box-shadow: var(--shadow-card);
|
|
}
|
|
.rte-linkmenu-list {
|
|
margin-top: 8px;
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.rte-linkmenu-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: baseline;
|
|
gap: 12px;
|
|
padding: 7px 9px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
color: var(--text);
|
|
font-family: var(--sans);
|
|
font-size: 0.88rem;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
.rte-linkmenu-item:hover {
|
|
background: var(--blue);
|
|
color: var(--ink);
|
|
}
|
|
.rte-linkmenu-slug {
|
|
color: var(--dim);
|
|
font-family: ui-monospace, Menlo, monospace;
|
|
font-size: 0.76rem;
|
|
}
|
|
|
|
/* ===== Wiki connectivity (tags + red links) ===== */
|
|
.wiki-tag {
|
|
display: inline-block;
|
|
padding: 3px 10px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-pill);
|
|
background: rgba(127, 153, 189, 0.1);
|
|
color: var(--accent);
|
|
font-family: var(--sans);
|
|
font-size: 0.78rem;
|
|
text-decoration: none;
|
|
}
|
|
.wiki-tag:hover {
|
|
border-color: var(--accent);
|
|
background: var(--blue);
|
|
}
|
|
.prose a.wiki-red-link {
|
|
color: #d98b84;
|
|
border-bottom: 1px dotted #d98b84;
|
|
}
|
|
|
|
/* ===== Wiki revision history ===== */
|
|
.wiki-history {
|
|
display: grid;
|
|
grid-template-columns: 240px 1fr;
|
|
gap: 18px;
|
|
align-items: start;
|
|
}
|
|
@media (max-width: 640px) {
|
|
.wiki-history {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
.wiki-history-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
max-height: 360px;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.wiki-rev {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
width: 100%;
|
|
padding: 8px 10px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
background: var(--panel-flat);
|
|
color: var(--text);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
.wiki-rev:hover {
|
|
border-color: var(--accent);
|
|
}
|
|
.wiki-rev.is-active {
|
|
border-color: var(--accent);
|
|
background: var(--blue);
|
|
}
|
|
.wiki-rev-note {
|
|
font-family: var(--sans);
|
|
font-size: 0.86rem;
|
|
color: var(--head);
|
|
}
|
|
.wiki-rev-latest {
|
|
color: var(--accent);
|
|
font-size: 0.74rem;
|
|
}
|
|
.wiki-rev-meta {
|
|
font-family: var(--sans);
|
|
font-size: 0.72rem;
|
|
color: var(--dim);
|
|
}
|
|
.wiki-diff {
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
font-family: var(--sans);
|
|
font-size: 0.92rem;
|
|
line-height: 1.6;
|
|
color: var(--text);
|
|
max-height: 360px;
|
|
overflow-y: auto;
|
|
padding: 12px 14px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
background: var(--bg);
|
|
}
|
|
.diff-add {
|
|
background: rgba(95, 185, 138, 0.22);
|
|
color: #b9e6cd;
|
|
}
|
|
.diff-del {
|
|
background: rgba(176, 102, 95, 0.24);
|
|
color: #e3b0aa;
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
/* ===== Hero canvas editor ===== */
|
|
/* Rich-text hero line (e.g. the homepage teaser). Inherits the line's font/color
|
|
from its inline style; collapse the editor's outer block margins so spacing is
|
|
driven by the line's own marginTop rather than a nested <p>. */
|
|
.hero-rich > :first-child {
|
|
margin-top: 0;
|
|
}
|
|
.hero-rich > :last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
.hero-rich a {
|
|
color: inherit;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.hero-el-editable {
|
|
outline: 1px dashed rgba(127, 153, 189, 0.45);
|
|
outline-offset: 2px;
|
|
user-select: none;
|
|
touch-action: none; /* let Pointer Events drive drag on touch */
|
|
}
|
|
.hero-el-editable:hover {
|
|
outline-color: var(--accent);
|
|
}
|
|
.hero-el-editable.is-selected {
|
|
outline: 2px solid var(--accent);
|
|
}
|
|
.hero-canvas-grid {
|
|
background-image:
|
|
linear-gradient(to right, rgba(127, 153, 189, 0.18) 1px, transparent 1px),
|
|
linear-gradient(to bottom, rgba(127, 153, 189, 0.18) 1px, transparent 1px);
|
|
}
|
|
|
|
/* ===== Admin tables ===== */
|
|
.adm-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
.adm-th {
|
|
text-align: left;
|
|
padding: 11px 14px;
|
|
color: var(--accent);
|
|
font-family: var(--sans);
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.adm-td {
|
|
padding: 13px 14px;
|
|
color: var(--text);
|
|
font-family: var(--sans);
|
|
font-size: 0.88rem;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
vertical-align: middle;
|
|
}
|
|
.badge {
|
|
border-radius: var(--radius-pill);
|
|
padding: 3px 11px;
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.04em;
|
|
font-family: var(--sans);
|
|
white-space: nowrap;
|
|
}
|
|
.badge-pub {
|
|
background: rgba(95, 185, 138, 0.16);
|
|
color: #7fd0a4;
|
|
border: 1px solid rgba(95, 185, 138, 0.4);
|
|
}
|
|
.badge-draft {
|
|
background: rgba(127, 153, 189, 0.14);
|
|
color: #9fb0c6;
|
|
border: 1px solid var(--line);
|
|
}
|
|
.badge-admin {
|
|
background: rgba(216, 226, 239, 0.12);
|
|
color: #d8e2ef;
|
|
border: 1px solid #3a4a5e;
|
|
}
|
|
.badge-editor {
|
|
background: rgba(127, 153, 189, 0.1);
|
|
color: var(--muted);
|
|
border: 1px solid var(--line);
|
|
}
|
|
.badge-moderator {
|
|
background: rgba(224, 176, 112, 0.12);
|
|
color: #e0b070;
|
|
border: 1px solid rgba(224, 176, 112, 0.4);
|
|
}
|
|
.badge-player {
|
|
background: rgba(126, 196, 156, 0.12);
|
|
color: #7ec49c;
|
|
border: 1px solid rgba(126, 196, 156, 0.4);
|
|
}
|
|
/* Action-type badges for the moderation dashboard. */
|
|
.badge-ban {
|
|
background: rgba(217, 139, 132, 0.16);
|
|
color: #d98b84;
|
|
border: 1px solid rgba(217, 139, 132, 0.4);
|
|
}
|
|
.badge-kick,
|
|
.badge-mute,
|
|
.badge-warn {
|
|
background: rgba(224, 176, 112, 0.12);
|
|
color: #e0b070;
|
|
border: 1px solid rgba(224, 176, 112, 0.4);
|
|
}
|
|
.badge-auto {
|
|
background: rgba(127, 153, 189, 0.14);
|
|
color: #9fb0c6;
|
|
border: 1px solid var(--line);
|
|
}
|
|
.link-accent {
|
|
color: var(--accent);
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
.link-accent:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* ===== Misc ===== */
|
|
.moon {
|
|
display: inline-block;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle at 35% 30%, #eef3f8, #9fb0c6 55%, #5d6e88);
|
|
}
|
|
.muted {
|
|
color: var(--muted);
|
|
}
|
|
.dim {
|
|
color: var(--dim);
|
|
}
|
|
.center {
|
|
text-align: center;
|
|
}
|
|
.spin {
|
|
width: 26px;
|
|
height: 26px;
|
|
border: 2px solid var(--line);
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
animation: spin 0.7s linear infinite;
|
|
}
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
.admin-grid {
|
|
min-height: 100vh;
|
|
display: grid;
|
|
grid-template-columns: 236px 1fr;
|
|
}
|
|
@media (max-width: 760px) {
|
|
.admin-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
/* Admin sidebar — collapsible category sections */
|
|
.admin-nav-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
margin-top: 6px;
|
|
}
|
|
.admin-nav-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
padding: 6px 14px 4px;
|
|
border: 0;
|
|
background: transparent;
|
|
color: var(--dim);
|
|
font-size: 0.66rem;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
cursor: pointer;
|
|
transition: color 0.15s;
|
|
}
|
|
.admin-nav-head:hover {
|
|
color: var(--muted);
|
|
}
|
|
.admin-nav-chev {
|
|
transition: transform 0.15s ease;
|
|
flex: 0 0 auto;
|
|
}
|
|
.admin-nav-items {
|
|
padding-left: 6px;
|
|
}
|
|
.admin-nav-link > span {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.admin-nav-link svg {
|
|
flex: 0 0 16px;
|
|
}
|
|
.wiki-grid {
|
|
display: grid;
|
|
grid-template-columns: 230px 1fr;
|
|
gap: 44px;
|
|
align-items: start;
|
|
}
|
|
@media (max-width: 860px) {
|
|
.wiki-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* ===== CMS page builder — public block rendering ===== */
|
|
/* Vertical rhythm between top-level blocks on a rendered page. */
|
|
.page-blocks > * + * {
|
|
margin-top: 26px;
|
|
}
|
|
.page-heading {
|
|
font-family: var(--serif, Georgia, serif);
|
|
color: var(--ink);
|
|
line-height: 1.2;
|
|
}
|
|
.page-divider {
|
|
border: none;
|
|
border-top: 1px solid var(--line);
|
|
margin: 8px 0;
|
|
}
|
|
/* image block */
|
|
.page-image {
|
|
margin: 0;
|
|
}
|
|
.page-image img {
|
|
max-width: 100%;
|
|
border-radius: var(--radius-input);
|
|
border: 1px solid var(--line);
|
|
display: block;
|
|
}
|
|
.page-image figcaption {
|
|
margin-top: 8px;
|
|
color: var(--muted);
|
|
font-family: var(--sans);
|
|
font-size: 0.82rem;
|
|
}
|
|
.page-image--center {
|
|
text-align: center;
|
|
}
|
|
.page-image--center img,
|
|
.page-image--center figcaption {
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
.page-image--right {
|
|
text-align: right;
|
|
}
|
|
.page-image--right img,
|
|
.page-image--right figcaption {
|
|
margin-left: auto;
|
|
}
|
|
.page-image--full img {
|
|
width: 100%;
|
|
}
|
|
/* cta block */
|
|
.page-cta-wrap {
|
|
display: flex;
|
|
}
|
|
.page-cta--secondary {
|
|
background: transparent;
|
|
color: var(--ink);
|
|
}
|
|
/* quote block */
|
|
.page-quote {
|
|
margin: 0;
|
|
border-left: 3px solid var(--accent);
|
|
padding: 4px 0 4px 20px;
|
|
}
|
|
.page-quote blockquote {
|
|
margin: 0;
|
|
font-size: 1.15rem;
|
|
line-height: 1.5;
|
|
color: var(--ink);
|
|
font-style: italic;
|
|
}
|
|
.page-quote figcaption {
|
|
margin-top: 8px;
|
|
color: var(--muted);
|
|
font-family: var(--sans);
|
|
font-size: 0.85rem;
|
|
}
|
|
/* two-column block */
|
|
.page-two-column {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 32px;
|
|
}
|
|
.page-column > * + * {
|
|
margin-top: 18px;
|
|
}
|
|
@media (max-width: 720px) {
|
|
.page-two-column {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* ===== CMS page builder — column sub-block editor ===== */
|
|
.pb-two-column-editor {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 16px;
|
|
}
|
|
@media (max-width: 720px) {
|
|
.pb-two-column-editor {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
.pb-column-editor {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
padding: 12px;
|
|
background: var(--panel-flat, transparent);
|
|
}
|
|
.pb-column-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.pb-add-select {
|
|
width: auto;
|
|
padding: 6px 10px;
|
|
font-size: 0.82rem;
|
|
}
|
|
.pb-subblock {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-input);
|
|
padding: 10px;
|
|
margin-top: 10px;
|
|
background: var(--bg);
|
|
}
|
|
.pb-subblock-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
.pb-subblock-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
.pb-mini {
|
|
min-width: 28px;
|
|
padding: 3px 8px;
|
|
font-size: 0.8rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* ===== CMS page builder — admin canvas ===== */
|
|
.pb-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 5;
|
|
padding: 10px 0;
|
|
background: var(--bg);
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.pb-error {
|
|
border: 1px solid #6e3b38;
|
|
background: rgba(110, 59, 56, 0.16);
|
|
color: #e6a9a3;
|
|
border-radius: var(--radius-input);
|
|
padding: 10px 14px;
|
|
margin-top: 14px;
|
|
font-size: 0.86rem;
|
|
}
|
|
.pb-notice {
|
|
border: 1px solid var(--accent);
|
|
background: var(--blue);
|
|
color: var(--accent-bright);
|
|
border-radius: var(--radius-input);
|
|
padding: 8px 14px;
|
|
margin-top: 14px;
|
|
font-size: 0.86rem;
|
|
}
|
|
.pb-tabs {
|
|
display: flex;
|
|
gap: 4px;
|
|
border-bottom: 1px solid var(--line);
|
|
margin-bottom: 18px;
|
|
}
|
|
.pb-tab {
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
color: var(--muted);
|
|
font-family: var(--sans);
|
|
font-size: 0.9rem;
|
|
padding: 10px 16px;
|
|
cursor: pointer;
|
|
}
|
|
.pb-tab.is-active {
|
|
color: var(--ink);
|
|
border-bottom-color: var(--accent);
|
|
}
|
|
.pb-palette {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 12px;
|
|
border: 1px dashed var(--line);
|
|
border-radius: var(--radius-card);
|
|
margin-bottom: 16px;
|
|
}
|
|
.pb-canvas {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
}
|
|
.pb-block-card {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius-card);
|
|
background: var(--panel-flat, transparent);
|
|
}
|
|
.pb-block-card.is-dragging {
|
|
opacity: 0.5;
|
|
}
|
|
.pb-block-card.is-hidden {
|
|
opacity: 0.6;
|
|
}
|
|
.pb-block-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 12px;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.pb-drag {
|
|
cursor: grab;
|
|
color: var(--muted);
|
|
user-select: none;
|
|
}
|
|
.pb-block-body {
|
|
padding: 14px;
|
|
}
|
|
.pb-settings {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
max-width: 720px;
|
|
}
|
|
.pb-danger {
|
|
border-color: #6e3b38;
|
|
color: #d98b84;
|
|
}
|
|
.pb-danger:hover:not([disabled]) {
|
|
background: rgba(110, 59, 56, 0.18);
|
|
border-color: #8a4b47;
|
|
}
|
|
|
|
/* Site footer: powered-by badge left-justified, info centered. */
|
|
.site-footer-inner {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 16px;
|
|
max-width: 1100px;
|
|
margin: 0 auto;
|
|
}
|
|
.site-footer-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex: 0 0 auto;
|
|
}
|
|
.site-footer-badge img {
|
|
height: 48px;
|
|
width: 48px;
|
|
object-fit: contain;
|
|
filter: drop-shadow(0 0 6px rgba(127, 153, 189, 0.35));
|
|
}
|
|
.site-footer-badge span {
|
|
font-size: 0.72rem;
|
|
line-height: 1.25;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: var(--dim);
|
|
text-align: left;
|
|
}
|
|
.site-footer-badge strong {
|
|
color: var(--muted);
|
|
font-weight: 700;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
.site-footer-brand-link {
|
|
text-decoration: none;
|
|
color: inherit;
|
|
}
|
|
.site-footer-brand-link:hover strong {
|
|
color: var(--accent);
|
|
}
|
|
.site-footer-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
text-align: center;
|
|
}
|
|
/* Pin the badge to the left while the info column stays visually centered. */
|
|
@media (min-width: 641px) {
|
|
.site-footer-badge {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
}
|
|
}
|
|
@media (max-width: 640px) {
|
|
.site-footer-inner {
|
|
flex-direction: column;
|
|
}
|
|
.site-footer-badge img {
|
|
height: 40px;
|
|
width: 40px;
|
|
}
|
|
}
|
|
|
|
/* Draft-preview banner on the public renderer. */
|
|
.page-preview-banner {
|
|
border: 1px solid var(--accent);
|
|
background: var(--blue);
|
|
color: var(--accent-bright);
|
|
border-radius: var(--radius-input);
|
|
padding: 8px 14px;
|
|
margin-bottom: 20px;
|
|
font-size: 0.85rem;
|
|
text-align: center;
|
|
}
|