feat: My Sea applet dynamic population + lay/leave POSITION_LABELS swap fix + My Sign applet stat-block + Brief-fied sign-gate + --duoUser olive on all four personal-data surfaces. Six visual+structural items batched across the dashboard/billboard/gameboard.
(1) **My Sea applet dynamic population.** Applet at `_applet-my-sea.html` was referencing an undefined `latest_draw_cards` template var — fell through to "No draws yet" even when the user had an active draw. New helpers in `apps/gameboard/models.py`: `DRAW_ORDER` + `POSITION_LABELS` constants (Python mirrors of the JS dicts in `my_sea.html:274-293`) + `latest_draw_slots(user)` builder that pairs each spread position w. its drawn card + display label + polarity. Wired through `gameboard()` + `toggle_game_applets()` views as `my_sea_slots`. Applet now renders all spread slots in DRAW_ORDER: filled = `.my-sea-slot--filled.my-sea-slot--{gravity,levity}` w. corner-tl + face (name + arcana) + corner-br (mirror) markup (same shape language as my_sign.html `.sig-stage-card`), empty = `.my-sea-slot--empty` w. `0.15rem dashed rgba(var(--terUser), 1)` border (matches the picker's `.sea-card-slot` style exactly so the applet reads as a true scaled-down twin). Container queries (`container-type: size` on `.my-sea-scroll`) lift `--slot-w` to fill the applet's vertical aperture (`min(100cqi, calc((100cqh - 1rem) * 5 / 8))` carves the label row). Position labels pulled tight against the slot's bottom border (`margin-top: -0.15rem` crosses the border line) + vertically stretched (`transform: scaleY(1.4)` mirroring `.sea-pos-label` in `_card-deck.scss:1671-1684`) — empty-slot labels keep the same `--secUser` ink as filled-slot labels for title cohesion across the row. Horizontal-scroll on multi-card spreads via mousewheel — `bindMySeaWheel()` in `gameboard.js` translates vertical wheel events to `scrollLeft += deltaY` (lifted verbatim from `bindPaletteWheel` in `dashboard.js:7-14`).
(2) **lay/leave POSITION_LABELS swap fix.** User caught in the Escape Velocity picker that LEFT slot read "Lay" + BOTTOM slot read "Leave" — opposite of traditional Celtic Cross semantics (LEFT = Behind/past, BOTTOM = Beneath/root). Root cause: POSITION_LABELS for both Waite-Smith + Escape Velocity had `lay`/`leave` slug→label assignments inverted vs the CSS grid's spatial mapping (`_card-deck.scss:1276-1279` puts slug `lay` at BOTTOM, slug `leave` at LEFT). Fix in 5 places: `my_sea.html:287,292` JS POSITION_LABELS (WS: lay→"Beneath", leave→"Behind"; EV: lay→"Lay", leave→"Leave"), `gameboard/models.py:44-47` Python mirror, `test_game_my_sea.py:618-619` FT label-assertion table, `_sea_overlay.html:28,53` annotated comments (`sea-pos-leave` → "Behind (past) — CC pos 6 / EV pos 4"; `sea-pos-lay` → "Beneath (root) — CC pos 4 / EV pos 3"). Slug-to-CSS mapping, DRAW_ORDER, + DB persistence unchanged → no migrations, no data invalidation. **Crucial for Voronoi mapping correctness** per user spec.
(3) **My Sign applet — stage-card layout + stat-block beside.** Applet card markup upgraded to mirror my_sign.html `.sig-stage-card`: corner-tl + face (name + arcana centred) + corner-br (mirror, rotated 180°). Sized to fill applet height via container queries (`--applet-card-w: min(48cqi, 62.5cqh)` — 48cqi caps the card at half the row to leave room for the stat-block). Sibling `.my-sign-applet-stat-block` partial added — emanation/reversal face label + keyword list (from `card.keywords_upright` / `keywords_reversed` keyed off `significator_reversed`), no SPIN/FYI buttons (applet is read-only). Styling cribbed from `.sig-stat-block` in `_card-deck.scss:595-607` — priUser-translucent bg + terUser border + matching `--applet-card-w` sizing.
(4) **My Sea sign-gate refactored to Brief banner.** Was an inline `.my-sea-sign-gate` div w. its own SCSS — broke from the project's `Brief.showBanner` portal pattern. Refactored to a shared `_my_sea_sign_gate_brief.html` partial that fires `Brief.showBanner` w. title="Sign required" + line_text="Look!—pick your sign before drawing the Sea." + post_url=`/billboard/my-sign/`. Brief portals to the page-level h2 anchor via `note.js`'s `_alignToH2` (gaussian-glass `.note-banner` shell, FYI button → my-sign picker, NVM dismisses). Modifier class `.my-sea-sign-gate-brief` added post-render for FT selector disambiguation. note.js load hoisted to gameboard.html `{% block scripts %}` + the top of `my_sea.html {% block content %}` (single load per page — note.js declares `const Brief = ...` at global scope, second load = SyntaxError). All `.my-sea-sign-gate{,--applet,__line,__actions,__back,__fyi}` SCSS deleted. FTs (`test_no_sig_renders_lookline_gate_on_standalone_page` + 5 siblings) + ITs (`test_my_sea_applet_fires_sign_gate_brief_for_user_without_sig` etc.) updated to assert `.note-banner.my-sea-sign-gate-brief` + the JS-rendered FYI/NVM buttons inside the Brief shell.
(5) **Levity card text invisibility fix.** My-sea applet levity slots (--secUser bg) rendered their corner-rank + suit-icon invisible because `.fan-card-corner` carries a global `color: rgba(var(--secUser), 0.75)` rule at `_card-deck.scss:312-319` (specificity 0,1,0) that out-specifics the slot's inherited `color: --priUser`. Same trap as the `.fan-card-name { color: --quiUser }` global. Fix at `_gameboard.scss` inside the levity rule: explicit `.fan-card-corner { color: rgba(var(--priUser), 1) }` + `.fan-card-name { color: rgba(var(--priUser), 1) }` + `.fan-card-arcana { color: rgba(var(--priUser), 0.7) }` overrides at (1,3,1) specificity — beats the globals without `!important`. **Trap captured in memory** — pattern repeats across game-kit, my-sign, my-sea so worth pinning.
(6) **--duoUser olive on all five personal-data surfaces.** Per user spec, the four "personal" applets (My Sign on billboard, My Sea on gameboard, My Sky on dashboard) + the standalone Dashsky page + the standalone My Sign page got `background-color: rgba(var(--duoUser), 1)` so they read as a unified olive-bg group across navigation surfaces. For Dashsky specifically, the form column also got the override (`.sky-page .sky-form-col { background: --duoUser }`) — the base `.sky-form-col { background: --priUser }` (`_sky.scss:137`, shared w. the in-room CAST SKY modal) was leaving the dashsky form column purple inside the otherwise-olive page. Scoped to `.sky-page` so the in-room modal's purple form-col stays intact (sits over --secUser room bg, needs that contrast). One detour caught: tried `body.page-sky { background-color: --duoUser }` to fill the gap below .sky-page's content-sized aperture but it bled to navbar + footer (which sit outside .container) — reverted.
**TDD coverage**: 3 new ITs in `apps/gameboard/tests/integrated/test_views.py` — `test_my_sea_applet_renders_drawn_cards_in_draw_order` (SAO 1-of-3 fills `lay` slot, cover/crown render as empty placeholders), `test_my_sea_applet_labels_match_locked_spread` (SAO labels exactly Situation/Action/Outcome), `test_my_sea_applet_waite_smith_labels_post_fix` (regression pin for the WS Cover/Cross/Crown/**Beneath**/Before/**Behind** sequence post-swap-fix). Existing my-sea applet ITs updated to match the new selector vocabulary (`.my-sea-slot--filled` instead of `.my-sea-card`, Brief script substring instead of `.my-sea-sign-gate--applet`). 6 my-sea FTs updated to the Brief-banner contract. 1214/1214 IT/UT green.
**.gitignore**: temporary entry for `src/apps/epic/static/apps/epic/images/cards-faces/minchiate-fiorentine/` until images get renamed — flagged for removal once the rename lands. (Per user's wget download of the Minchiate faces into the gameboard cards/ tree this session.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,48 @@
|
||||
<div class="my-sign-applet-body">
|
||||
{% if request.user.significator %}
|
||||
{% with card=request.user.significator %}
|
||||
{# Mirrors the my_sign.html `.sig-stage-card` layout — corner #}
|
||||
{# top-left, full name in the face, polarity-reversed mirror #}
|
||||
{# at the bottom (pre-rotated). Sized to fill the applet's #}
|
||||
{# vertical aperture via container queries in `_billboard.scss`. #}
|
||||
<div class="my-sign-applet-card{% if request.user.significator_reversed %} stage-card--reversed{% endif %}"
|
||||
data-card-id="{{ card.id }}">
|
||||
<div class="fan-card-corner fan-card-corner--tl">
|
||||
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||
</div>
|
||||
<p class="fan-card-name">{{ card.name_title }}</p>
|
||||
<div class="fan-card-face">
|
||||
<p class="fan-card-name">{{ card.name_title }}</p>
|
||||
<p class="fan-card-arcana">{{ card.get_arcana_display }}</p>
|
||||
</div>
|
||||
<div class="fan-card-corner fan-card-corner--br">
|
||||
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{# Stat block — same shape as my_sign.html's `.sig-stat-block` #}
|
||||
{# (Emanation/Reversal face label + keyword list) but no SPIN #}
|
||||
{# or FYI buttons since the applet is a read-only preview. The #}
|
||||
{# face shown is keyed off significator_reversed: True → #}
|
||||
{# reversal keywords (labelled "Reversal"), False → upright #}
|
||||
{# (labelled "Emanation"). Mirrors the FYI panel populated by #}
|
||||
{# `StageCard.populateKeywords` in my_sign.html's JS init. #}
|
||||
<div class="my-sign-applet-stat-block">
|
||||
{% if request.user.significator_reversed %}
|
||||
<p class="stat-face-label">Reversal</p>
|
||||
<ul class="stat-keywords">
|
||||
{% for kw in card.keywords_reversed %}
|
||||
<li>{{ kw }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="stat-face-label">Emanation</p>
|
||||
<ul class="stat-keywords">
|
||||
{% for kw in card.keywords_upright %}
|
||||
<li>{{ kw }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
|
||||
@@ -3,26 +3,56 @@
|
||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||
>
|
||||
<h2><a href="{% url 'my_sea' %}">My Sea</a></h2>
|
||||
<div class="my-sea-scroll">
|
||||
{% if not request.user.significator_id %}
|
||||
{# Sprint 4b applet-gate mirror — same Look!-formatted nudge as #}
|
||||
{# the standalone page so the UX is consistent across surfaces. #}
|
||||
<div class="my-sea-sign-gate my-sea-sign-gate--applet">
|
||||
<p class="my-sea-sign-gate__line">
|
||||
Look!—pick your sign before drawing the Sea.
|
||||
</p>
|
||||
<a class="btn btn-info my-sea-sign-gate__fyi"
|
||||
href="{% url 'billboard:my_sign' %}">FYI</a>
|
||||
</div>
|
||||
{% elif latest_draw_cards %}
|
||||
{% for card in latest_draw_cards %}
|
||||
<div class="my-sea-card" data-position="{{ card.position }}">
|
||||
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||
{# `my_sea_slots` (built by `latest_draw_slots()` in `gameboard.models`) #}
|
||||
{# carries one entry per spread position in DRAW_ORDER — filled slots #}
|
||||
{# render the drawn card, empty slots render as labelled placeholders. #}
|
||||
{# Spread lock-in: the row is created at first card draw, so the moment #}
|
||||
{# 1+ cards exist all the spread's positions show in the applet. The #}
|
||||
{# scroll container handles overflow (mirrors the Palettes applet). #}
|
||||
{% if not request.user.significator_id %}
|
||||
{# Sprint 4b applet-gate — DRYly rendered as a project-wide Brief #}
|
||||
{# banner (`note-banner` Gaussian-glass shell, portaled to the #}
|
||||
{# page h2). Inline body falls through to the empty-state "No #}
|
||||
{# draws yet" since no sig → no draws is the only possible state.#}
|
||||
{% include "apps/gameboard/_partials/_my_sea_sign_gate_brief.html" %}
|
||||
<p class="my-sea-empty">No draws yet.</p>
|
||||
{% elif my_sea_slots %}
|
||||
<div class="my-sea-scroll">
|
||||
{% for slot in my_sea_slots %}
|
||||
{% if slot.card %}
|
||||
<div class="my-sea-slot-wrap">
|
||||
{# Mirrors the my_sign.html `.sig-stage-card` layout — #}
|
||||
{# corner top-left, face w. name + arcana, mirror corner #}
|
||||
{# bottom-right. Sized to fill the applet height via #}
|
||||
{# container queries in `_gameboard.scss`. #}
|
||||
<div class="my-sea-slot my-sea-slot--filled my-sea-slot--{{ slot.polarity }}{% if slot.reversed %} my-sea-slot--reversed{% endif %}"
|
||||
data-position="{{ slot.position }}"
|
||||
data-card-id="{{ slot.card.id }}">
|
||||
<div class="fan-card-corner fan-card-corner--tl">
|
||||
<span class="fan-corner-rank">{{ slot.card.corner_rank }}</span>
|
||||
{% if slot.card.suit_icon %}<i class="fa-solid {{ slot.card.suit_icon }}"></i>{% endif %}
|
||||
</div>
|
||||
<div class="fan-card-face">
|
||||
<p class="fan-card-name">{{ slot.card.name_title }}</p>
|
||||
<p class="fan-card-arcana">{{ slot.card.get_arcana_display }}</p>
|
||||
</div>
|
||||
<div class="fan-card-corner fan-card-corner--br">
|
||||
<span class="fan-corner-rank">{{ slot.card.corner_rank }}</span>
|
||||
{% if slot.card.suit_icon %}<i class="fa-solid {{ slot.card.suit_icon }}"></i>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<span class="my-sea-slot-label">{{ slot.label }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="my-sea-empty">No draws yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="my-sea-slot-wrap">
|
||||
<div class="my-sea-slot my-sea-slot--empty"
|
||||
data-position="{{ slot.position }}"></div>
|
||||
<span class="my-sea-slot-label my-sea-slot-label--empty">{{ slot.label }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="my-sea-empty">No draws yet.</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{# Sign-gate Brief — fired when the user has no `significator` set on the #}
|
||||
{# My Sea page OR the My Sea applet. Replaces the prior inline `.my-sea- #}
|
||||
{# sign-gate` markup w. a DRY call into the project-wide `Brief.showBanner` #}
|
||||
{# pattern (gaussian-glass `.note-banner` shell, portaled to the h2 anchor #}
|
||||
{# by `note.js`'s `_alignToH2`). FYI → /billboard/my-sign/ (pick a sign); #}
|
||||
{# NVM dismisses (built into Brief). `.my-sea-sign-gate-brief` modifier #}
|
||||
{# class is added post-render so FT selectors can disambiguate this banner #}
|
||||
{# from any other Brief on the same page. #}
|
||||
{# #}
|
||||
{# Host pages MUST load `apps/dashboard/note.js` themselves (the partial #}
|
||||
{# can't safely re-load it — `note.js` declares `const Brief = ...` at #}
|
||||
{# global scope, so a second load would SyntaxError on re-declaration). #}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (!window.Brief || !Brief.showBanner) return;
|
||||
Brief.showBanner({
|
||||
title: 'Sign required',
|
||||
line_text: 'Look!—pick your sign before drawing the Sea.',
|
||||
post_url: '{% url "billboard:my_sign" %}',
|
||||
created_at: '',
|
||||
kind: 'NUDGE',
|
||||
});
|
||||
var banner = document.querySelector('.note-banner');
|
||||
if (banner) banner.classList.add('my-sea-sign-gate-brief');
|
||||
});
|
||||
</script>
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="sea-crucifix-cell sea-pos-crown">
|
||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||
</div>
|
||||
{# Beneath (past) — CC pos 4 / EV pos 3 #}
|
||||
{# Behind (past) — CC pos 6 / EV pos 4 #}
|
||||
<div class="sea-crucifix-cell sea-pos-leave">
|
||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="sea-crucifix-cell sea-pos-loom">
|
||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||
</div>
|
||||
{# Behind (root) — CC pos 6 / EV pos 4 #}
|
||||
{# Beneath (root) — CC pos 4 / EV pos 3 #}
|
||||
<div class="sea-crucifix-cell sea-pos-lay">
|
||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||
</div>
|
||||
|
||||
@@ -14,5 +14,10 @@
|
||||
{% endblock content %}
|
||||
|
||||
{% block scripts %}
|
||||
{# note.js exposes window.Brief — used by the My Sea applet's sign-gate #}
|
||||
{# Brief partial (`_my_sea_sign_gate_brief.html`) when the user has no #}
|
||||
{# saved significator. Page-level load so the partial doesn't risk a #}
|
||||
{# re-declaration SyntaxError (note.js uses `const Brief = ...`). #}
|
||||
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
||||
<script src="{% static 'apps/gameboard/gameboard.js' %}"></script>
|
||||
{% endblock scripts %}
|
||||
|
||||
@@ -5,26 +5,23 @@
|
||||
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}
|
||||
|
||||
{% block content %}
|
||||
{# note.js exposes window.Brief — needed by BOTH the no-sig branch's #}
|
||||
{# sign-gate Brief partial AND the with-sig branch's Default-deck + #}
|
||||
{# Free-draw-locked Briefs. Hoisted out of the branches so it loads #}
|
||||
{# exactly once regardless of which path the user is on (note.js #}
|
||||
{# declares `const Brief = ...` at global scope — a second load would #}
|
||||
{# SyntaxError on re-declaration). #}
|
||||
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
||||
<div class="my-sea-page"
|
||||
data-phase="{% if show_picker %}picker{% else %}landing{% endif %}"
|
||||
data-polarity="{% if significator_reversed %}levity{% else %}gravity{% endif %}">
|
||||
{% if not user_has_sig %}
|
||||
{# Sprint 4b sign-gate. The draw UX is gated behind a saved #}
|
||||
{# significator — render a Look!-formatted Brief-style line w. #}
|
||||
{# FYI (→ /billboard/my-sign/) + NVM (→ /gameboard/) until the #}
|
||||
{# user picks a sign. Inline (not portaled like .note-banner) #}
|
||||
{# because the gate IS the page content, not a transient nudge. #}
|
||||
<div class="my-sea-sign-gate">
|
||||
<p class="my-sea-sign-gate__line">
|
||||
Look!—pick your sign before drawing the Sea.
|
||||
</p>
|
||||
<div class="my-sea-sign-gate__actions">
|
||||
<a class="btn btn-cancel my-sea-sign-gate__back"
|
||||
href="{% url 'gameboard' %}">NVM</a>
|
||||
<a class="btn btn-info my-sea-sign-gate__fyi"
|
||||
href="{% url 'billboard:my_sign' %}">FYI</a>
|
||||
</div>
|
||||
</div>
|
||||
{# Sprint 4b sign-gate — DRYly Brief-banner'd (was inline div #}
|
||||
{# .my-sea-sign-gate, refactored 2026-05-22 to use the project- #}
|
||||
{# wide `.note-banner` portal via `Brief.showBanner`). NVM lives #}
|
||||
{# inside the Brief shell; FYI links to /billboard/my-sign/. #}
|
||||
{# Page body stays empty — the Brief is the entire no-sig UX. #}
|
||||
{% include "apps/gameboard/_partials/_my_sea_sign_gate_brief.html" %}
|
||||
{% else %}
|
||||
{% if not show_picker %}
|
||||
{# Sprint 5 iter 1 — DRAW SEA landing UX. DRY table hex from #}
|
||||
@@ -284,12 +281,20 @@
|
||||
'situation-action-outcome': { lay: 'Situation', cover: 'Action', crown: 'Outcome' },
|
||||
'mind-body-spirit': { crown: 'Mind', lay: 'Body', loom: 'Spirit' },
|
||||
'desire-obstacle-solution': { loom: 'Desire', cross: 'Obstacle',crown: 'Solution' },
|
||||
'waite-smith': { crown: 'Crown', leave: 'Beneath', cover: 'Cover', cross: 'Cross', loom: 'Before', lay: 'Behind' },
|
||||
// CSS grid (`_card-deck.scss:1276-1279`) places sea-pos-
|
||||
// leave at LEFT + sea-pos-lay at BOTTOM. Traditional
|
||||
// Celtic Cross has "Behind" (past) at LEFT + "Beneath"
|
||||
// (root) at BOTTOM, so labels here pair lay→"Beneath"
|
||||
// + leave→"Behind". Fix landed 2026-05-22: prior assign-
|
||||
// ment had Beneath at LEFT + Behind at BOTTOM (inverted
|
||||
// vs tradition + vs the grid's own commented intent in
|
||||
// `_sea_overlay.html`).
|
||||
'waite-smith': { crown: 'Crown', leave: 'Behind', cover: 'Cover', cross: 'Cross', loom: 'Before', lay: 'Beneath' },
|
||||
// Escape Velocity remaps the diagonal positions per the
|
||||
// user-locked spec (2026-05-19): Beneath→Lay, Before→
|
||||
// Loom, Behind→Leave. Crown/Cover/Cross keep the WS
|
||||
// names.
|
||||
'escape-velocity': { crown: 'Crown', leave: 'Lay', cover: 'Cover', cross: 'Cross', loom: 'Loom', lay: 'Leave' },
|
||||
// names. Carries the same LEFT/BOTTOM correction as WS.
|
||||
'escape-velocity': { crown: 'Crown', leave: 'Leave', cover: 'Cover', cross: 'Cross', loom: 'Loom', lay: 'Lay' },
|
||||
};
|
||||
var hidden = document.getElementById('id_sea_spread');
|
||||
var cross = document.querySelector('.my-sea-cross');
|
||||
@@ -934,8 +939,8 @@
|
||||
{# /billboard/my-sign/'s no-equipped-deck path. Same copy, #}
|
||||
{# same FYI (→ /gameboard/) + NVM (dismiss + proceed) actions.#}
|
||||
{# Tagged w. .my-sea-intro-banner so FTs disambiguate from #}
|
||||
{# any other Briefs on the page. #}
|
||||
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
||||
{# any other Briefs on the page. note.js itself is hoisted #}
|
||||
{# to the top of {% block content %} (single load per page). #}
|
||||
{% if active_draw %}
|
||||
{# Iter 4b — saved-draw Brief. Standard portaled banner via #}
|
||||
{# Brief.showBanner (Gaussian-glass bg, atop-h2 positioning); #}
|
||||
|
||||
Reference in New Issue
Block a user