Files
python-tdd/src/static_src/scss/_sky.scss
Disco DeDisco d5e4fc53f0 CAST SKY cascade: felt eases out → glow → DRAW SEA eases in; burger handoff; reload-into-open — TDD
Post-save choreography + the polish asks from this session.

- Post-save cascade (_sky_overlay.html): after SAVE the gamer lingers on the
  wheel ~3s, then the felt eases OUT (fade, .sky-page--cascade-out) to reveal the
  table-hex; the burger fires its --priTk glow + #id_sky_btn goes active (both
  ride the cascade now, not the save instant); 3s later the DRAW SEA btn eases IN
  and the sea overlay is injected so it's live with NO reload.
- Hex phase-stack (room.html + _room.scss): CAST SKY + DRAW SEA share one grid
  cell (.hex-phase-stack) so they cross-fade in place (.hex-phase-btn--out); the
  server seeds --out on the inactive one, the cascade swaps them. A confirmed
  reload lands DRAW SEA visible / CAST SKY out, same as the cascade end.
- Seamless sea injection: _injectSeaOverlay fetches sea_partial (the URL already
  existed, unused) + re-creates its <script>s so the overlay's own init (openSea
  + SeaDeal.reinit) runs — DRAW SEA opens with no reload. (Bridge until Sea Select
  is itself hollowed into a felt.)
- Burger → Sky-btn handoff (burger-btn.js + _burger.scss): _pulseGlow generalized;
  once saved, the next burger-OPEN pulses #id_sky_btn --priTk ("now click me to
  reopen"). Jasmine specs added (BurgerSpec).
- #id_text_btn disabled while the felt is up (openSky/closeSky) — its swipe
  machine would otherwise half-load the scroll-locked reelhouse.
- Reload-into-open (sig-select.js + _sky_overlay.html): the SIG_SELECT→SKY_SELECT
  CAST SKY click crosses a server-render boundary (felt/phase-stack/sea-inject
  only exist in SKY_SELECT), so it still reloads — but drops a sessionStorage
  flag first so the felt OPENS on arrival. Kills the old click→reload→click-again
  double-take (the "first click reloads" report). DRAW SEA can inject in-place
  (stays within SKY_SELECT); CAST SKY can't, so this is the seamless equivalent.

Tests: BurgerSpec handoff specs + full Jasmine green; 32 render ITs + 930
epic+gameboard green. Cascade timing live-verified by the user.

[[feedback-scss-import-order-specificity]]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:24:32 -04:00

1229 lines
42 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─── Sky (Pick Sky) overlay ────────────────────────────────────────────────
// Gaussian backdrop + centred modal, matching the gate/sig overlay pattern.
// Open state: html.sky-open (added by JS on CAST SKY click).
//
// Layout: header / two-column body (form | wheel) / footer
// Collapses to stacked single-column below 600 px.
// ── Scroll-lock ───────────────────────────────────────────────────────────────
html.sky-open {
overflow: hidden;
// NB: the modal-era `#id_aperture_fill { opacity: 1 }` backdrop is GONE here.
// It's a full-cover --duoUser div at z-90; the old dark modal sat above it
// (z-120), but the inline felt sits at z-5, so lighting the fill painted an
// opaque green sheet OVER the felt + form (the fill's opacity transition is
// why the form "flashed then vanished"). The felt is its own --duoUser
// surface + covers the hex on its own, so the fill stays transparent now.
}
// ── In-room CAST SKY felt (unified with my_sky / sky.html) ────────────────────
// The .sky-page apparatus rendered INSIDE .room-hex-pane (my_sea-style, like
// .sig-overlay) instead of the old fixed dark modal. The hex pane becomes a
// positioning context only while the felt shows, so the absolute-filling felt
// scopes to the pane (not the viewport) and the position-strip's root stacking
// stays untouched in every other phase. Hidden until the CAST SKY btn adds
// html.sky-open; reuses every dashboard .sky-page form/wheel rule below.
.room-hex-pane.has-sky-stage {
position: relative;
}
// NB: chained to (0,2,0) — the base `.sky-page { position: relative }` block
// lives LATER in this file, so a bare `.sky-page--room` (0,1,0) loses the tie on
// source order and the felt stays position:relative → it collapses to width 0 as
// a flex child of the hex-pane (the form vanishes onto a 0-wide column). The
// chain wins regardless of order. [[feedback-scss-import-order-specificity]]
.sky-page.sky-page--room {
position: absolute;
inset: 0;
// Within the hex-pane stacking context (matches .sig-overlay's z:5), above
// the hex/seats it covers but below the root-level position strip (z-130).
z-index: 5;
// Hidden until opened — pointer-events off so the hidden felt can't eat the
// CAST SKY btn click beneath it.
visibility: hidden;
pointer-events: none;
}
html.sky-open .sky-page.sky-page--room {
visibility: visible;
pointer-events: auto;
}
// While the felt is up, hide the position strip (a z-130 hex-pane sibling that
// would otherwise float its circles over the form) so the felt reads as a clean
// homogeneous surface — restored the instant the felt closes.
html.sky-open .position-strip {
visibility: hidden;
}
// Post-save cascade ease-out — the felt fades to transparent over ~0.5s (the JS
// _FELT_FADE timer) BEFORE sky-open is removed, so the table-hex is revealed with
// a soft dissolve instead of a hard snap. Visibility is still on (sky-open) for
// the duration of the fade; the JS removes sky-open once the opacity lands at 0.
.sky-page.sky-page--cascade-out {
opacity: 0;
transition: opacity 0.5s ease;
}
// ── Backdrop ──────────────────────────────────────────────────────────────────
.sky-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(5px);
z-index: 100;
pointer-events: none;
// Hidden until html.sky-open
opacity: 0;
transition: opacity 0.15s ease;
}
html.sky-open .sky-backdrop {
opacity: 1;
}
// ── Overlay shell (positions + scrolls the modal) ─────────────────────────────
.sky-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 120;
overflow-y: auto;
overscroll-behavior: contain;
pointer-events: none;
// Hidden until html.sky-open
visibility: hidden;
@media (orientation: landscape) {
$sidebar-w: 4rem;
left: $sidebar-w;
right: $sidebar-w;
}
}
html.sky-open .sky-overlay {
visibility: visible;
pointer-events: none; // modal itself is pointer-events: auto
}
// ── Modal panel ───────────────────────────────────────────────────────────────
// Thin wrapper: position:relative so the NVM circle can sit on the corner
// without being clipped by the modal's overflow:hidden.
.sky-modal-wrap {
position: relative;
pointer-events: none; // overlay handles pointer-events; children re-enable
width: 92vw;
max-width: 920px;
// Fade + slide in — wraps modal AND NVM btn so both animate together
opacity: 0;
transform: translateY(1rem);
transition: opacity 0.2s ease, transform 0.2s ease;
}
html.sky-open .sky-modal-wrap {
opacity: 1;
transform: translateY(0);
}
.sky-modal {
pointer-events: auto;
display: flex;
flex-direction: column;
width: 100%; // fills .sky-modal-wrap
max-height: 96vh;
border: 0.1rem solid rgba(var(--terUser), 0.25);
border-radius: 0.5rem;
overflow: hidden;
}
// ── Header ────────────────────────────────────────────────────────────────────
.sky-modal-header {
flex-shrink: 0;
padding: 0.6rem 1rem;
background: rgba(var(--priUser), 1);
border-bottom: 0.1rem solid rgba(var(--terUser), 0.15);
display: flex;
flex-direction: row;
align-items: baseline;
gap: 0.75rem;
h2 {
margin: 0;
font-size: 1.1rem;
letter-spacing: 0.06em;
span { color: rgba(var(--secUser), 1); }
}
p {
margin: 0;
font-size: 0.7rem;
opacity: 0.55;
}
}
// ── Body: two columns ─────────────────────────────────────────────────────────
.sky-modal-body {
flex: 1;
min-height: 0;
display: flex;
flex-direction: row;
overflow: hidden;
}
// Form column — fixed width; form-main scrolls, confirm btn pinned at bottom
.sky-form-col {
flex: 0 0 240px;
overflow: hidden;
padding: 0.9rem 1rem;
background: rgba(var(--priUser), 1);
border-right: 0.1rem solid rgba(var(--terUser), 0.12);
display: flex;
flex-direction: column;
gap: 0.65rem;
}
// Scrollable inner container (form fields + status)
.sky-form-main {
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.65rem;
}
// Confirm btn inside form-col — full width, pinned at column bottom
.sky-form-col > #id_sky_confirm {
flex-shrink: 0;
}
// Wheel column — fills remaining space
.sky-wheel-col {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0.75rem;
// background: rgba(var(--duoUser), 1);
position: relative;
}
.sky-svg {
display: block;
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
max-width: 480px;
max-height: 480px;
}
// ── Form fields ───────────────────────────────────────────────────────────────
.sky-field {
display: flex;
flex-direction: column;
gap: 0;
& + & {
margin-top: 0.4rem;
}
label {
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: rgba(var(--quaUser), 0.8);
}
// Match the .form-control look used by the login email input —
// priUser fill, secUser gold border + text, pill rounding, terUser focus glow.
input {
width: 100%;
// min-width:0 lets the input shrink below its native content-width on
// iOS Firefox, which gives <input type="date|time"> widgets a fixed
// minimum that would otherwise spill past .sky-form-main on narrow
// phones & trigger horizontal page scroll. max-width:100% is belt &
// suspenders for iOS WebKit, which treats date/time as a native
// widget that ignores min-width:0 alone.
min-width: 0;
max-width: 100%;
background-color: rgba(var(--priUser), 1);
color: rgba(var(--secUser), 1);
font-weight: 700;
border: 0.1rem solid rgba(var(--secUser), 0.5);
--_pad-v: 0.5rem;
padding: var(--_pad-v) 0.75rem;
border-radius: calc((var(--_pad-v) * 2 + 1em) / 3);
font-family: inherit;
// iOS Firefox renders <input type="date|time"> as a native widget
// whose intrinsic width exceeds .sky-form-main on narrow phones.
// appearance:none drops the native chrome so width/padding/border
// are honored uniformly; the date/time picker still opens on tap.
&[type="date"],
&[type="time"] {
-webkit-appearance: none;
appearance: none;
}
&:focus {
outline: none;
border-color: rgba(var(--terUser), 0.75);
box-shadow: 0 0 0.75rem rgba(var(--terUser), 0.5);
color: rgba(var(--terUser), 1);
}
&[readonly] {
// Match the birth-place placeholder's effective opacity. The
// browser's default ::placeholder pseudo-element is ~0.54; the
// lat/lon/tz fields used to sit at the input-element-level
// opacity:0.6, which compounded with that to ~0.32 — making
// placeholders almost invisible. Bumping to 0.85 keeps the
// readonly fields visually de-emphasized vs. the editable ones
// while leaving the placeholder copy clearly readable
// (0.85 × 0.54 ≈ 0.46, parity w. birth-place's ~0.54).
opacity: 0.85;
&:focus {
border-color: rgba(var(--secUser), 0.75);
box-shadow: 0 0 0.75rem rgba(var(--secUser), 0.5);
color: inherit;
}
cursor: default;
}
}
small {
font-size: 0.58rem;
opacity: 0.45;
line-height: 1.3;
margin-top: 0.2rem;
}
}
// Place search field wrapper: text input + geo button inline
.sky-place-field { position: relative; }
.sky-place-wrap {
display: flex;
gap: 0.4rem;
align-items: center;
input { flex: 1; min-width: 0; }
.btn-sm { flex-shrink: 0; margin-top: 0; margin-bottom: 0; }
}
// Nominatim suggestion dropdown
.sky-suggestions {
position: absolute;
left: 0;
right: 0;
top: calc(100% + 2px);
z-index: 10;
background: rgba(var(--priUser), 1);
border: 0.1rem solid rgba(var(--terUser), 0.3);
border-radius: 0.3rem;
overflow-y: auto;
max-height: 10rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.sky-suggestion-item {
display: block;
width: 100%;
padding: 0.4rem 0.6rem;
text-align: left;
background: none;
border: none;
border-bottom: 0.05rem solid rgba(var(--terUser), 0.1);
font-size: 0.65rem;
color: rgba(var(--ninUser), 0.85);
cursor: pointer;
line-height: 1.35;
&:last-child { border-bottom: none; }
&:hover, &:focus {
background: rgba(var(--terUser), 0.12);
color: rgba(var(--ninUser), 1);
outline: none;
}
}
// Coords row: lat | lon (read-only, populated by place selection)
.sky-coords {
flex-direction: row;
align-items: flex-end;
gap: 0.4rem;
> div {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0;
label {
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: rgba(var(--quaUser), 0.8);
}
input {
width: 100%;
// opacity is inherited from the .sky-field input[readonly] rule
// above (0.85) — keep this block lean so the readonly-styling
// single source of truth doesn't drift.
}
}
}
// ── Status line ───────────────────────────────────────────────────────────────
.sky-status {
font-size: 0.65rem;
opacity: 0.6;
min-height: 1rem;
text-align: center;
&--error {
opacity: 1;
color: rgba(var(--priRd), 1);
}
}
// ── NVM corner btn ────────────────────────────────────────────────────────────
// Absolutely pinned to top-right corner of .sky-modal-wrap.
// transform: translate(50%,-50%) centres the circle on the corner point.
// Lives outside .sky-modal so overflow:hidden doesn't clip it.
#id_sky_cancel {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
z-index: 10;
margin: 0;
pointer-events: auto;
}
// ── Narrow / portrait ─────────────────────────────────────────────────────────
@media (max-width: 600px) {
.sky-modal-wrap {
width: 92vw;
}
.sky-modal {
max-height: 96vh;
}
.sky-modal-body {
flex-direction: column;
overflow-y: auto;
}
// Form col stacks above wheel; internally becomes a flex-row so
// form-main gets most of the width and confirm btn sits to its right.
.sky-form-col {
flex: 0 0 auto;
flex-direction: row;
align-items: flex-end;
border-right: none;
border-bottom: 0.1rem solid rgba(var(--terUser), 0.12);
overflow: visible; // form-main handles its own scroll
gap: 0.5rem;
}
.sky-form-main {
flex: 1;
min-width: 0;
overflow-y: auto;
max-height: 40vh;
}
.sky-form-col > #id_sky_confirm {
flex-shrink: 0;
align-self: flex-end;
}
.sky-wheel-col {
flex: 0 0 280px;
}
}
// ── SVG wheel element styles ──────────────────────────────────────────────────
// Colors and opacity live here; geometry (cx/cy/r/font-size) stays in JS.
.nw-outer-ring {
fill: none;
stroke: rgba(var(priYl), 1);
stroke-width: 1.5px;
}
.nw-inner-disc {
fill: rgba(0, 0, 0, 0);
backdrop-filter: blur(5px);
stroke: rgba(var(--priYl), 1);
stroke-width: 0.75px;
// box-shadow: 1px 1px 3px rgba(0, 0, 0, 1);
}
// Axes (ASC / DSC / MC / IC)
.nw-axis-line { stroke: rgba(var(--secUser), 1); stroke-width: 1.5px; }
.nw-axis-label { fill: rgba(var(--secUser), 1); }
// Sign ring — uniform --priYl bg at half opacity
.nw-sign--fire,
.nw-sign--stone,
.nw-sign--air,
.nw-sign--water {
fill: rgba(0, 0, 0, 0.33);
backdrop-filter: blur(5px);
stroke: rgba(var(--priYl), 1);
stroke-width: 0.75px;
}
// Icon bg circles — element fill + matching border
.nw-sign-icon-bg--fire { fill: rgba(var(--quaRd), 0.92); stroke: rgba(var(--priOr), 1); stroke-width: 1px; }
.nw-sign-icon-bg--stone { fill: rgba(var(--quaFs), 0.92); stroke: rgba(var(--priMe), 1); stroke-width: 1px; }
.nw-sign-icon-bg--air { fill: rgba(var(--quiCy), 0.92); stroke: rgba(var(--priBl), 1); stroke-width: 1px; }
.nw-sign-icon-bg--water { fill: rgba(var(--sixId), 0.92); stroke: rgba(var(--priVt), 1); stroke-width: 1px; }
// Inline SVG path icons — per-element colors
.nw-sign-icon--fire { fill: rgba(var(--priOr), 1); }
.nw-sign-icon--stone { fill: rgba(var(--priMe), 1); }
.nw-sign-icon--air { fill: rgba(var(--priBl), 1); }
.nw-sign-icon--water { fill: rgba(var(--priVt), 1); }
// House ring — uniform --priGn bg
.nw-house-cusp { stroke: rgba(var(--priYl), 1); stroke-width: 1.2px; }
.nw-house-num { fill: rgba(var(--priYl), 1); }
.nw-house-fill--even { fill: rgba(var(--secGn), 0.75); stroke: rgba(var(--priYl), 1); stroke-width: 0.75px; }
.nw-house-fill--odd { fill: rgba(var(--quiGn), 0.75); stroke: rgba(var(--priYl), 1); stroke-width: 0.75px; }
// Planets — base geometry
.nw-planet-circle,
.nw-planet-circle--rx { stroke-width: 1px; }
.nw-planet-label { stroke-width: 1px; paint-order: stroke fill; }
// Per-planet circle: fill = ternary, border = senary
.nw-planet--au { fill: rgba(var(--terAu), 1); stroke: rgba(var(--sixAu), 1); } // Sun
.nw-planet--ag { fill: rgba(var(--terAg), 1); stroke: rgba(var(--sixAg), 1); } // Moon
.nw-planet--hg { fill: rgba(var(--terHg), 1); stroke: rgba(var(--sixHg), 1); } // Mercury
.nw-planet--cu { fill: rgba(var(--terCu), 1); stroke: rgba(var(--sixCu), 1); } // Venus
.nw-planet--fe { fill: rgba(var(--terFe), 1); stroke: rgba(var(--sixFe), 1); } // Mars
.nw-planet--sn { fill: rgba(var(--terSn), 1); stroke: rgba(var(--sixSn), 1); } // Jupiter
.nw-planet--pb { fill: rgba(var(--terPb), 1); stroke: rgba(var(--sixPb), 1); } // Saturn
.nw-planet--u { fill: rgba(var(--terU), 1); stroke: rgba(var(--sixU), 1); } // Uranus
.nw-planet--np { fill: rgba(var(--terNp), 1); stroke: rgba(var(--sixNp), 1); } // Neptune
.nw-planet--pu { fill: rgba(var(--terPu), 1); stroke: rgba(var(--sixPu), 1); } // Pluto
// Per-planet label: fill + stroke halo = senary
.nw-planet-label--au { fill: rgba(var(--sixAu), 1); stroke: rgba(var(--sixAu), 0.6); }
.nw-planet-label--ag { fill: rgba(var(--sixAg), 1); stroke: rgba(var(--sixAg), 0.6); }
.nw-planet-label--hg { fill: rgba(var(--sixHg), 1); stroke: rgba(var(--sixHg), 0.6); }
.nw-planet-label--cu { fill: rgba(var(--sixCu), 1); stroke: rgba(var(--sixCu), 0.6); }
.nw-planet-label--fe { fill: rgba(var(--sixFe), 1); stroke: rgba(var(--sixFe), 0.6); }
.nw-planet-label--sn { fill: rgba(var(--sixSn), 1); stroke: rgba(var(--sixSn), 0.6); }
.nw-planet-label--pb { fill: rgba(var(--sixPb), 1); stroke: rgba(var(--sixPb), 0.6); }
.nw-planet-label--u { fill: rgba(var(--sixU), 1); stroke: rgba(var(--sixU), 0.6); }
.nw-planet-label--np { fill: rgba(var(--sixNp), 1); stroke: rgba(var(--sixNp), 0.6); }
.nw-planet-label--pu { fill: rgba(var(--sixPu), 1); stroke: rgba(var(--sixPu), 0.6); }
// Rx retrograde badge — mirrors the wallet Shop applet's `.shop-badge` (×N
// quantity chip): --secUser disc + --priUser glyph + bold. User-spec 2026-
// 05-25 PM. Drawn as an SVG `<circle>` behind the existing `<text>` so the
// pair scales w. the chart (font-size + circle radius both keyed off `_r`).
.nw-rx-badge { fill: rgba(var(--secUser), 1); stroke: rgba(var(--priUser), 0.6); stroke-width: 0.5px; }
.nw-rx { fill: rgba(var(--priUser), 1); stroke: none; font-weight: 900; }
// Hover and active-lock glow — planet, element, sign, house groups
.nw-planet-group,
.nw-element-group,
.nw-sign-group,
.nw-house-group { cursor: pointer; }
// Planets get a base 1px×1px black drop-shadow so the colored circle
// reads as a depthed badge floating above the wheel rings — user spec
// 2026-05-25 PM. Chained w. the hover/active glow below (CSS `filter`
// replaces rather than appends, so the chain has to be re-stated on the
// hover rule to keep the shadow when the glow kicks in).
.nw-planet-group {
filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.7));
}
.nw-planet-group:hover,
.nw-planet-group.nw-planet--active,
.nw-planet-group.nw-planet--asp-active {
filter:
drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.7))
drop-shadow(0 0 5px rgba(var(--priLm), 1));
}
.nw-element-group:hover,
.nw-element-group.nw-element--active,
.nw-sign-group:hover,
.nw-sign-group.nw-sign--active,
.nw-house-group:hover,
.nw-house-group.nw-house--active {
filter: drop-shadow(0 0 5px rgba(var(--priLm), 1));
}
// Zodiac icon circles — muted by default, full opacity on hover/active
.nw-sign-icon-bg { opacity: 0.5; }
.nw-sign-group:hover .nw-sign-icon-bg,
.nw-sign-group.nw-sign--active .nw-sign-icon-bg { opacity: 1; }
// House numbers — muted by default, full opacity on hover/active
.nw-house-num { opacity: 0.75; }
.nw-house-group:hover .nw-house-num,
.nw-house-group.nw-house--active .nw-house-num { opacity: 1; }
// ── Planet tick lines — hidden until parent group is active ──────────────────
.nw-planet-tick {
fill: none;
stroke-width: 1px;
stroke-opacity: 0;
stroke-linecap: round;
transition: stroke-opacity 0.15s ease;
}
.nw-planet-group.nw-planet--active .nw-planet-tick,
.nw-planet-group.nw-planet--asp-active .nw-planet-tick {
stroke: rgba(var(--terUser), 1);
stroke-opacity: 0.7;
filter: drop-shadow(0 0 3px rgba(var(--terUser), 0.8))
drop-shadow(0 0 6px rgba(var(--terUser), 0.4));
}
.nw-planet-tick--au { stroke: rgba(var(--priAu), 1); }
.nw-planet-tick--ag { stroke: rgba(var(--priAg), 1); }
.nw-planet-tick--hg { stroke: rgba(var(--priHg), 1); }
.nw-planet-tick--cu { stroke: rgba(var(--priCu), 1); }
.nw-planet-tick--fe { stroke: rgba(var(--priFe), 1); }
.nw-planet-tick--sn { stroke: rgba(var(--priSn), 1); }
.nw-planet-tick--pb { stroke: rgba(var(--priPb), 1); }
.nw-planet-tick--u { stroke: rgba(var(--priU), 1); }
.nw-planet-tick--np { stroke: rgba(var(--priNp), 1); }
.nw-planet-tick--pu { stroke: rgba(var(--priPu), 1); }
// Aspects — per-planet color tokens (light shades on dark palettes; mid on light)
:root {
--asp-Au: var(--sixAu); --asp-Ag: var(--sixAg);
--asp-Hg: var(--sixHg); --asp-Cu: var(--sixCu);
--asp-Fe: var(--sixFe); --asp-Sn: var(--sixSn);
--asp-Pb: var(--sixPb); --asp-U: var(--sixU);
--asp-Np: var(--sixNp); --asp-Pu: var(--sixPu);
}
body[class*="-light"] {
--asp-Au: var(--terAu); --asp-Ag: var(--terAg);
--asp-Hg: var(--terHg); --asp-Cu: var(--terCu);
--asp-Fe: var(--terFe); --asp-Sn: var(--terSn);
--asp-Pb: var(--terPb); --asp-U: var(--terU);
--asp-Np: var(--terNp); --asp-Pu: var(--terPu);
}
.nw-aspects { opacity: 0.8; }
// Element pie — deasil order: Fire → Stone → Time → Space → Air → Water
.nw-element--fire { fill: rgba(var(--priRd, 192, 64, 64), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
.nw-element--stone { fill: rgba(var(--priFs, 122, 96, 64), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
.nw-element--time { fill: rgba(var(--priYl, 192, 160, 48), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
.nw-element--space { fill: rgba(var(--priGn, 64, 96, 64), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
.nw-element--air { fill: rgba(var(--priCy, 64, 144, 176), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
.nw-element--water { fill: rgba(var(--priId, 80, 80, 160), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
// ── Planet hover tooltip — must live outside any ancestor with transform or
// container-type (both break position:fixed). Placed as a direct sibling of
// .sky-overlay in room.html; alongside #id_tooltip_portal in home.html. ──
#id_sky_tooltip,
#id_sky_tooltip_2 {
position: fixed;
z-index: 200;
pointer-events: auto;
padding: 0.75rem 0.75rem 0.75rem 1.5rem;
min-width: 14rem;
.tt-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0; }
.tt-description { font-size: 0.75rem; }
.tt-sign-icon { fill: currentColor; vertical-align: middle; margin-bottom: 0.1em; }
// Planet tooltip — flex row: name | symbol; location row: @deg° Sign | sign icon
.tt-planet-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.2rem;
}
.tt-planet-sym {
font-size: 1.8rem;
opacity: 0.85;
}
.tt-angle-sym {
font-variant-caps: all-small-caps;
font-size: 1.1rem;
opacity: 0.85;
}
.tt-angle-house .tt-ord {
margin-left: 0;
}
.tt-planet-loc {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
font-size: 0.8rem;
margin-bottom: 0.3rem;
}
.tt-planet-sign-icon { font-size: 1.2rem; line-height: 1; }
// Sign tooltip — name in element color | SVG icon; modality | vector; planets
.tt-sign-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.2rem;
}
.tt-sign-icon-wrap {
font-size: 1.5rem;
line-height: 1;
flex-shrink: 0;
.tt-sign-icon { fill: currentColor; }
}
.tt-sign-meta {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.75rem;
opacity: 0.85;
margin-bottom: 0.3rem;
}
.tt-sign-planets {
display: flex;
flex-direction: column;
gap: 0.15rem;
margin-top: 0.1rem;
font-size: 0.85rem;
}
.tt-sign-section-header {
font-size: 0.65rem;
font-weight: 600;
opacity: 0.55;
letter-spacing: 0.04em;
margin-bottom: 0.15rem;
}
.tt-sign-cusps {
display: flex;
flex-direction: column;
gap: 0.15rem;
font-size: 0.85rem;
}
// House tooltip — "House of X" | number; planets in house
.tt-house-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.3rem;
}
.tt-house-of {
font-size: 0.7rem;
font-weight: 700;
margin-right: 0.15em;
opacity: 0.9;
}
.tt-house-num {
font-size: 1.4rem;
font-weight: 700;
opacity: 1;
flex-shrink: 0;
}
.tt-element-type,
.tt-house-type,
.tt-sign-type {
display: block;
font-size: 0.7rem;
font-weight: 400;
opacity: 0.7;
margin-top: 0.1em;
font-style: italic;
}
.tt-house-planets {
display: flex;
flex-direction: column;
gap: 0.15rem;
font-size: 0.85rem;
}
.tt-house-planet-row {
display: flex;
align-items: center;
gap: 0.3rem;
}
// DON|DOFF aspect line toggle — stacked at top-left outside the tooltip box,
// matching the PRV/NXT pattern at the bottom corners.
.nw-asp-don,
.nw-asp-doff {
position: absolute;
left: -1rem;
margin: 0;
pointer-events: auto; // override btn-disabled; click must land here, not pass through to SVG
}
.nw-asp-don { top: -1rem; }
.nw-asp-doff { top: 1.2rem; }
.nw-tt-prv,
.nw-tt-nxt {
position: absolute;
bottom: -1rem;
margin: 0;
}
.nw-tt-prv { left: -1rem; }
.nw-tt-nxt { right: -1rem; }
// Aspect list — always visible below planet description
.tt-aspects {
display: block;
margin-top: 0.6rem;
font-size: 0.94rem;
font-weight: 600;
opacity: 0.85;
line-height: 1.3;
}
.tt-asp-row {
display: flex;
align-items: center;
gap: 0.3rem;
white-space: nowrap;
}
.tt-asp-line { flex-shrink: 0; vertical-align: middle; }
.tt-asp-orb {
margin-left: auto;
opacity: 0.6;
font-size: 0.65rem;
padding-left: 0.5rem;
white-space: nowrap;
}
.tt-asp-in {
opacity: 0.6;
font-size: 0.65rem;
padding-left: 0.25rem;
}
.tt-dim {
opacity: 0.6;
font-size: 0.65rem;
}
.tt-ord {
font-size: 0.6rem;
vertical-align: 0.25rem;
line-height: 0;
opacity: 1;
margin-left: -0.25rem;
letter-spacing: 0;
}
// Element tooltip — title + square badge
.tt-el-header {
margin-bottom: 0.3rem;
}
.tt-el-badge-col {
float: right;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
margin-left: 0.5rem;
}
.tt-el-square {
display: block;
}
.tt-el-score {
font-size: 0.7rem;
text-align: center;
}
.tt-el-vec {
display: inline;
vertical-align: middle;
margin: 0 0.1em;
}
.tt-el-body-line {
font-size: 0.75rem;
opacity: 0.9;
margin-bottom: 0.25rem;
}
.tt-el-contribs {
display: flex;
flex-direction: column;
gap: 0.2rem;
margin-top: 0.3rem;
}
.tt-el-planet-row {
opacity: 0.75;
margin-left: 0.5rem;
margin-top: -0.1rem;
}
.tt-el-formation-header {
font-size: 0.85rem;
font-weight: 700;
margin-top: 0.35rem;
.tt-el-formation-label {
font-size: 0.65rem;
font-weight: 600;
opacity: 0.55;
letter-spacing: 0.04em;
}
}
.tt-el-formation {
font-size: 0.75rem;
opacity: 0.7;
margin-top: 0.2rem;
font-style: italic;
}
// Planet title colors — senary (brightest) tier on dark palettes
.tt-title--au { color: rgba(var(--sixAu), 1); } // Sun
.tt-title--ag { color: rgba(var(--sixAg), 1); } // Moon
.tt-title--hg { color: rgba(var(--sixHg), 1); } // Mercury
.tt-title--cu { color: rgba(var(--sixCu), 1); } // Venus
.tt-title--fe { color: rgba(var(--sixFe), 1); } // Mars
.tt-title--sn { color: rgba(var(--sixSn), 1); } // Jupiter
.tt-title--pb { color: rgba(var(--sixPb), 1); } // Saturn
.tt-title--u { color: rgba(var(--sixU), 1); } // Uranus
.tt-title--np { color: rgba(var(--sixNp), 1); } // Neptune
.tt-title--pu { color: rgba(var(--sixPu), 1); } // Pluto
}
// Element title colors — primary tier on dark palettes
#id_sky_tooltip,
#id_sky_tooltip_2 {
.tt-title--el-fire { color: rgba(var(--priRd), 1); }
.tt-title--el-stone { color: rgba(var(--priFs), 1); }
.tt-title--el-time { color: rgba(var(--priYl), 1); }
.tt-title--el-space { color: rgba(var(--priGn), 1); }
.tt-title--el-air { color: rgba(var(--priCy), 1); }
.tt-title--el-water { color: rgba(var(--priId), 1); }
}
// Sign tooltip title + sign icon SVG — element border colors (Stone/Air/Fire/Water schema)
#id_sky_tooltip,
#id_sky_tooltip_2 {
.tt-title--sign-fire { color: rgba(var(--priOr), 1); }
.tt-title--sign-stone { color: rgba(var(--priMe), 1); }
.tt-title--sign-air { color: rgba(var(--priBl), 1); }
.tt-title--sign-water { color: rgba(var(--priVt), 1); }
.tt-sign-icon-wrap--fire .tt-sign-icon,
.tt-planet-sign-icon--fire .tt-sign-icon { fill: rgba(var(--priOr), 1); }
.tt-sign-icon-wrap--stone .tt-sign-icon,
.tt-planet-sign-icon--stone .tt-sign-icon { fill: rgba(var(--priMe), 1); }
.tt-sign-icon-wrap--air .tt-sign-icon,
.tt-planet-sign-icon--air .tt-sign-icon { fill: rgba(var(--priBl), 1); }
.tt-sign-icon-wrap--water .tt-sign-icon,
.tt-planet-sign-icon--water .tt-sign-icon { fill: rgba(var(--priVt), 1); }
}
// On light palettes — switch to tertiary tier for legibility
body[class*="-light"] #id_sky_tooltip,
body[class*="-light"] #id_sky_tooltip_2 {
.tt-title--el-fire { color: rgba(var(--terRd), 1); }
.tt-title--el-stone { color: rgba(var(--terFs), 1); }
.tt-title--el-time { color: rgba(var(--terYl), 1); }
.tt-title--el-space { color: rgba(var(--terGn), 1); }
.tt-title--el-air { color: rgba(var(--terCy), 1); }
.tt-title--el-water { color: rgba(var(--terId), 1); }
}
// On light palettes — switch to primary (darkest) tier for legibility
body[class*="-light"] #id_sky_tooltip,
body[class*="-light"] #id_sky_tooltip_2 {
.tt-title--au { color: rgba(var(--priAu), 1); }
.tt-title--ag { color: rgba(var(--priAg), 1); }
.tt-title--hg { color: rgba(var(--priHg), 1); }
.tt-title--cu { color: rgba(var(--priCu), 1); }
.tt-title--fe { color: rgba(var(--priFe), 1); }
.tt-title--sn { color: rgba(var(--priSn), 1); }
.tt-title--pb { color: rgba(var(--priPb), 1); }
.tt-title--u { color: rgba(var(--priU), 1); }
.tt-title--np { color: rgba(var(--priNp), 1); }
.tt-title--pu { color: rgba(var(--priPu), 1); }
}
// ── My Sky dashboard applet ───────────────────────────────────────────────────
#id_applet_my_sky {
display: flex;
flex-direction: column;
// Anchor for #id_applet_sky_delete_btn's absolute centering.
position: relative;
background-color: rgba(var(--duoUser), 1) !important;
h2 {
flex-shrink: 0;
// Mask the --duoUser aperture behind the rotated title: an opaque
// --priUser base (the default applet content bg) under the same two
// 0,0,0/0.125 overlays %applet-box + its >h2 give every other applet,
// so the bar reads identically dark instead of green-tinted. Layered
// (not a flat 0,0,0) so it stays palette-correct on *-light palettes.
background:
linear-gradient(rgba(0, 0, 0, 0.125), rgba(0, 0, 0, 0.125)),
linear-gradient(rgba(0, 0, 0, 0.125), rgba(0, 0, 0, 0.125)),
rgba(var(--priUser), 1);
box-shadow: rgba(0, 0, 0, 1) !important;
}
.sky-svg {
flex: 1;
min-height: 0;
max-width: none;
max-height: none;
align-self: center;
}
#id_applet_sky_form_wrap {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0.5rem 0.25rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
#id_sky_confirm {
margin-top: -1.5rem;
align-self: center;
position: relative;
z-index: 1;
}
}
}
// ── Sky full page (column layout) ─────────────────────────────────────────
//
// Aperture foundation lives universally in _base.scss; nothing sky-specific
// here besides the .sky-page block below.
// Sky page fills the aperture; its content can scroll past the bottom edge.
// overflow-x: hidden clips any horizontal overflow that would otherwise create
// page-level horizontal scroll on iOS Firefox — html/body overflow:hidden on
// page-sky doesn't always catch native <input type="date|time"> widgets whose
// minimum content width exceeds the form column on narrow phones.
.sky-page {
position: relative;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
// --duoUser aperture bg — matches the My Sky / My Sea / My Sign
// applet shells (`_sky.scss:909`, `_gameboard.scss:583`,
// `_billboard.scss:449`) so all four personal-data surfaces read
// as a unified olive-bg group across the dashboard/billboard/
// gameboard navigation.
background-color: rgba(var(--duoUser), 1);
}
// Note: tried `body.page-sky { background-color: --duoUser }` to fill
// any gap below the .sky-page aperture but it bled to navbar + footer
// (which sit OUTSIDE .container) — reverted 2026-05-22. The real fix
// is the scoped `.sky-page .sky-form-col` rule below, which puts the
// olive on the form column where the purple was leaking through.
// DEL btn pinned at the wheel center — appears wherever a wheel is shown
// (Dashsky form#id_sky_delete_form, CAST SKY overlay #id_sky_delete_btn,
// My Sky applet #id_applet_sky_delete_btn). Anchored to .sky-wheel-col /
// the applet section (both position:relative) so the btn sits over the SVG's
// geometric center regardless of the surrounding layout.
#id_sky_delete_form,
#id_sky_delete_btn,
#id_applet_sky_delete_btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 5;
margin: 0;
}
// The applet's padding is asymmetric — 2.5rem left (rotated h2 gutter) and
// 0.75rem right — so the SVG's geometric center sits 0.875rem right of the
// applet's padding-box center. Shift the DEL btn by half the asymmetry so it
// re-aligns with the SVG's actual center instead of the applet's box center.
#id_applet_sky_delete_btn {
left: calc(50% + 0.875rem);
}
// Stack wheel above form; allow body to grow past viewport (page scrolls, not body)
.sky-page .sky-modal-body {
flex-direction: column;
flex-shrink: 0;
}
// Wheel takes its natural square size from its width — never shrinks for the form
.sky-page .sky-wheel-col {
order: -1;
flex: 0 0 auto;
width: 100%;
aspect-ratio: 1 / 1;
max-width: 480px;
max-height: 480px;
align-self: center;
}
// Form col is a vertical stack — fields on top, SAVE SKY beneath, both
// centered horizontally. Pre-save the wheel-col is hidden so this column
// fills the aperture & sits visually centered. Post-save the snap layout
// (body.sky-saved) keeps the same internal stacking but pins each col to
// the aperture height.
.sky-page .sky-form-col {
flex: 1 0 auto;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
min-height: 100%;
border-right: none;
border-top: 0.1rem solid rgba(var(--terUser), 0.12);
}
.sky-page .sky-form-main {
flex: 0 0 auto;
width: 100%;
max-width: 22rem;
min-width: 0;
max-height: none;
overflow-y: visible;
}
// Override the base `.sky-form-col { background: --priUser }` (line 137,
// shared w. the in-room CAST SKY modal) so the dashsky form column sits
// on the same --duoUser olive as the rest of the .sky-page aperture.
// Scoped to `.sky-page` so the in-room modal's purple form-col stays
// intact (it sits over the room's --secUser bg + needs the contrast).
.sky-page .sky-form-col {
background: rgba(var(--duoUser), 1);
border-right-color: transparent;
}
// The (max-width:600px) block (written for the in-room CAST SKY modal where
// form-col is flex-row) sets align-self:flex-end on the btn — that's "right"
// once we flip to flex-column. Reset.
.sky-page .sky-form-col > #id_sky_confirm {
align-self: auto;
}
// Pre-save the wheel section is hidden — no preview wheel shunts the form
// downward & the user clearly sees SAVE SKY. The DEL btn rides along so
// async SAVE SKY can reveal it without a template re-render.
body:not(.sky-saved) .sky-page .sky-wheel-col {
display: none;
}
// On the same media envelopes where .btn-primary shrinks to 2.75rem, narrow
// the form-col's vertical gap so SAVE SKY tucks closer to #id_sky_status &
// the form fits a short landscape / portrait phone aperture without clipping.
@media (orientation: landscape) and (max-width: 1100px),
(orientation: portrait) {
.sky-page .sky-form-col {
gap: 0.4rem;
}
}
// ── Snap-binary aperture (post-save) ──────────────────────────────────────────
// Once a sky is saved, the .sky-page aperture flips into scroll-snap mode:
// the wheel section + form section each fill the aperture, so scrolling toggles
// between them rather than free-flowing. SAVE SKY (in sky.html's click handler)
// animates the aperture back to the top after a successful save.
//
// modal-body keeps height:100% (= aperture height), so its two flex children
// using min-height:100% each resolve to a full aperture each → modal-body's
// content overflows itself → .sky-page (overflow-y:auto) becomes the scroller.
body.sky-saved {
.sky-page {
scroll-snap-type: y mandatory;
}
// modal-body acts as a layout pass-through so the wheel & form cols become
// direct flex children of .sky-page, where `min-height: 100%` resolves
// against the aperture height (.sky-page has flex:1 + min-height:0 so its
// height is explicit in the parent flex column).
.sky-page .sky-modal-body {
display: contents;
}
.sky-page .sky-wheel-col,
.sky-page .sky-form-col {
scroll-snap-align: start;
scroll-snap-stop: always;
// height (not min-height) pins each section to EXACTLY one aperture so
// the snap-stops land at integer multiples of the viewport. min-height
// alone let .sky-svg's 480px max-height push the wheel section past one
// viewport on landscape mobile (~350px aperture), which created an
// intermediate scroll position between the wheel-end and the form-start.
height: 100%;
flex: 0 0 auto;
}
// Release the wheel-col aspect-ratio cap so the section fills the aperture;
// .sky-svg inside still renders square (its own aspect-ratio:1/1) and stays
// centered via the col's align-items:center. max-height: 100% caps the SVG
// to the aperture height — without it the .sky-svg's 480px max-* would let
// the wheel render taller than the section, overflowing into the form-col
// on cramped landscape phones.
.sky-page .sky-wheel-col {
aspect-ratio: auto;
max-width: none;
max-height: 100%;
width: 100%;
overflow: hidden;
.sky-svg {
max-width: 100%;
max-height: 100%;
}
}
// form-col loses its grow/min-height fill so the snap basis (min-height:100%
// above) wins instead — without this, two `flex: 1 0 auto` sections compete
// for the aperture height and the snap stops landing at the col boundaries.
.sky-page .sky-form-col {
flex: 0 0 auto;
}
}
// ── Sidebar z-index sink (landscape sidebars must go below backdrop) ───────────
@media (orientation: landscape) {
html.sky-open body .container .navbar,
html.sky-open body #id_footer {
z-index: 90;
}
}