**(1) `_stat_face.html` partial** — extracted to `templates/core/_partials/_stat_face.html` per user 2026-05-25 PM: "Why are there so many individual instances of this feature? Couldn't we call the same DRY partial for each?". One partial covers all 4 stat-block surfaces (sig-stat-block / sea-stat-block / fan-stage-block / my-sign-applet-stat-block) — ~80 lines of duplicated markup collapse to 7 `{% include %}` sites (3 surfaces × 2 faces + applet × 1 face). Args: `face_modifier` (required: "upright"|"reversed"), `label_text` (required: "Emanation"|"Reversal"), `card` (optional TarotCard for applet's server-render path), `keywords_ul_id` (optional id attr on the keyword `<ul>` — sea_stage + fan need `id_sea_stat_upright/reversed` + `id_fan_stat_upright/reversed` for stage-card.js's `populateKeywords` surface-specific selector overrides). The `.stat-face` wrapper that the partial introduces is a no-op for the applet — applet's bespoke `.my-sign-applet-stat-block` rule doesn't `@include stat-block-shared` so `.stat-face` inherits no padding / display-none from the shared mixin.
**(2) FLIP-btn `@mixin flip-btn-base` + `%flip-btn-revealed` + `%flip-btn-mid-flip` primitives** — `_card-deck.scss` head per user 2026-05-25 PM: "unify the many disparate calculations we use for when we allow that FLIP btn to appear and where it appears". Each surface's flip-btn declaration now `@include`s the base (position absolute + zero margin + hidden default opacity 0 + 0.3s transition) and `@extend`s `%flip-btn-revealed` on its surface-specific reveal trigger + `%flip-btn-mid-flip` on its surface-specific `[data-flipping]` selector chain. ~30 lines of duplication collapsed to 6 lines of mixin/placeholder + 3 `@include` + 4 `@extend` calls.
**(3) my_sign FLIP btn moved INSIDE `.sig-stage-card`** + `.my-sign-flip-btn` + `.my-sign-applet-flip-btn` share one positioning rule (`bottom: 0.6rem; left: 0.6rem`) — was a sibling under `.my-sign-stage` positioned via stage-padding-relative `calc(1.5rem + 0.4rem)`. Polish-5 nests it INSIDE the card so positioning is naturally card-relative + the separate `.my-sign-page[data-current-card-id]` centered-mode geometric override (re-deriving offsets from the centred-row layout) is DROPPED entirely. The applet was already inside-card positioned; same `bottom: 0.6rem; left: 0.6rem` rule combines both surfaces in a single `_card-deck.scss` declaration. The applet's `_billboard.scss` flip-btn rule is now just a shim `@include` + `@extend` (the positioning got DRY'd up to the shared rule).
**(4) Hover-reveal everywhere** + instant mid-flip vanish — user-spec 2026-05-25 PM: "The .btn-reveal behavior here should now (1) disappear much earlier, so no independent ease-in/-out logic needed on clicking FLIP; (2) calculate its position more dynamically; be mirrored in the gameboard's My Sign applet. In all places does the hover-to-reveal-FLIP-.btn-reveal effect abate while the card is finishing a FLIP". my_sign main flipped from `display: none → display: inline-flex` (frozen-gated) to opacity-based hover-reveal on `.sig-stage-card:hover` (still gated by `.sig-stage--frozen`). Applet flipped from always-visible to opacity-based hover-reveal on `.my-sign-applet-card:hover`. Fan kept its existing hover-reveal. Mid-flip-hide changed from `opacity: 0 + pointer-events: none` (faded out over the 0.3s transition, which competed w. the click) to `display: none` — INSTANT vanish, no ease-out animation. All 3 surfaces consolidated into one combined `[data-flipping] -> flip-btn` selector list extending `%flip-btn-mid-flip`. The `:has(.flip-btn:hover)` self-pin clause (already present on fan) added to my_sign + applet too — keeps the btn visible while the cursor is on it, otherwise the btn (z-index 25, on top of the card) steals `:hover` from the card the moment the cursor moves onto it + retracts the reveal mid-click.
**(5) `.sea-stage--levity .sea-stage-card` image-mode bg fix** — user-reported 2026-05-25 PM: "the card preview stage in my_sea.html still sports the old card bg (the --secUser here) behind the card img (with the --quiUser box-shadow border)". Same source-order collision pattern as the sea-sig-card fix in polish-4: `.sea-stage--levity .sea-stage-card`'s `@include stage-card-polarity($invert-frame: true)` sets `background: rgba(var(--secUser), 1) + border-color: rgba(var(--priUser), 1)` at specificity 0,2,0 — matches the shared `.sig-stage-card.sig-stage-card--image` comma-list rule's specificity but source-loses to it (levity rule lives at line 2150, comma-list at line 705). Fix: add a `&.sig-stage-card--image { background: transparent; border: 0; }` nested override (0,3,0 specificity) — re-states the transparency under the levity polarity branch so image-mode drawn cards (Minchiate today) don't show a beige card-shape behind the PNG art. The gravity branch was already fine (its mixin call doesn't pass `$invert-frame`).
**(6) Multi-line `{# #}` comment syntax cleanup** — user-spotted 2026-05-25 PM after my polish-5 partial extraction caused visible comment text to leak into rendered HTML on 4 templates (per [[feedback-django-multiline-comments]] / [[feedback-django-comments-single-line-only]] traps the user has flagged before). All multi-line block comments I added in this polish converted to `{% comment %}...{% endcomment %}` form — covers the `_stat_face.html` partial header + 4 template include sites (my_sign.html × 2 blocks, _applet-my-sign.html, _sea_stage.html, game_kit.html).
Tests: 1314/1314 IT+UT total green (72s). No new tests — existing chip-presence + image-mode ITs from polish-4 still pass through the partial extraction. Visual verify 2026-05-25 PM via Claudezilla: my_sign main page (Queen of Coins) renders cleanly via partial w. card+stat-block; applet renders cleanly w. server-filled chip + title; carousel + sea_stage modal work via JS-populated partial includes; my_sign FLIP btn moved into card + hover-reveals + vanishes instantly on FLIP click; sea-stage-card no longer shows --secUser bg behind image-mode PNG art under levity. DRY partial extraction was held out of polish-4 as user-requested separate concern: "hold it for a separate commit, but fold the FLIP btn unification into it as the styling cleanup part" — done.
**Follow-up parked for next sprint**: user-flagged 2026-05-25 PM "If it's interfering to have bespoke rules, just allow the FLIP btn everywhere, including in my_sea.html". This needs (a) dropping the `not card.deck_variant.is_polarized` server-render gate in the applet template, (b) adding a FLIP btn + back-img element to the `_sea_stage.html` modal scaffold, (c) wiring a JS handler in sea.js (currently has no FLIP behavior for drawn-card stage). Out of scope for the polish-5 commit since it's template + JS scope; will pick up as polish-6 or a fresh sprint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
742 lines
25 KiB
SCSS
742 lines
25 KiB
SCSS
// ── Shared scrollable applet list (.applet-list) ───────────────────────────
|
|
// In-grid applet partials (My Posts, My Buds, My Notes, My Scrolls, My Games)
|
|
// + the dedicated applet-list pages (billbuds, billposts) all wrap items in a
|
|
// `<ul class="applet-list">`. The list rules live at top level so the same
|
|
// item-entry styling applies in both surfaces; per-context wrappers below
|
|
// handle flex sizing so the list scrolls inside its parent's aperture.
|
|
|
|
.applet-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0 0.75rem 0 0;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
|
|
// Flex-column lets the empty-state entry fill the aperture so it can
|
|
// centre vertically. Fires only when the list is entirely empty —
|
|
// as soon as a real .applet-list-entry lands, layout reverts to the
|
|
// default left-aligned vertical stack.
|
|
&:has(> .applet-list-entry--empty) {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
|
|
// Empty-state filler (`No <X> yet.` rows). Centres in any flex-column
|
|
// parent — the .applet-list above OR a `display: flex; flex-direction:
|
|
// column` applet section directly (e.g. #id_applet_most_recent_scroll
|
|
// when no Room has events yet).
|
|
.applet-list-entry--empty {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
opacity: 0.6;
|
|
font-style: italic;
|
|
margin: 0;
|
|
}
|
|
|
|
// 3-col applet row — `<title> | <body> | <ts>`. Mirrors post.html's
|
|
// `.post-line` grid (`minmax(4rem,auto) 1fr minmax(3rem,auto)`) so the
|
|
// rightward ts column lines up across post.html, scroll.html, and every
|
|
// applet list. Title gets the project-wide 35char/32+... truncation via
|
|
// the `truncate_title` template filter; body is dimmed to 0.6.
|
|
.applet-list-entry.row-3col {
|
|
display: grid;
|
|
grid-template-columns: minmax(4rem, auto) 1fr minmax(3rem, auto);
|
|
align-items: baseline;
|
|
gap: 0.5rem;
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
border-radius: 0.25rem;
|
|
transition: background-color 0.12s ease, color 0.12s ease;
|
|
|
|
.row-title {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.row-body {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.row-ts {
|
|
font-size: 0.75rem;
|
|
opacity: 0.5;
|
|
text-align: right;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
// Hover (mouse) / click-lock (touch + click persistence) — both states
|
|
// share the highlight. JS in `apps/applets/row-lock.js` toggles
|
|
// `.row-locked` on click; `:hover` is pure CSS. Background to --secUser,
|
|
// title to --quaUser, dimmed body + ts brought to full opacity in
|
|
// --priUser (the back-of-card colour, readable against the --secUser
|
|
// fill).
|
|
&:hover,
|
|
&.row-locked {
|
|
background-color: rgba(var(--secUser), 1);
|
|
|
|
.row-title,
|
|
a.row-title { color: rgba(var(--quiUser), 1); text-shadow: none; }
|
|
|
|
.row-body,
|
|
.row-ts { color: rgba(var(--priUser), 1); opacity: 1; }
|
|
}
|
|
}
|
|
|
|
.applet-list-entry {
|
|
padding: 0.4rem 0;
|
|
|
|
.bud-name { font-weight: bold; opacity: 0.85; }
|
|
|
|
a {
|
|
color: rgba(var(--terUser), 1);
|
|
text-decoration: none;
|
|
font-weight: bold;
|
|
transition: text-shadow 0.15s ease;
|
|
|
|
&:hover,
|
|
&:active {
|
|
color: rgba(var(--ninUser), 1);
|
|
text-shadow: 0 0 0.55rem rgba(var(--terUser), 0.7);
|
|
}
|
|
}
|
|
}
|
|
|
|
.applet-list-buffer {
|
|
flex-shrink: 0;
|
|
height: 0.5rem;
|
|
}
|
|
|
|
// In-grid applet sections: flex-column so the .applet-list can flex:1
|
|
// and scroll within the applet box. Left-aligned items across the
|
|
// board (My Games used to centre — symmetrised w. the rest 2026-05-12).
|
|
|
|
#id_applet_my_posts,
|
|
#id_applet_my_buds,
|
|
#id_applet_my_scrolls,
|
|
#id_applet_notes {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.applet-list {
|
|
flex: 1;
|
|
padding-top: 0.25rem;
|
|
}
|
|
}
|
|
|
|
// ── Shared aperture fill for both billboard pages ──────────────────────────
|
|
//
|
|
// Aperture foundation (html/body/.container overflow + flex-column +
|
|
// .row flex-shrink: 0) now lives universally in _base.scss. Only the
|
|
// page-specific `.row { margin-bottom: -1rem }` pull (tightening the
|
|
// h2 row against subsequent applet content) stays here, since wallet
|
|
// + sky pages deliberately don't carry that pull.
|
|
|
|
%billboard-page-base {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
position: relative;
|
|
}
|
|
|
|
body.page-billboard,
|
|
body.page-billscroll,
|
|
body.page-billpost,
|
|
body.page-billbuds,
|
|
body.page-billposts {
|
|
.row {
|
|
margin-bottom: -1rem;
|
|
}
|
|
}
|
|
|
|
// ── Billboard page (three-applet grid) ─────────────────────────────────────
|
|
|
|
.billboard-page {
|
|
@extend %billboard-page-base;
|
|
}
|
|
|
|
// ── Billscroll page (single full-aperture applet) ──────────────────────────
|
|
|
|
.billscroll-page {
|
|
@extend %billboard-page-base;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0.75rem;
|
|
|
|
// The single scroll applet stretches to fill the remaining aperture
|
|
.applet-scroll {
|
|
@extend %applet-box;
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
#id_drama_scroll {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
padding-right: 0.75rem;
|
|
|
|
.scroll-buffer {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: baseline;
|
|
padding: 2rem 0 1rem;
|
|
opacity: 0.4;
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
|
|
.scroll-buffer-text {
|
|
letter-spacing: 0.33em;
|
|
}
|
|
|
|
.scroll-buffer-dots {
|
|
display: inline-flex;
|
|
letter-spacing: 0;
|
|
|
|
span {
|
|
display: inline-block;
|
|
width: 0.7em;
|
|
text-align: center;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Dashpost page (bottom-anchored thread + composer) ─────────────────────
|
|
// Mirrors billscroll's flex-column / overflow-y / scroll-buffer pattern,
|
|
// with the composer pinned at the bottom (flex-shrink: 0) so the thread
|
|
// breathes against the viewport bottom and the input stays in reach.
|
|
|
|
.post-page {
|
|
@extend %billboard-page-base;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0.75rem;
|
|
gap: 0.5rem;
|
|
|
|
// Username + title attribution spans — line author column, self/shared
|
|
// header lines, server-rendered grant prose. --quaUser palette key
|
|
// unifies them across the page; placed at .post-page scope so it
|
|
// applies in BOTH .post-header and #id_post_table descendants.
|
|
.post-attribution {
|
|
color: rgba(var(--quaUser), 1);
|
|
}
|
|
|
|
.post-header {
|
|
flex-shrink: 0;
|
|
|
|
.post-title {
|
|
margin: 0 0 0.25rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.post-shared-recipients,
|
|
.post-shared-self {
|
|
margin: 0;
|
|
font-size: 0.85rem;
|
|
opacity: 0.75;
|
|
}
|
|
}
|
|
|
|
#id_post_table {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0 0.75rem 0 0;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
// Bottom-anchor: scroll buffer above the lines pushes them down
|
|
// until they fill from the bottom; once content exceeds the
|
|
// aperture, normal scrolling kicks in.
|
|
justify-content: flex-end;
|
|
|
|
.post-line {
|
|
display: grid;
|
|
grid-template-columns: minmax(4rem, auto) 1fr minmax(3rem, auto);
|
|
align-items: baseline;
|
|
gap: 0.5rem;
|
|
padding: 0.25rem 0;
|
|
|
|
.post-line-author {
|
|
font-weight: bold;
|
|
color: rgba(var(--quaUser), 1);
|
|
white-space: nowrap;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.post-line-text {
|
|
min-width: 0;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.post-line-time {
|
|
font-size: 0.75rem;
|
|
opacity: 0.5;
|
|
text-align: right;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
// System-authored Lines (adman) get a subtler typographic key
|
|
// — the inline `<a class="note-ref">` carries the emphasis.
|
|
&.post-line--system .post-line-text {
|
|
font-style: italic;
|
|
opacity: 0.85;
|
|
}
|
|
}
|
|
|
|
.post-line-buffer {
|
|
flex-shrink: 0;
|
|
height: 0.25rem;
|
|
}
|
|
}
|
|
|
|
.post-line-form {
|
|
flex-shrink: 0;
|
|
margin: 0;
|
|
padding-top: 0.25rem;
|
|
|
|
input.form-control {
|
|
width: 100%;
|
|
|
|
// Admin-Post readonly input — no response is invited, so the
|
|
// focus halo softens to --secUser (cooler than the regular
|
|
// --terUser glow used on user-Post composers).
|
|
&[readonly]:focus {
|
|
border-color: rgba(var(--secUser), 0.6);
|
|
box-shadow: 0 0 0.75rem rgba(var(--secUser), 0.4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Applet-list page (Billbuds, Billposts) ───────────────────────────────
|
|
// Shared shell for pages built around _applet-list-shell.html — vertical
|
|
// title rotated on the left of an .applet-scroll card + scrollable <ul>
|
|
// aperture. `--single` hosts one section (My Buds); `--two-up` stacks
|
|
// two sections in portrait, places them side-by-side in landscape (My
|
|
// Posts: own + shared).
|
|
|
|
.applet-list-page {
|
|
@extend %billboard-page-base;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0.75rem;
|
|
gap: 0.75rem;
|
|
|
|
.applet-scroll {
|
|
@extend %applet-box;
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
// .applet-list / .applet-list-entry / .applet-list-buffer rules
|
|
// live at top level (above) so they apply to in-grid applets too.
|
|
.applet-list { flex: 1; }
|
|
}
|
|
|
|
// Side-by-side in landscape; stacked in portrait (default).
|
|
&--two-up {
|
|
@media (orientation: landscape) {
|
|
flex-direction: row;
|
|
.applet-scroll { flex: 1; }
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Billboard applet placement ─────────────────────────────────────────────
|
|
// Left column (4-wide): My Scrolls → Contacts → Notes stacked.
|
|
// Right column (8-wide): Most Recent Scroll spans full height.
|
|
// Portrait override (container query) restores stacked full-width layout.
|
|
|
|
#id_billboard_applets_container {
|
|
#id_applet_my_scrolls { grid-column: 1 / span 4; grid-row: 1 / span 3; }
|
|
#id_applet_my_buds { grid-column: 1 / span 4; grid-row: 4 / span 3; }
|
|
#id_applet_notes { grid-column: 1 / span 4; grid-row: 7 / span 4; }
|
|
#id_applet_most_recent_scroll { grid-column: 5 / span 8; grid-row: 1 / span 10; }
|
|
|
|
@container (max-width: 550px) {
|
|
#id_applet_my_scrolls,
|
|
#id_applet_my_buds,
|
|
#id_applet_notes,
|
|
#id_applet_most_recent_scroll {
|
|
grid-column: 1 / span 12;
|
|
grid-row: span var(--applet-rows, 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Most Recent Scroll applet — scrollable drama feed ─────────────────────
|
|
|
|
#id_applet_most_recent_scroll {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.most-recent-room-link {
|
|
flex-shrink: 0;
|
|
margin-bottom: 0.25rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
#id_drama_scroll {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.most-recent-load-more {
|
|
display: block;
|
|
padding-bottom: 0.5rem;
|
|
font-size: 0.8rem;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
// ── Drama event entries: 90 / 10 column split ─────────────────────────────
|
|
|
|
.drama-event {
|
|
display: flex;
|
|
align-items: baseline;
|
|
|
|
.drama-event-body {
|
|
flex: 0 0 80%;
|
|
|
|
&.struck {
|
|
text-decoration: line-through;
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.drama-event-time {
|
|
flex: 0 0 20%;
|
|
font-size: 0.75rem;
|
|
opacity: 0.5;
|
|
text-align: right;
|
|
}
|
|
}
|
|
|
|
// My Scrolls now rides the shared `.applet-list` rule above (lifted out of
|
|
// `.applet-list-page .applet-scroll`). Old `.scroll-list` styling removed.
|
|
|
|
// ── My Sign applet (billboard) ────────────────────────────────────────────
|
|
// Saved-sig preview — mirrors the `.sig-stage-card` layout (corner top-
|
|
// left + face w. name + arcana + mirror corner bottom-right) but sized
|
|
// to fill the applet's vertical aperture rather than a fixed 5rem.
|
|
// Container queries on `.my-sign-applet-body` lift `--applet-card-w` to
|
|
// `min(100cqi, 62.5cqh)` — the card grows to fill whichever axis is
|
|
// constraining (62.5cqh = `100cqh * 5/8` keeps the 5:8 aspect inside
|
|
// the container height). All child font sizes calc off --applet-card-w
|
|
// so the typography scales w. the card without per-applet tuning.
|
|
#id_applet_my_sign {
|
|
display: flex;
|
|
flex-direction: column;
|
|
// Anchor for #id_applet_sky_delete_btn's absolute centering.
|
|
position: relative;
|
|
background-color: rgba(var(--duoUser), 1) !important;
|
|
|
|
.my-sign-applet-empty {
|
|
opacity: 1 !important;
|
|
}
|
|
|
|
h2 {
|
|
flex-shrink: 0;
|
|
background-color: rgba(var(--priUser), 1);
|
|
box-shadow: rgba(0, 0, 0, 1) !important;
|
|
}
|
|
|
|
.my-sign-applet-body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
padding: 0.3rem;
|
|
container-type: size;
|
|
}
|
|
|
|
.my-sign-applet-card {
|
|
// Width-cap shrinks by half the row (card + gap + stat-block) so the
|
|
// pair centres without horizontal overflow. `1.0cqi` keeps the
|
|
// gap/padding allowance — anything left after the stat-block grows
|
|
// to fill, capped by the card's natural 5:8 aspect.
|
|
--applet-card-w: min(48cqi, 62.5cqh);
|
|
width: var(--applet-card-w);
|
|
aspect-ratio: 5 / 8;
|
|
border-radius: 0.4rem;
|
|
// Gravity default — `--priUser` bg + `--terUser` ink. `--levity`
|
|
// modifier below inverts to `--secUser` bg + `--quiUser` ink,
|
|
// matching the page stage card's polarity convention (cf
|
|
// `_card-deck.scss:1002-1019` for levity, :1039-1057 for gravity).
|
|
background: rgba(var(--priUser), 1);
|
|
border: 0.12rem solid rgba(var(--secUser), 0.6);
|
|
|
|
// Sprint A.6 — image-mode override. `_card-deck.scss` imports before
|
|
// `_billboard.scss`, so the shared `.my-sign-applet-card--image` rule
|
|
// there gets out-cascaded by the base bg/border above (same specificity,
|
|
// later declaration wins). Re-state the transparency here AFTER the
|
|
// base. The contour stroke + depth shadow on the <img> still come
|
|
// from the shared `_card-deck.scss` rule, which only loses on `bg` +
|
|
// `border` properties — not on the filter chain.
|
|
&.my-sign-applet-card--image {
|
|
background: transparent;
|
|
border: 0;
|
|
position: relative; // anchor for the absolute FLIP btn
|
|
}
|
|
color: rgba(var(--terUser), 1);
|
|
padding: 0.35rem;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
transition: transform 0.4s ease;
|
|
|
|
// Top-left + bottom-right corners — rank + suit icon stacked.
|
|
// br is rotated 180° (mirror) so the card reads as "completed"
|
|
// from both edges, matching the stage card pattern.
|
|
.fan-card-corner--tl,
|
|
.fan-card-corner--br {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
line-height: 1.05;
|
|
gap: 0.05rem;
|
|
position: absolute;
|
|
|
|
.fan-corner-rank {
|
|
font-size: calc(var(--applet-card-w) * 0.16);
|
|
font-weight: 700;
|
|
}
|
|
i { font-size: calc(var(--applet-card-w) * 0.13); }
|
|
}
|
|
.fan-card-corner--tl { top: 0.25rem; left: 0.3rem; }
|
|
.fan-card-corner--br {
|
|
bottom: 0.25rem; right: 0.3rem;
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
// Card face — qualifier + title + arcana stacked, centred in the
|
|
// remaining vertical space between the two corners. `gap: 0` so
|
|
// qualifier sits directly above the title at the title's own
|
|
// line-height; `.fan-card-arcana` carries its own margin-top to
|
|
// restore breathing room between title block and arcana label.
|
|
.fan-card-face {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0;
|
|
text-align: center;
|
|
padding: 0 0.2rem;
|
|
}
|
|
|
|
// Qualifier + title share the same typography (per `_card-deck.scss`
|
|
// convention at lines 568-572 / 1821-1823) — both bold, same size,
|
|
// same wrap, same line-height. Polarity color (gravity → --terUser,
|
|
// levity → --quiUser) lives on the parent — both elements inherit.
|
|
.fan-card-qualifier,
|
|
.fan-card-name {
|
|
margin: 0;
|
|
font-size: calc(var(--applet-card-w) * 0.11);
|
|
font-weight: 700;
|
|
line-height: 1.15;
|
|
text-wrap: balance;
|
|
color: inherit;
|
|
}
|
|
.fan-card-qualifier:empty { display: none; }
|
|
|
|
.fan-card-arcana {
|
|
margin: calc(var(--applet-card-w) * 0.05) 0 0;
|
|
font-size: calc(var(--applet-card-w) * 0.075);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
// Levity inversion — `--secUser` bg + `--quiUser` ink + `--priUser`
|
|
// border, mirroring `.sig-overlay[data-polarity="levity"]
|
|
// .sig-stage-card` at `_card-deck.scss:1002-1010`.
|
|
&.my-sign-applet-card--levity {
|
|
background: rgba(var(--secUser), 1);
|
|
border-color: rgba(var(--priUser), 0.6);
|
|
color: rgba(var(--quiUser), 1);
|
|
.fan-card-corner { color: rgba(var(--priUser), 0.75); }
|
|
.fan-card-arcana { color: rgba(var(--priUser), 1); }
|
|
}
|
|
}
|
|
|
|
// Sprint A.6 — FLIP btn for non-polarized image-equipped decks in the
|
|
// applet. Nested INSIDE .my-sign-applet-card.--image (which has
|
|
// position: relative) so absolute positioning anchors to the card.
|
|
// Polish-5: shares `@include flip-btn-base` + `%flip-btn-mid-flip` w.
|
|
// the other 2 surfaces (my_sign main + game-kit fan) via `_card-deck.scss`.
|
|
.my-sign-applet-card .my-sign-applet-flip-btn {
|
|
@include flip-btn-base;
|
|
z-index: 10;
|
|
bottom: 0.6rem;
|
|
left: 0.6rem;
|
|
}
|
|
// Btn is nested INSIDE the card, so the [data-flipping] hook is a direct
|
|
// ancestor — no `:has()` needed (unlike the my_sign + fan surfaces where
|
|
// the btn is a sibling of the flipping card under a common parent).
|
|
.my-sign-applet-card[data-flipping] .my-sign-applet-flip-btn {
|
|
@extend %flip-btn-mid-flip;
|
|
}
|
|
|
|
// Stat block — mirrors the stage card's footprint (same 5:8 aspect +
|
|
// height) so the pair reads as a balanced 2-tile composition centred
|
|
// in the applet aperture. Styling cribbed from `.sig-stat-block` in
|
|
// `_card-deck.scss:595-607` (priUser-translucent bg + terUser border)
|
|
// minus the SPIN/FYI button apparatus — applet is read-only, no
|
|
// interaction needed. `--applet-card-w` is reused as the sizing knob
|
|
// so stat-face-label + stat-keywords typography scales w. the card.
|
|
.my-sign-applet-stat-block {
|
|
--applet-card-w: min(48cqi, 62.5cqh);
|
|
width: var(--applet-card-w);
|
|
aspect-ratio: 5 / 8;
|
|
align-self: center;
|
|
// Sprint A.7.5-polish-3 — unified 1.0 alpha across all stat-block surfaces.
|
|
background: rgba(var(--priUser), 1);
|
|
border-radius: 0.4rem;
|
|
border: 0.1rem solid rgba(var(--terUser), 0.15);
|
|
padding: calc(var(--applet-card-w) * 0.08);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
|
|
.stat-face-label {
|
|
font-size: calc(var(--applet-card-w) * 0.08);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.09em;
|
|
opacity: 0.7;
|
|
// Sprint A.7.5 user-spec 2026-05-25 PM — applet sets the convention:
|
|
// EMANATION/REVERSAL label is --secUser so it recedes against the
|
|
// title (--quaUser/--terUser per arcana). Shared mixin + the other
|
|
// 3 stat-block surfaces (sig-stat-block, sea-stat-block, fan-stage-
|
|
// block) updated to match.
|
|
color: rgba(var(--secUser), 1);
|
|
margin: 0;
|
|
// text-decoration dropped in polish-4 — the `.stat-face-header`
|
|
// border-bottom (below) now underscores the whole header unit.
|
|
}
|
|
|
|
// Sprint A.7.5-polish-4 — header is a 2-row vertical stack: rank on
|
|
// row 1 (room for long Roman numerals), icon + EMANATION/REVERSAL on
|
|
// row 2 inline. Border-bottom underscores both rows as one header
|
|
// unit, separating it from the title block below. Sized off the
|
|
// applet's `--applet-card-w` container-query var rather than `--sig-
|
|
// card-w`. Mirrors the shared mixin shape; deliberate parallel since
|
|
// applet sizing is independent.
|
|
.stat-face-header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: calc(var(--applet-card-w) * 0.015);
|
|
margin: 0 0 calc(var(--applet-card-w) * 0.06);
|
|
padding-bottom: calc(var(--applet-card-w) * 0.035);
|
|
border-bottom: 0.05rem solid rgba(var(--secUser), 0.4);
|
|
}
|
|
.stat-chip-rank {
|
|
font-size: calc(var(--applet-card-w) * 0.12);
|
|
font-weight: bold;
|
|
line-height: 1;
|
|
color: rgba(var(--secUser), 1);
|
|
&:empty { display: none; }
|
|
}
|
|
.stat-chip-tag {
|
|
display: inline-flex;
|
|
align-items: baseline;
|
|
gap: calc(var(--applet-card-w) * 0.04);
|
|
line-height: 1;
|
|
|
|
i {
|
|
font-size: calc(var(--applet-card-w) * 0.085);
|
|
color: rgba(var(--secUser), 1);
|
|
}
|
|
}
|
|
|
|
// Sprint A.7-polish-3 — title + arcana in applet stat-block per
|
|
// user spec 2026-05-25 PM. Title color keys off the parent's
|
|
// `data-arcana-key` (rendered server-side from `card.arcana`).
|
|
.stat-face-title {
|
|
font-size: calc(var(--applet-card-w) * 0.11);
|
|
font-weight: 700;
|
|
line-height: 1.15;
|
|
margin: 0 0 calc(var(--applet-card-w) * 0.03);
|
|
text-wrap: balance;
|
|
color: rgba(var(--quaUser), 1);
|
|
}
|
|
&[data-arcana-key="MAJOR"] .stat-face-title {
|
|
color: rgba(var(--terUser), 1);
|
|
}
|
|
.stat-face-arcana {
|
|
font-size: calc(var(--applet-card-w) * 0.075);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
opacity: 0.6;
|
|
margin: 0 0 calc(var(--applet-card-w) * 0.06);
|
|
}
|
|
.stat-face-title:empty,
|
|
.stat-face-arcana:empty { display: none; }
|
|
|
|
.stat-keywords {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
|
|
li {
|
|
font-size: calc(var(--applet-card-w) * 0.1);
|
|
padding: calc(var(--applet-card-w) * 0.04) 0;
|
|
color: rgba(var(--quiUser), 1);
|
|
border-bottom: 0.05rem solid rgba(var(--terUser), 0.18);
|
|
&:last-child { border-bottom: none; }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Polarity inversion of the stat block — mirrors the page convention
|
|
// where the stat block always carries the OPPOSITE-polarity colors of
|
|
// its sibling card (`_card-deck.scss:1042-1046` for the gravity case,
|
|
// levity inherits the default --priUser bg). Gravity polarity card →
|
|
// --secUser stat block (light), w. --quiUser label + --priUser
|
|
// keywords for contrast against the light bg.
|
|
.my-sign-applet-body[data-polarity="gravity"] .my-sign-applet-stat-block {
|
|
// Sprint A.7.5-polish-2 — bg override dropped; default 0.5 alpha
|
|
// applies in both polarities now that we've unified opacity across
|
|
// the 4 stat-block surfaces. Border + keyword color overrides kept
|
|
// because they specifically target the gravity card-pair convention.
|
|
border-color: rgba(var(--secUser), 0.15);
|
|
.stat-keywords li {
|
|
color: rgba(var(--secUser), 1);
|
|
border-bottom-color: rgba(var(--secUser), 0.18);
|
|
}
|
|
}
|
|
|
|
.my-sign-applet-empty {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
opacity: 0.6;
|
|
font-style: italic;
|
|
margin: 0;
|
|
}
|
|
}
|