Replace the party-board d20 wordmark with the compass-rose monogram lockup

The wordmark row now reads rule · monogram · NestQuest · rule, matching
design/logos/NestQuest_Logo.png. The monogram is an 80px inline SVG (compass
rose, broken ring, d20 hexagon with a knocked-out Cinzel Decorative "N")
filled with the brand gradient; no raster or remote asset. The d20 hexagon
styles and HEXAGON_CLIP are removed from the party board, PANEL-SPEC §2
item 1 describes the new row, and the bundle is rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 09:15:16 -04:00
co-authored by Claude Opus 5.5
parent 9916d83138
commit 6dff670d83
4 changed files with 375 additions and 250 deletions
File diff suppressed because it is too large Load Diff
+8 -4
View File
@@ -22,10 +22,14 @@ Screens `1a`, `1b`, `1j`, `1c` of the mockups. Two cards:
Default view on the wall. Layout top to bottom:
1. **Wordmark row** at y=64. Rule · d20 hexagon · `NestQuest` (66px/900 display, brand
gradient via `background-clip: text`) · d20 · rule. d20 marks are 34px hexagons
(`clip-path: polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)`) filled with
the brand gradient, numeral 15px/700 Cinzel.
1. **Wordmark row** at y=64. Rule · monogram · `NestQuest` (66px/900 Cinzel Decorative
display, brand gradient via `background-clip: text`) · rule — the horizontal lockup of
`design/logos/NestQuest_Logo.png`. The monogram is an 80px inline SVG (no raster, no
remote asset): a compass rose — four long cardinal points, four short diagonal points,
a ring broken by the points — around a d20 hexagon with facet lines and a knocked-out
Cinzel Decorative "N", filled with the brand gradient (`--nq-brand-blue` →
`--nq-brand-purple`, 135°). Gaps are SVG masks, so the mark stays transparent over the
parchment.
2. **Kicker** `The Party · <weekday>, <Month D>` — 21px/600 Cinzel, tracking .30em, uppercase.
3. **Adventurer plates**, 3-up grid, 56px gap, page inset 110px, top y=262. One plate per
child returned by the party sensor, in configured order.
+75 -24
View File
@@ -231,8 +231,76 @@ function conditionPhrase(condition: string, label: string): string {
}
const SHIELD_CLIP = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)";
const HEXAGON_CLIP =
"polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)";
// The brand monogram (design/logos/NestQuest_Logo.png): a compass rose —
// four long cardinal points, four short diagonal points, a ring broken by
// the points — around a d20 hexagon carrying a knocked-out "N". Drawn on
// a 100-unit grid and filled with the brand gradient; the masks cut the
// parchment gaps so the mark stays transparent on any backdrop.
const MONOGRAM_POINTS =
"M50 3 44.5 24.1 50 26 55.5 24.1Z M100 50 75.9 44.5 74 50 75.9 55.5Z " +
"M50 97 55.5 75.9 50 74 44.5 75.9Z M0 50 24.1 55.5 26 50 24.1 44.5Z " +
"M29.1 29.1 29.7 36.3 36.3 29.7Z M70.9 29.1 63.7 29.7 70.3 36.3Z " +
"M70.9 70.9 70.3 63.7 63.7 70.3Z M29.1 70.9 36.3 70.3 29.7 63.7Z";
const MONOGRAM_HEX = "M50 26.5 70.4 38.2 70.4 61.8 50 73.5 29.6 61.8 29.6 38.2Z";
const MONOGRAM_FACETS =
"M29.6 38.2H70.4M29.6 61.8H70.4M50 26.5 36.5 38.2M50 26.5 63.5 38.2" +
"M50 73.5 36.5 61.8M50 73.5 63.5 61.8M36.5 38.2V61.8M63.5 38.2V61.8" +
"M29.6 38.2 36.5 61.8M36.5 38.2 29.6 61.8" +
"M70.4 38.2 63.5 61.8M63.5 38.2 70.4 61.8";
const MONOGRAM = html`<svg
class="monogram"
viewBox="0 0 100 100"
aria-hidden="true"
focusable="false"
>
<defs>
<linearGradient
id="nq-monogram-fill"
gradientUnits="userSpaceOnUse"
x1="0"
y1="0"
x2="100"
y2="100"
>
<stop offset="0" style="stop-color: var(--nq-brand-blue)" />
<stop offset="1" style="stop-color: var(--nq-brand-purple)" />
</linearGradient>
<mask id="nq-monogram-points-gap" maskUnits="userSpaceOnUse">
<rect width="100" height="100" fill="#fff" />
<path d=${MONOGRAM_POINTS} stroke="#000" stroke-width="3.5" />
</mask>
<mask id="nq-monogram-facets" maskUnits="userSpaceOnUse">
<rect width="100" height="100" fill="#fff" />
<path d=${MONOGRAM_FACETS} stroke="#000" stroke-width="1.6" />
<text
x="50"
y="59.4"
text-anchor="middle"
font-family="'Cinzel Decorative', Cinzel, serif"
font-weight="900"
font-size="25"
fill="#fff"
stroke="#000"
stroke-width="2"
paint-order="stroke"
>N</text>
</mask>
</defs>
<g fill="url(#nq-monogram-fill)">
<path d=${MONOGRAM_POINTS} />
<g mask="url(#nq-monogram-points-gap)">
<circle
cx="50"
cy="50"
r="34.25"
fill="none"
stroke="url(#nq-monogram-fill)"
stroke-width="6.5"
/>
<path mask="url(#nq-monogram-facets)" d=${MONOGRAM_HEX} />
</g>
</g>
</svg>`;
const boardStyles = css`
* {
@@ -299,23 +367,11 @@ const boardStyles = css`
background: rgba(92, 62, 26, 0.35);
}
.d20 {
.monogram {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
clip-path: ${unsafeCSS(HEXAGON_CLIP)};
background: var(--nq-brand-gradient);
}
.d20-numeral {
font-family: var(--nq-p-font-heading);
font-size: 15px;
font-weight: 700;
line-height: 1;
color: #ffffff;
display: block;
width: 80px;
height: 80px;
}
.wordmark {
@@ -862,13 +918,8 @@ export class NestQuestPartyBoardCard extends LitElement {
<div class="frame frame-inner"></div>
<div class="wordmark-row">
<span class="rule"></span>
<span class="d20" aria-hidden="true">
<span class="d20-numeral">20</span>
</span>
${MONOGRAM}
<span class="wordmark">NestQuest</span>
<span class="d20" aria-hidden="true">
<span class="d20-numeral">20</span>
</span>
<span class="rule"></span>
</div>
<p class="kicker">The Party · ${formatDay(this._now, this._timeZone())}</p>
+34
View File
@@ -600,6 +600,40 @@ def _render_party_board(
return json.loads(completed.stdout)
def test_party_board_wordmark_row_is_the_monogram_lockup() -> None:
"""PANEL-SPEC §2 item 1: the wordmark row is rule · compass-rose
monogram · NestQuest · rule. The monogram is an inline SVG filled
with the brand gradient — the old d20 hexagon pair and its "20"
numerals are gone, and nothing is fetched from a remote URL or
embedded as a raster logo."""
source = _read(PARTY_BOARD)
assert "d20" not in _style_literal(source, STYLE_CONSTANTS[PARTY_BOARD])
assert 'class="d20' not in source
assert "HEXAGON_CLIP" not in source
html = _render_party_board(
{"type": "custom:nestquest-party-board-card"},
_three_child_states(),
)["html"]
row = re.search(r'<div class="wordmark-row">(.*?)</div>', html, re.DOTALL)
assert row is not None
row_html = row.group(1)
classes = re.findall(r'<(?:span|svg) class="([^"]+)"', row_html)
assert classes == ["rule", "monogram", "wordmark", "rule"]
assert '<svg class="monogram" viewBox="0 0 100 100" aria-hidden="true"' in row_html
assert "var(--nq-brand-blue)" in row_html
assert "var(--nq-brand-purple)" in row_html
assert ">N</text>" in row_html
assert ">NestQuest</span>" in row_html
assert "d20" not in row_html
assert "<img" not in row_html
bundle = _read(BUNDLE_PATH)
assert "d20-numeral\">20<" not in bundle
assert "data:image/png" not in bundle
assert not re.search(r"https?://", bundle)
def test_party_board_renders_discovered_children_in_sort_order() -> None:
"""No child_order key: three plates render in the roster's sort
order (the coordinator snapshot's order), one per child."""