Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a29402d93 | ||
|
|
1a479023d5 |
@@ -0,0 +1,27 @@
|
||||
# Throwaway prototype for #54 — the ornament and frame component set
|
||||
|
||||
Not for merge. Nothing here is theme code; it is the named component set made
|
||||
concrete so it can be looked at.
|
||||
|
||||
- `ornament.css` — the component set itself, standalone, zero images.
|
||||
- `specimen.html` — every named component, at both densities, on one page.
|
||||
- `real-surface.css` — the same components mapped onto real NodeBB markup.
|
||||
- `shots.js` — before/after on the live category list and a 13-post topic.
|
||||
- `shots/` — the images the resolution is judged on.
|
||||
|
||||
The palette is candidate B from #53, injected at runtime (`../_base-plumnoir.css`
|
||||
plus `../candidate-b.css`), so no rebuild is needed to see it.
|
||||
|
||||
Replay:
|
||||
|
||||
```bash
|
||||
# specimen page — needs http, not file://: mask-image on an SVG is blocked by CORS
|
||||
python3 -m http.server 8931 # then open /prototype/ornament/specimen.html
|
||||
|
||||
# real surface
|
||||
cd ../sow-nodebb && nix develop --command make dev-reset && nix develop --command make dev
|
||||
cd - && node prototype/seed.js
|
||||
export PLAYWRIGHT_BROWSERS_PATH=$(nix build --print-out-paths --no-link nixpkgs#playwright-driver.browsers)
|
||||
P=$(nix build --print-out-paths --no-link nixpkgs#playwright-test)
|
||||
NODE_PATH=$P/lib/node_modules nix shell nixpkgs#playwright-test -c node prototype/ornament/shots.js
|
||||
```
|
||||
@@ -0,0 +1,299 @@
|
||||
/* #54 — the ornament and frame component set, as a closed vocabulary.
|
||||
Throwaway. Zero images: every mark here is CSS. Palette = candidate B (#53). */
|
||||
|
||||
/* ---- palette B (from prototype/_base-plumnoir.css + candidate-b.css) ---- */
|
||||
:root {
|
||||
--wg-bg: #150e13;
|
||||
--wg-bg-deep: #0c0810;
|
||||
--wg-panel: #21161c;
|
||||
--wg-panel-2: #180f16;
|
||||
--wg-panel-3: #351e28;
|
||||
--wg-plum: #351e28;
|
||||
--wg-plum-soft: #4a2a38;
|
||||
|
||||
--wg-gold: #c2a35a;
|
||||
--wg-gold-rgb: 194, 163, 90;
|
||||
--wg-gold-lit: #e0c878;
|
||||
--wg-old-gilt: #a8893f;
|
||||
--wg-ledger-ink: #d8c28a;
|
||||
--wg-red: #8e3438;
|
||||
--wg-stamp: #a84a4e;
|
||||
|
||||
--wg-pewter: #9ea7b3;
|
||||
--wg-pewter-rgb: 158, 167, 179;
|
||||
|
||||
--wg-text: #e6e0d6;
|
||||
--wg-text-soft: #b9b2a6;
|
||||
--wg-text-muted: #9ea7b3;
|
||||
|
||||
/* the four dividers, as tokens — this list is closed */
|
||||
--wg-ruling: rgba(var(--wg-pewter-rgb), 0.075);
|
||||
--wg-edge: rgba(var(--wg-pewter-rgb), 0.2);
|
||||
--wg-edge-soft: rgba(var(--wg-pewter-rgb), 0.11);
|
||||
|
||||
--wg-page-width: 1420px;
|
||||
--wg-ledger-line: 1.78rem;
|
||||
--wg-font-display: Cinzel, Georgia, serif;
|
||||
--wg-font-body: Jost, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
1. RAIL — not decoration, a constraint. One left edge, one right edge,
|
||||
every page, every density (#49 One Rail).
|
||||
====================================================================== */
|
||||
.wg-rail {
|
||||
max-width: var(--wg-page-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
2. PANEL — the bounded surface. Two weights, nothing between.
|
||||
Edges Fade (#49): the edge is a masked overlay, strongest at the top,
|
||||
gone by the bottom. No hairline runs corner to corner.
|
||||
====================================================================== */
|
||||
.wg-panel {
|
||||
position: relative;
|
||||
background: var(--wg-panel);
|
||||
border-radius: 0;
|
||||
padding: var(--wg-pad, 1rem);
|
||||
}
|
||||
.wg-panel::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
border: 1px solid var(--wg-edge);
|
||||
-webkit-mask-image: linear-gradient(180deg, #000, rgba(0, 0, 0, 0.12));
|
||||
mask-image: linear-gradient(180deg, #000, rgba(0, 0, 0, 0.12));
|
||||
pointer-events: none;
|
||||
}
|
||||
/* cheap panel: the same shape one step down. No sheen, no fade — flat. */
|
||||
.wg-panel--plain {
|
||||
background: var(--wg-panel-2);
|
||||
}
|
||||
.wg-panel--plain::before {
|
||||
border-color: var(--wg-edge-soft);
|
||||
-webkit-mask-image: none;
|
||||
mask-image: none;
|
||||
}
|
||||
/* velvet sheen — texture #1. Inset only, never an asset. */
|
||||
.wg-panel--velvet {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.025),
|
||||
inset 0 0 34px rgba(96, 32, 68, 0.11),
|
||||
0 10px 26px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
.wg-panel--velvet:hover {
|
||||
background: color-mix(in srgb, var(--wg-panel) 88%, var(--wg-plum-soft));
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
3. PLAQUE — a name plate, not a band. inline-block is the whole idea.
|
||||
Page titles and standing labels. Suppressed where the logo does the job.
|
||||
====================================================================== */
|
||||
.wg-plaque {
|
||||
display: inline-block;
|
||||
background: var(--wg-bg-deep);
|
||||
border: 1px solid var(--wg-edge);
|
||||
padding: 0.35rem 1rem;
|
||||
font-family: var(--wg-font-display);
|
||||
font-weight: 600;
|
||||
font-size: 0.86rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--wg-ledger-ink);
|
||||
}
|
||||
.wg-plaque--gilt {
|
||||
border-color: rgba(var(--wg-gold-rgb), 0.34);
|
||||
}
|
||||
/* the straddling legend: a title sitting on its own panel edge */
|
||||
.wg-legend {
|
||||
position: relative;
|
||||
}
|
||||
.wg-legend > .wg-plaque {
|
||||
position: absolute;
|
||||
top: -1.05rem;
|
||||
left: 1.25rem;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
4. BAND — a strip that spans the rail, edge to edge. Never inset.
|
||||
Shell chrome only: topbar, lintel, footer, thresholds.
|
||||
====================================================================== */
|
||||
.wg-band {
|
||||
position: relative;
|
||||
background: var(--wg-panel-2);
|
||||
padding-block: 0.75rem;
|
||||
}
|
||||
/* LINTEL — divider #3. Gilt rule that bleeds light downward (#48 Warframe). */
|
||||
.wg-band--lintel {
|
||||
border-top: 1px solid rgba(var(--wg-gold-rgb), 0.5);
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
rgba(var(--wg-gold-rgb), 0.1),
|
||||
transparent 32px
|
||||
);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
5. LEDGER — the archive interior. Ruled rows, one line height.
|
||||
====================================================================== */
|
||||
.wg-ledger {
|
||||
background: var(--wg-panel-2);
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.wg-ledger__row {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: baseline;
|
||||
min-height: calc(var(--wg-ledger-line) * 1.6);
|
||||
padding: 0.6rem 1rem;
|
||||
/* RULING — divider #1. Row separation, nothing else. */
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent calc(100% - 1px),
|
||||
var(--wg-ruling) calc(100% - 1px)
|
||||
);
|
||||
/* gilt left mark: this row has something at stake (unread, owned) */
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
.wg-ledger__row--marked {
|
||||
border-left-color: rgba(var(--wg-gold-rgb), 0.34);
|
||||
}
|
||||
.wg-ledger__row:last-child {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
DIVIDERS — the closed list. Four, and no fifth.
|
||||
1. ruling (above, .wg-ledger__row)
|
||||
2. fading rule
|
||||
3. lintel (above, .wg-band--lintel)
|
||||
4. spine
|
||||
====================================================================== */
|
||||
.wg-rule {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: linear-gradient(90deg, var(--wg-edge), transparent);
|
||||
}
|
||||
.wg-spine {
|
||||
position: relative;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.wg-spine::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0.2rem;
|
||||
bottom: 0.2rem;
|
||||
width: 1px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(var(--wg-gold-rgb), 0.18),
|
||||
rgba(var(--wg-gold-rgb), 0.07)
|
||||
);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
SECTION HEADER — one component, two forms.
|
||||
A header earns ornament by switching face and case, not by gaining a
|
||||
background or a flourish (#48 Warframe).
|
||||
====================================================================== */
|
||||
.wg-head {
|
||||
font-family: var(--wg-font-body);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
color: var(--wg-text-soft);
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
.wg-head--earned {
|
||||
font-family: var(--wg-font-display);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--wg-ledger-ink);
|
||||
}
|
||||
/* the one permitted flourish, and only on an earned header: a rule that
|
||||
starts at the text and fades out to the rail. */
|
||||
.wg-head--earned.wg-head--ruled {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
.wg-head--earned.wg-head--ruled::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(var(--wg-gold-rgb), 0.34), transparent);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
ICON — mask over a token, never a coloured image.
|
||||
Pewter structures, gilt marks value (#49 Two Metals).
|
||||
====================================================================== */
|
||||
.wg-icon {
|
||||
display: inline-block;
|
||||
width: var(--wg-icon-size, 20px);
|
||||
height: var(--wg-icon-size, 20px);
|
||||
background-color: var(--wg-pewter);
|
||||
-webkit-mask: var(--wg-icon-src) center / contain no-repeat;
|
||||
mask: var(--wg-icon-src) center / contain no-repeat;
|
||||
vertical-align: -0.2em;
|
||||
}
|
||||
.wg-icon--gilt {
|
||||
background-color: var(--wg-gold);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
HORIZON — texture #2. The city at night instead of hero art.
|
||||
Showcase only. Radial, warm, never cyan (#49 No Machines).
|
||||
====================================================================== */
|
||||
.wg-horizon {
|
||||
background:
|
||||
radial-gradient(
|
||||
120% 80% at 50% 100%,
|
||||
rgba(var(--wg-gold-rgb), 0.12),
|
||||
transparent 62%
|
||||
),
|
||||
linear-gradient(180deg, var(--wg-bg-deep), var(--wg-bg));
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
DENSITY — one shell, two densities. Ornament weight and row spacing
|
||||
only. Width never changes (One Rail).
|
||||
====================================================================== */
|
||||
.wg-showcase {
|
||||
--wg-pad: 1.5rem;
|
||||
--wg-icon-size: 28px;
|
||||
}
|
||||
.wg-app {
|
||||
--wg-pad: 0.75rem;
|
||||
--wg-icon-size: 20px;
|
||||
}
|
||||
/* app density: sheen off, plaques off except the page title, headers reduced */
|
||||
.wg-app .wg-panel--velvet {
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
.wg-app .wg-band--lintel {
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
rgba(var(--wg-gold-rgb), 0.06),
|
||||
transparent 18px
|
||||
);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
STAMP — the only red. A state or a mark of loss, never decoration.
|
||||
====================================================================== */
|
||||
.wg-stamp {
|
||||
font-family: var(--wg-font-display);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--wg-stamp);
|
||||
border: 1px solid rgba(168, 74, 78, 0.45);
|
||||
padding: 0.1rem 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/* #54 — the component set applied to one real surface: the category list and
|
||||
a topic, on the live stack. Injected at runtime over palette B. Throwaway.
|
||||
|
||||
Every rule below is one of the named components doing its job on real
|
||||
NodeBB markup. Nothing new is invented here. */
|
||||
|
||||
/* ---- SQUARE. One shape, every surface. A cut or rounded corner is a second
|
||||
shape language and the shell only gets one. ---- */
|
||||
/* specificity has to beat the theme's own `border-radius: 8px !important` */
|
||||
html body [class],
|
||||
html body [class]::before,
|
||||
html body [class]::after,
|
||||
html body [class] [class],
|
||||
html body [class] [class][class],
|
||||
html body blockquote,
|
||||
html body pre,
|
||||
html body code,
|
||||
html body img,
|
||||
html body input,
|
||||
html body textarea,
|
||||
html body button {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
/* Signets stay square like everything else — the theme's own signet plate is
|
||||
the settled treatment and it is square. Only the status dot is round,
|
||||
because it is a dot. */
|
||||
html body [class] [class].status,
|
||||
html body .status {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
/* ---- PANEL: edges fade. No hairline runs corner to corner, but the edge is
|
||||
present enough to hold the panel off the page. ---- */
|
||||
li[component="categories/category"],
|
||||
li[component="category/topic"],
|
||||
.westgate-surface,
|
||||
.card,
|
||||
ul.categories-list,
|
||||
ul.westgate-categories-list,
|
||||
[component="post/content"],
|
||||
.post-container {
|
||||
border-color: transparent !important;
|
||||
position: relative;
|
||||
}
|
||||
ul.categories-list::after,
|
||||
ul.westgate-categories-list::after,
|
||||
.westgate-surface::after,
|
||||
.post-container::after,
|
||||
body.template-category .category-header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid rgba(var(--wg-pewter-rgb), 0.34);
|
||||
-webkit-mask-image: linear-gradient(180deg, #000, rgba(0, 0, 0, 0.16));
|
||||
mask-image: linear-gradient(180deg, #000, rgba(0, 0, 0, 0.16));
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* ---- A square edge with no falloff reads as a cut, not an edge. Every
|
||||
bounded surface sits on a wide, offset-down black shadow: no lift, no
|
||||
stacking claim, just the corner catching the room (Candlelight). ---- */
|
||||
html body ul.categories-list,
|
||||
html body ul.westgate-categories-list,
|
||||
html body .westgate-surface,
|
||||
html body .post-container,
|
||||
html body .topic-list-header,
|
||||
html body [data-widget-area] .card,
|
||||
html body.template-category .category-header {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.02),
|
||||
0 10px 26px rgba(0, 0, 0, 0.34),
|
||||
0 2px 4px rgba(0, 0, 0, 0.28) !important;
|
||||
}
|
||||
|
||||
/* ---- LEDGER: rows are ruled, not boxed. The category list is a ledger:
|
||||
one surface, ruled rows, no gaps and no per-row card. ---- */
|
||||
ul.categories-list,
|
||||
ul.westgate-categories-list {
|
||||
gap: 0 !important;
|
||||
background: var(--wg-panel-2);
|
||||
}
|
||||
ul.categories-list li[component="categories/category"],
|
||||
ul.westgate-categories-list li[component="categories/category"] {
|
||||
border-radius: 0 !important;
|
||||
background: none !important;
|
||||
/* RULING — the only separator inside a ledger */
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent calc(100% - 1px),
|
||||
var(--wg-ledger-ruling) calc(100% - 1px)
|
||||
) !important;
|
||||
}
|
||||
ul.categories-list li[component="categories/category"]::after,
|
||||
ul.westgate-categories-list li[component="categories/category"]::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* ---- SECTION HEADER: earned form = display face + uppercase + one colour
|
||||
token + the single permitted flourish, a gilt rule fading to the rail. ---- */
|
||||
li[component="categories/category"] h2.title,
|
||||
.category-header h1,
|
||||
[data-widget-area] .card-header {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
font-family: var(--wg-font-display) !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.08em !important;
|
||||
color: var(--wg-ledger-ink) !important;
|
||||
background: none !important;
|
||||
}
|
||||
li[component="categories/category"] h2.title::after,
|
||||
.category-header h1::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(var(--wg-gold-rgb), 0.34), transparent);
|
||||
}
|
||||
|
||||
/* ---- CASE: Cinzel is always uppercase. One rule, no exceptions, because
|
||||
its lowercase is a small-caps design and reads as a weaker version of the
|
||||
same thing rather than a second voice. ---- */
|
||||
html body [class] h1,
|
||||
html body [class] h2,
|
||||
html body [class] h3,
|
||||
html body .topic-title,
|
||||
html body .breadcrumb {
|
||||
text-transform: uppercase !important;
|
||||
}
|
||||
|
||||
/* ---- PLAQUE: the page title becomes a name plate, not a band. ---- */
|
||||
body.template-category .category-header .category-title,
|
||||
body.template-topic .topic-title h1 {
|
||||
display: inline-block;
|
||||
background: var(--wg-bg-deep);
|
||||
border: 1px solid var(--wg-border);
|
||||
padding: 0.35rem 1rem;
|
||||
font-family: var(--wg-font-display);
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--wg-ledger-ink);
|
||||
}
|
||||
|
||||
/* ---- ICON: pewter is structure. Gilt is reserved for what can be touched. ---- */
|
||||
li[component="categories/category"] .icon [class*="wg-icon-"],
|
||||
li[component="categories/category"] [class*="wg-icon-"] {
|
||||
background-color: var(--wg-pewter) !important;
|
||||
}
|
||||
/* The category icon plate keeps its plum wash and desaturation: ACP colours
|
||||
arrive candy-bright and the existing treatment is what pulls them into the
|
||||
register. Only its ring moves from gilt to pewter — the plate structures,
|
||||
it does not mark value. */
|
||||
li[component="categories/category"] .icon {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||||
inset 0 -3px 7px rgba(0, 0, 0, 0.52),
|
||||
0 0 0 1px rgba(var(--wg-pewter-rgb), 0.24) !important;
|
||||
}
|
||||
|
||||
/* ---- LINTEL: the topbar is a threshold, and says so. ---- */
|
||||
.wg-topbar,
|
||||
#header-menu {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
.wg-topbar {
|
||||
border-top: 1px solid rgba(var(--wg-gold-rgb), 0.5) !important;
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
rgba(var(--wg-gold-rgb), 0.1),
|
||||
transparent 32px
|
||||
) !important;
|
||||
}
|
||||
|
||||
/* ---- APP DENSITY: sheen off. Hierarchy is tone plus the two metals. ---- */
|
||||
li[component="categories/category"],
|
||||
li[component="category/topic"] {
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.42) !important;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Throwaway screenshotter for #54. Palette B is the ground; the only variable
|
||||
// is whether the ornament component set is applied.
|
||||
// PLAYWRIGHT_BROWSERS_PATH=... nix shell nixpkgs#playwright-test -c node prototype/ornament/shots.js
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const BASE = process.env.BASE || 'http://localhost:4567';
|
||||
const OUT = path.join(__dirname, 'shots');
|
||||
const read = (f) => fs.readFileSync(path.join(__dirname, f), 'utf8');
|
||||
const paletteB = read('../_base-plumnoir.css') + read('../candidate-b.css');
|
||||
const ornament = read('real-surface.css');
|
||||
|
||||
const VARIANTS = { before: paletteB, after: paletteB + ornament };
|
||||
const PAGES = [
|
||||
['categories', '/categories'],
|
||||
['category', '/category/8'],
|
||||
['topic', '/topic/2'],
|
||||
];
|
||||
|
||||
(async () => {
|
||||
fs.mkdirSync(OUT, { recursive: true });
|
||||
const browser = await chromium.launch();
|
||||
const ctx = await browser.newContext({ viewport: { width: 1600, height: 1000 }, deviceScaleFactor: 2 });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
await page.goto(BASE + '/login');
|
||||
await page.fill('#username', 'admin');
|
||||
await page.fill('#password', 'Admin12345!');
|
||||
await page.click('#login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
for (const [name, css] of Object.entries(VARIANTS)) {
|
||||
for (const [pname, url] of PAGES) {
|
||||
await page.goto(BASE + url, { waitUntil: 'networkidle' });
|
||||
await page.addStyleTag({ content: css });
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: path.join(OUT, `${pname}-${name}.png`), fullPage: true });
|
||||
await page.screenshot({
|
||||
path: path.join(OUT, `${pname}-${name}-crop.png`),
|
||||
clip: { x: 120, y: 60, width: 1060, height: 620 },
|
||||
});
|
||||
console.log(pname, name);
|
||||
}
|
||||
}
|
||||
await browser.close();
|
||||
})();
|
||||
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 559 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 635 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 421 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 418 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 962 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 969 KiB |
@@ -0,0 +1,272 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>#54 — ornament and frame specimen (zero images)</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: Cinzel;
|
||||
src: url("../../public/fonts/cinzel/cinzel-latin-600-normal.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Jost;
|
||||
src: url("../../public/fonts/jost/jost-latin-400-normal.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Jost;
|
||||
src: url("../../public/fonts/jost/jost-latin-600-normal.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="ornament.css" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--wg-bg);
|
||||
color: var(--wg-text-soft);
|
||||
font-family: var(--wg-font-body);
|
||||
line-height: 1.55;
|
||||
}
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--wg-font-display);
|
||||
color: var(--wg-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
section {
|
||||
padding-block: 2.5rem;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.note {
|
||||
color: var(--wg-text-muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
a {
|
||||
color: var(--wg-gold);
|
||||
}
|
||||
.swatchrow {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- SHELL: identical in both densities. Band + lintel + rail. -->
|
||||
<div class="wg-band">
|
||||
<div class="wg-rail" style="display: flex; gap: 1.5rem; align-items: center">
|
||||
<span
|
||||
style="
|
||||
font-family: var(--wg-font-display);
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--wg-ledger-ink);
|
||||
"
|
||||
>Shadows Over Westgate</span
|
||||
>
|
||||
<span class="note">topbar — same band, same rail, every page</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wg-band wg-band--lintel wg-horizon" style="padding-block: 3rem">
|
||||
<div class="wg-rail">
|
||||
<h1 style="margin: 0 0 0.5rem; font-size: 2.6rem">The City and Its Ledger</h1>
|
||||
<p class="note" style="margin: 0">
|
||||
LINTEL + HORIZON — the threshold into the page. Zero images: the glow is a radial
|
||||
gradient, the rule bleeds 32px of light downward.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="wg-rail">
|
||||
<!-- ================= SHOWCASE DENSITY ================= -->
|
||||
<section class="wg-showcase">
|
||||
<h2 class="wg-head wg-head--earned wg-head--ruled">Showcase density</h2>
|
||||
|
||||
<p class="note">
|
||||
Earned header: display face, uppercase, one colour token, and the single permitted
|
||||
flourish — a gilt rule that starts at the text and fades to the rail.
|
||||
</p>
|
||||
|
||||
<div class="grid" style="margin-top: 1.5rem">
|
||||
<div class="wg-panel wg-panel--velvet">
|
||||
<h3 class="wg-head wg-head--earned" style="font-size: 0.95rem">Panel</h3>
|
||||
<p>
|
||||
The bounded surface. Its edge is a masked overlay — full strength at the top, gone
|
||||
by the bottom. No hairline runs corner to corner.
|
||||
</p>
|
||||
</div>
|
||||
<div class="wg-panel wg-panel--plain">
|
||||
<h3 class="wg-head" style="font-size: 0.95rem">Plain panel</h3>
|
||||
<p>
|
||||
The cheap panel. One tone down, softer edge, no sheen, no fade. This is what a
|
||||
panel looks like when it has nothing to say.
|
||||
</p>
|
||||
</div>
|
||||
<div class="wg-panel wg-panel--velvet wg-legend">
|
||||
<span class="wg-plaque">Plaque</span>
|
||||
<p style="margin-top: 0.75rem">
|
||||
A name plate, not a band — <code>inline-block</code> is the whole idea. As a
|
||||
straddling legend it sits on its own panel edge.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="wg-head wg-head--earned wg-head--ruled" style="margin-top: 2.5rem">
|
||||
The divider inventory
|
||||
</h2>
|
||||
<p class="note">Four. There is no fifth. Anything else is a habit, not a divider.</p>
|
||||
|
||||
<div class="wg-panel wg-panel--plain" style="margin-top: 1rem">
|
||||
<p><strong>1. Ruling</strong> — row separation inside a ledger, and nowhere else.</p>
|
||||
<div class="wg-ledger" style="margin: 0.75rem 0 1.5rem">
|
||||
<div class="wg-ledger__row wg-ledger__row--marked">
|
||||
<span style="color: var(--wg-ledger-ink)">A marked row</span>
|
||||
<span class="note" style="margin-left: auto">14 replies · 2h</span>
|
||||
</div>
|
||||
<div class="wg-ledger__row">
|
||||
<span>An ordinary row</span>
|
||||
<span class="note" style="margin-left: auto">3 replies · 5h</span>
|
||||
</div>
|
||||
<div class="wg-ledger__row">
|
||||
<span>Another ordinary row</span>
|
||||
<span class="note" style="margin-left: auto">no replies · 1d</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>2. Fading rule</strong> — between sections. It ends by fading, not by stopping.</p>
|
||||
<hr class="wg-rule" style="margin: 0.75rem 0 1.5rem" />
|
||||
|
||||
<p><strong>3. Lintel</strong> — a threshold. One per band, at the top of the page shell.</p>
|
||||
<div class="wg-band wg-band--lintel" style="margin: 0.75rem 0 1.5rem; padding: 1rem">
|
||||
<span class="note">the band below a lintel</span>
|
||||
</div>
|
||||
|
||||
<p><strong>4. Spine</strong> — a vertical fade marking a sequence: a thread of posts.</p>
|
||||
<div class="wg-spine" style="margin-top: 0.75rem">
|
||||
<p style="margin: 0">First reply in the thread.</p>
|
||||
<p style="margin: 0.5rem 0 0">Second reply, same spine.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="wg-head wg-head--earned wg-head--ruled" style="margin-top: 2.5rem">
|
||||
Icons, stamps, and the two metals
|
||||
</h2>
|
||||
<div class="wg-panel wg-panel--velvet" style="margin-top: 1rem">
|
||||
<div class="swatchrow">
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/scroll-unfurled.svg')"
|
||||
></span>
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/tavern-sign.svg')"
|
||||
></span>
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/duality-mask.svg')"
|
||||
></span>
|
||||
<span class="note">pewter: structure and identity</span>
|
||||
<span
|
||||
class="wg-icon wg-icon--gilt"
|
||||
style="--wg-icon-src: url('../../public/game-icons/wax-seal.svg')"
|
||||
></span>
|
||||
<span class="note">gilt: this matters, or can be touched</span>
|
||||
<span class="wg-stamp">Closed</span>
|
||||
<span class="note">the only red</span>
|
||||
</div>
|
||||
<p class="note" style="margin-bottom: 0">
|
||||
Every icon is a mask over a token, never a coloured file — one asset serves every
|
||||
state and forced-colors mode.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="wg-rule" />
|
||||
|
||||
<!-- ================= APP DENSITY ================= -->
|
||||
<section class="wg-app">
|
||||
<h2 class="wg-head wg-head--earned wg-head--ruled">App density — same rail, same shell</h2>
|
||||
<p class="note">
|
||||
Identical width, identical components. Sheen off, rows tighter, headers reduced, icons
|
||||
one step smaller. Nothing here is a different component; it is the same set turned down.
|
||||
</p>
|
||||
|
||||
<div style="display: flex; gap: 1rem; align-items: baseline; margin: 1rem 0">
|
||||
<span class="wg-plaque">Roleplay</span>
|
||||
<span class="note">the page title keeps its plaque; nothing else gets one</span>
|
||||
</div>
|
||||
|
||||
<div class="wg-ledger">
|
||||
<div class="wg-ledger__row wg-ledger__row--marked">
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/stabbed-note.svg')"
|
||||
></span>
|
||||
<span style="color: var(--wg-text)">Notices from the Night Market</span>
|
||||
<span class="note" style="margin-left: auto">42 · 12m</span>
|
||||
</div>
|
||||
<div class="wg-ledger__row">
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/mute.svg')"
|
||||
></span>
|
||||
<span>Rumors heard near the docks</span>
|
||||
<span class="note" style="margin-left: auto">9 · 1h</span>
|
||||
</div>
|
||||
<div class="wg-ledger__row">
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/sword-altar.svg')"
|
||||
></span>
|
||||
<span>Journal of a compromised clerk</span>
|
||||
<span class="note" style="margin-left: auto">3 · 6h</span>
|
||||
</div>
|
||||
<div class="wg-ledger__row">
|
||||
<span
|
||||
class="wg-icon"
|
||||
style="--wg-icon-src: url('../../public/game-icons/cowled.svg')"
|
||||
></span>
|
||||
<span>Faction standing, second quarter</span>
|
||||
<span class="note" style="margin-left: auto">27 · 1d</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="wg-head" style="margin-top: 1.5rem">A reduced header (has not earned ornament)</h3>
|
||||
<div class="wg-panel wg-panel--plain">
|
||||
<p style="margin: 0">
|
||||
Body face, no case switch, no rule, no background. The reduced header is the default;
|
||||
ornament is the exception that has to be argued for.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="wg-rule" />
|
||||
|
||||
<section>
|
||||
<h2 class="wg-head wg-head--earned">The standing test</h2>
|
||||
<p class="note" style="max-width: 60ch">
|
||||
This page contains no images of any kind — every frame, rule, plaque, glow, and icon
|
||||
above is CSS or a mask over a token. If this page looks unfinished, the component set
|
||||
is not done.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="wg-band wg-band--lintel" style="margin-top: 3rem; padding-block: 2rem">
|
||||
<div class="wg-rail">
|
||||
<span class="note">Footer — the same band and lintel that opened the page.</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Variant: gilt structures, pewter only speaks.
|
||||
Two Metals inverted from #53's assignment — the metals keep separate jobs,
|
||||
but the visible edge work goes back to gold and pewter is demoted to
|
||||
metadata text only. Answers "there is too much silver now". */
|
||||
:root {
|
||||
--wg-border: rgba(var(--wg-gold-rgb), 0.2);
|
||||
--wg-border-soft: rgba(var(--wg-gold-rgb), 0.11);
|
||||
--wg-ledger-ruling: rgba(var(--wg-gold-rgb), 0.08);
|
||||
--wg-ledger-rule-strong: rgba(var(--wg-gold-rgb), 0.2);
|
||||
--wg-ledger-border: rgba(var(--wg-gold-rgb), 0.24);
|
||||
}
|
||||
/* panel edges, ledger edge, icon ring: gilt */
|
||||
html body ul.categories-list::after,
|
||||
html body ul.westgate-categories-list::after,
|
||||
html body .westgate-surface::after,
|
||||
html body .post-container::after,
|
||||
html body.template-category .category-header::after {
|
||||
border-color: rgba(var(--wg-gold-rgb), 0.28) !important;
|
||||
}
|
||||
html body li[component="categories/category"] .icon {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||||
inset 0 -3px 7px rgba(0, 0, 0, 0.52),
|
||||
0 0 0 1px rgba(var(--wg-gold-rgb), 0.28) !important;
|
||||
}
|
||||
/* category names keep the ledger ink, one step brighter than body */
|
||||
html body li[component="categories/category"] h2.title,
|
||||
html body li[component="categories/category"] h2.title a {
|
||||
color: var(--wg-ledger-ink) !important;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Variant: rows breathe. The category list goes back to separate panels with
|
||||
air between them, instead of one ruled ledger. Same components, same square
|
||||
shape, same fading edge — only the ledger reading is dropped. */
|
||||
html body ul.categories-list,
|
||||
html body ul.westgate-categories-list {
|
||||
gap: var(--wg-space-sm, 0.75rem) !important;
|
||||
background: none !important;
|
||||
}
|
||||
html body ul.categories-list::after,
|
||||
html body ul.westgate-categories-list::after {
|
||||
content: none;
|
||||
}
|
||||
html body ul.categories-list li[component="categories/category"],
|
||||
html body ul.westgate-categories-list li[component="categories/category"] {
|
||||
background: var(--wg-panel-2) !important;
|
||||
background-image: none !important;
|
||||
padding: var(--wg-space-md, 1rem) !important;
|
||||
}
|
||||
/* each row gets its own fading edge, since it is a panel again */
|
||||
html body ul.categories-list li[component="categories/category"]::after,
|
||||
html body ul.westgate-categories-list li[component="categories/category"]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid rgba(var(--wg-pewter-rgb), 0.34);
|
||||
-webkit-mask-image: linear-gradient(180deg, #000, rgba(0, 0, 0, 0.16));
|
||||
mask-image: linear-gradient(180deg, #000, rgba(0, 0, 0, 0.16));
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/* Variant: the ledger row as a line in a record, not three blocks in a row.
|
||||
The data columns move into WELLS; the left side stays prose. */
|
||||
|
||||
/* ---- WELL — a recess, the inverse of Panel. Panel is laid on the surface
|
||||
and casts down; a well is cut into it and shadows inward. Three of them,
|
||||
one per reading, bleeding to the row's right edge. The gap between two
|
||||
wells is their divider: a well never carries a rule inside it. ---- */
|
||||
html body li[component="categories/category"] > div.col-lg-5 {
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
align-items: stretch !important;
|
||||
padding: 0 !important;
|
||||
gap: 0.4rem !important;
|
||||
/* the wells sit inside the row's margin on every side — a well cut flush to
|
||||
the right edge reads as the row running off the page */
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* the stat pair: two equal columns of well, not two little cards */
|
||||
html body li[component="categories/category"] .meta.stats,
|
||||
html body li[component="categories/category"] .stats {
|
||||
display: grid !important;
|
||||
/* the counts need only their own width; the room goes to the teaser */
|
||||
grid-template-columns: 6rem 6rem !important;
|
||||
justify-content: end !important;
|
||||
gap: 0.4rem !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* the well itself */
|
||||
html body li[component="categories/category"] .stats .card,
|
||||
html body li[component="categories/category"] .stats .card-header,
|
||||
html body li[component="categories/category"] .teaser {
|
||||
/* the basin sits below the row it is cut into, but not as dead as the
|
||||
page floor: between panel-2 and bg-deep */
|
||||
background: color-mix(in srgb, var(--wg-panel-2) 45%, var(--wg-bg-deep)) !important;
|
||||
background-image: none !important;
|
||||
border: 0 !important;
|
||||
box-shadow:
|
||||
inset 0 1px 3px rgba(0, 0, 0, 0.55),
|
||||
inset 0 0 0 1px rgba(var(--wg-gold-rgb), 0.1) !important;
|
||||
padding: 0.5rem 0.85rem !important;
|
||||
}
|
||||
/* the teaser is prose, so it gets the room and a proper margin of its own */
|
||||
html body li[component="categories/category"] .teaser {
|
||||
flex: 1 1 auto !important;
|
||||
/* the basin's inset is even on all four sides — a wider left/right inset
|
||||
reads as the text not filling the well */
|
||||
padding: 0.55rem !important;
|
||||
}
|
||||
/* the teaser's inner blocks are flex items once the well is a flex box, so
|
||||
they shrink to their text and stop filling. Make them fill it. */
|
||||
html body li[component="categories/category"] .lastpost,
|
||||
html body li[component="categories/category"] .westgate-lastpost {
|
||||
flex: 1 1 auto !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
html body li[component="categories/category"] [component="category/posts"] {
|
||||
padding-left: 0 !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
/* the byline and its line of prose are one block, not two rows */
|
||||
gap: 0.1rem !important;
|
||||
height: auto !important;
|
||||
line-height: 1.35 !important;
|
||||
}
|
||||
html body li[component="categories/category"] .teaser .text-nowrap {
|
||||
line-height: 1.2 !important;
|
||||
/* the signet's ring is drawn outside its 18px box, and this row is
|
||||
`text-truncate` (overflow: hidden), so at x=0 the left of the ring is cut.
|
||||
Give it the ring's width back. */
|
||||
padding-left: 2px !important;
|
||||
}
|
||||
html body li[component="categories/category"] .stats .card {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
/* the number carries; the label is engraved and small */
|
||||
html body li[component="categories/category"] .stats .card > span:first-child {
|
||||
font-family: var(--wg-font-display) !important;
|
||||
color: var(--wg-text) !important;
|
||||
}
|
||||
html body li[component="categories/category"] .stats .card > span + span,
|
||||
html body li[component="categories/category"] .stats small {
|
||||
font-family: var(--wg-font-display) !important;
|
||||
font-size: 0.78rem !important;
|
||||
letter-spacing: 0.12em !important;
|
||||
text-transform: uppercase !important;
|
||||
color: var(--wg-text-muted) !important;
|
||||
}
|
||||
|
||||
/* the teaser well reads as text, so it is set left and loses NodeBB's
|
||||
inline pure-white rule — a ledger has one ruling weight, not two */
|
||||
html body li[component="categories/category"] .teaser {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
html body li[component="categories/category"] .teaser *,
|
||||
html body li[component="categories/category"] .lastpost,
|
||||
html body li[component="categories/category"] .westgate-lastpost {
|
||||
text-align: left !important;
|
||||
border-left: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
/* the byline row must stretch, or it shrinks to its own text and the
|
||||
`text-truncate` on it starts clipping the signet */
|
||||
html body li[component="categories/category"] [component="category/posts"],
|
||||
html body li[component="categories/category"] .lastpost,
|
||||
html body li[component="categories/category"] .westgate-lastpost {
|
||||
align-items: stretch !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
html body li[component="categories/category"] .teaser .text-nowrap,
|
||||
html body li[component="categories/category"] .teaser .post-content {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
html body li[component="categories/category"] .teaser p,
|
||||
html body li[component="categories/category"] [component="category/posts"] p {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
color: var(--wg-text-muted) !important;
|
||||
}
|
||||
|
||||
/* the left side is prose and stops at its own measure; the wells take the
|
||||
room that was standing empty between the two */
|
||||
html body li[component="categories/category"] > div.col-lg-7 {
|
||||
flex: 0 0 46% !important;
|
||||
max-width: 46% !important;
|
||||
}
|
||||
html body li[component="categories/category"] > div.col-lg-5 {
|
||||
flex: 1 1 auto !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
/* one baseline for the whole row, so rows scan as lines rather than pages.
|
||||
The columns stretch: a centred well cannot grow, so a two-line teaser
|
||||
overflows it top and bottom and gets clipped. */
|
||||
html body li[component="categories/category"] {
|
||||
align-items: stretch !important;
|
||||
}
|
||||
html body li[component="categories/category"] .teaser,
|
||||
html body li[component="categories/category"] .lastpost,
|
||||
html body li[component="categories/category"] .westgate-lastpost,
|
||||
html body li[component="categories/category"] [component="category/posts"] {
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
/* the clamp keeps its own overflow — it is what puts the ellipsis on. But its
|
||||
line box has to be taller than the glyphs, or `overflow: hidden` shaves the
|
||||
ascenders off the first line and the descenders off the last. */
|
||||
html body li[component="categories/category"] .post-content {
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
line-height: 1.5 !important;
|
||||
/* NodeBB's `line-clamp-sm-2` clamps by a hard `max-height` sized to its own
|
||||
line-height. Ours is taller, so that max-height cuts the glyphs. Drop it
|
||||
and clamp by line count instead. */
|
||||
max-height: none !important;
|
||||
display: -webkit-box !important;
|
||||
-webkit-line-clamp: 2 !important;
|
||||
-webkit-box-orient: vertical !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
html body li[component="categories/category"] h2.title {
|
||||
margin-bottom: 0.15rem !important;
|
||||
}
|
||||