/* astro_mash — dark celestial theme.
   Mobile-first: base rules target phone screens, min-width queries
   progressively enhance to tablet/desktop. */

:root {
  --bg: #0d1017;
  --bg-soft: #141926;
  --card: #171d2c;
  --card-edge: #232b40;
  --ink: #e8ecf6;
  --ink-dim: #9aa3b8;
  --accent: #e3b34c;
  --accent-soft: #8a6d2f;
  --error: #ff6b6b;
  --fire: #ff8a5c;
  --earth: #8fbf6f;
  --air: #7db4ff;
  --water: #b28dff;
  --harmonious: #58b0c4;
  --tense: #d9634f;
  --neutral: #c9a24b;
  color-scheme: dark;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* Hard overflow guard: nothing may push the page wider than the viewport.
   Wide tables still slide inside their own .table-wrap on purpose. */
html, body { max-width: 100%; }

body {
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  margin: 0;
  background:
    radial-gradient(1200px 600px at 80% -10%, #1b2237 0%, transparent 60%),
    radial-gradient(900px 500px at -10% 110%, #131a2c 0%, transparent 55%),
    var(--bg);
  color: var(--ink);
  font: 16px/1.5 "Segoe UI", system-ui, -apple-system, sans-serif;
  min-height: 100vh;
}

.top-bar {
  padding: 20px 16px 4px;
  text-align: center;
}

.top-bar h1 {
  margin: 0;
  font-size: 1.9rem;
  letter-spacing: 0.08em;
  font-weight: 300;
}

.top-bar h1 em {
  color: var(--accent);
  font-style: normal;
  font-weight: 600;
}

.tagline {
  margin: 4px 0 0;
  color: var(--ink-dim);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

main {
  max-width: 1180px;
  margin: 16px auto 40px;
  padding: 0 12px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.card {
  background: var(--card);
  border: 1px solid var(--card-edge);
  border-radius: 14px;
  padding: 18px 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
}

h2 {
  margin: 0 0 14px;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

/* One column on phones; lat/lon and date/time pair up once there is room.
   minmax(0, ...) lets tracks shrink below their content's min-content width,
   otherwise long inputs (city, tz) inflate the grid and overflow the page. */
.grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px 16px;
}

/* Body-sized ink labels: 0.8rem (12.8px) in dim grey was reported as
   unreadable on phones. Full ink keeps the field names clearly legible
   while the input boxes themselves still carry the visual weight. */
label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 1rem;
  color: var(--ink);
  letter-spacing: 0.04em;
}

label.checkbox {
  flex-direction: row;
  align-items: center;
  gap: 10px;
  font-size: 1rem;
  color: var(--ink);
  min-height: 44px;
}

label.checkbox input { width: 20px; height: 20px; }

.span-2 { grid-column: span 2; }

/* 18px reads and fills comfortably on phones (>=16px keeps iOS Safari from
   auto-zooming on focus); padding/min-height keep every control at or above
   a 48px touch target. */
input, select {
  background: var(--bg-soft);
  color: var(--ink);
  border: 1px solid var(--card-edge);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 18px;
  font-family: inherit;
  width: 100%;
  min-height: 48px;
}

input:focus, select:focus {
  outline: 2px solid var(--accent-soft);
  border-color: var(--accent-soft);
}

/* Disabled values (the 12:00 time fill) must stay legible: 0.45 opacity
   read as blank on phones. */
input:disabled { opacity: 0.6; }

/* Dimmer than ink so an empty field is still distinct from a typed value,
   but bright enough to read the hint (opacity:1 overrides Firefox). */
input::placeholder { color: #96a1b8; opacity: 1; }

input[type="checkbox"] { width: auto; }

.actions {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}

button {
  background: linear-gradient(180deg, #e8c064, var(--accent));
  color: #1b1408;
  border: 0;
  border-radius: 9px;
  padding: 14px 26px;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  min-height: 48px;
}

button:hover { filter: brightness(1.08); }
button:disabled { opacity: 0.5; cursor: wait; }

#form-status { color: var(--ink-dim); font-size: 0.85rem; text-align: center; }

.error {
  margin-top: 14px;
  padding: 10px 14px;
  border: 1px solid var(--error);
  border-radius: 8px;
  color: var(--error);
  background: rgba(255, 107, 107, 0.08);
  font-size: 0.9rem;
}

/* ---------- results ---------- */

.chart-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 8px;
}

.chart-head h3 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.chart-head .meta-line {
  color: var(--ink-dim);
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}

/* ---------- big three (Sun / Moon / Rising hero) ---------- */

.big-three {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin: 14px 0 2px;
}

.big-three-tile {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 12px 6px 10px;
  text-align: center;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), transparent 70%), var(--bg-soft);
  border: 1px solid var(--card-edge);
  border-radius: 12px;
}

.tile-label {
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.tile-glyph {
  font-size: 1.9rem;
  line-height: 1.2;
  color: var(--tile-color, var(--ink));
}

.tile-name { font-size: 0.82rem; font-weight: 600; }

.tile-detail { font-size: 0.72rem; color: var(--ink-dim); }

/* Wheel first, tables after it, on a single phone-sized column. */
.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  align-items: start;
}

/* Grid/flex children must be allowed to shrink below their content's
   min-content width, otherwise a wide table inflates the whole column and
   the page overflows instead of the table scrolling in its wrapper. */
main > .card,
.layout,
.layout > *,
.chart-section {
  min-width: 0;
}

.wheel-wrap { text-align: center; }
/* The wheel scales with its container via the viewBox; the stage's 640px cap
   is the wheel's intrinsic size, so it never renders larger than designed. */
.wheel-wrap svg { width: 100%; height: auto; }

.chart-section { margin-top: 22px; }
.chart-section > h4 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Tables read as wrapping rows of labelled values on phones (thead hidden,
   td prefixes come from data-label), so every column stays visible without
   hidden sideways scrolling. >=640px — where the full grid fits — restores
   the classic table; a wrapper then slides it if it ever grows wider. */
table {
  border-collapse: collapse;
  font-size: 0.88rem;
}

.table-wrap table,
.table-wrap tbody {
  display: block;
}

.table-wrap thead {
  display: none;
}

.table-wrap tr {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 14px;
  padding: 9px 2px;
  border-bottom: 1px solid var(--card-edge);
  text-align: left;
}

.table-wrap tbody tr:last-child {
  border-bottom: 0;
}

.table-wrap td {
  display: block;
  padding: 0;
  white-space: normal;
}

/* Column-name prefix keeps the stacked rows self-describing. */
.table-wrap td:not([data-label=""])::before {
  content: attr(data-label) " ";
  color: var(--ink-dim);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

@media (min-width: 640px) {
  .table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .table-wrap table {
    display: table;
    width: 100%;
  }

  .table-wrap thead {
    display: table-header-group;
  }

  .table-wrap tbody {
    display: table-row-group;
  }

  .table-wrap tr {
    display: table-row;
    padding: 0;
  }

  .table-wrap td {
    display: table-cell;
    padding: 8px 10px;
    white-space: nowrap;
  }

  .table-wrap td::before {
    content: none;
  }

  .table-wrap th {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid var(--card-edge);
    white-space: nowrap;
    color: var(--ink-dim);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    font-weight: 600;
  }
}

td.glyph { font-size: 1.15rem; }
td .dim { color: var(--ink-dim); }
tr:hover td { background: rgba(255, 255, 255, 0.03); }

.ret { color: var(--error); font-weight: 700; letter-spacing: 0.05em; }

.kind-pill {
  display: inline-block;
  padding: 1px 9px;
  border-radius: 999px;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
}

.kind-harmonious { color: var(--harmonious); border: 1px solid var(--harmonious); }
.kind-tense { color: var(--tense); border: 1px solid var(--tense); }
.kind-neutral { color: var(--neutral); border: 1px solid var(--neutral); }

.balances { display: flex; flex-direction: column; gap: 12px; }
.balance-row { display: grid; grid-template-columns: 96px 1fr 26px; gap: 10px; align-items: center; }
.balance-row .name { color: var(--ink-dim); font-size: 0.78rem; text-transform: capitalize; }
.bar { background: var(--bg-soft); border-radius: 999px; height: 12px; overflow: hidden; }
.bar > span { display: block; height: 100%; border-radius: 999px; }
.count { font-size: 0.85rem; text-align: right; }

.notes { margin-top: 20px; }
.notes li { color: var(--ink-dim); font-size: 0.85rem; margin-bottom: 4px; }

.lunation {
  margin-top: 18px;
  padding: 12px 16px;
  background: var(--bg-soft);
  border-radius: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  align-items: center;
  font-size: 0.9rem;
}

.lunation .moon-icon {
  font-size: 1.8rem;
  color: var(--ink);
}

.footer {
  text-align: center;
  color: var(--ink-dim);
  font-size: 0.78rem;
  padding: 18px 12px 30px;
}

.footer a { color: var(--accent-soft); text-decoration: none; }
.footer a:hover { color: var(--accent); }

/* ---------- chart reveal ---------- */

/* Sections stagger in top-to-bottom; --reveal-i is set per section in JS. */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

@keyframes wheel-in {
  from { opacity: 0; transform: scale(0.92) rotate(-4deg); }
  to { opacity: 1; transform: none; }
}

#results > * {
  animation: rise-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--reveal-i, 0) * 70ms);
}

.wheel-wrap svg {
  transform-origin: 50% 50%;
  animation: wheel-in 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.08s both;
}

/* ---------- living wheel ---------- */

/* The wheel keeps moving after the entrance: comet arcs circle the rim,
   planets twinkle in a staggered wave, the aspect web draws itself in and
   then breathes. Every animation runs on opacity/transform only, so phones
   composite them without layout work. */

.orbit-arc {
  fill: none;
  stroke-linecap: round;
}

/* SVG groups rotate about the viewBox centre (transform-box: view-box makes
   percentage origins resolve against the 640x640 viewBox, i.e. the wheel
   hub), not about their own bounding box. */
.orbit-spin,
.orbit-spin-rev {
  transform-box: view-box;
  transform-origin: 50% 50%;
}

.orbit-spin { animation: orbit-spin 26s linear infinite; }
.orbit-spin-rev { animation: orbit-spin-rev 43s linear infinite; }

@keyframes orbit-spin { to { transform: rotate(360deg); } }
@keyframes orbit-spin-rev { to { transform: rotate(-360deg); } }

/* --p-i is the planet's index around the wheel (set in chart.js), so the
   twinkle travels as a wave rather than blinking in lockstep. */
.planet {
  animation: planet-twinkle 3.8s ease-in-out infinite;
  animation-delay: calc(var(--p-i, 0) * 0.24s);
}

@keyframes planet-twinkle {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.78; }
}

/* The drawn-in aspect web then gently breathes as one body. */
.aspects { animation: aspect-breathe 7s ease-in-out 2.6s infinite; }

@keyframes aspect-breathe {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.8; }
}

/* Solid aspect lines draw in after the wheel lands: the renderer sets each
   line's dasharray to its own length and exposes it as --len, so the dash
   starts fully hidden and wipes open. Without the animation (reduced
   motion) the offset stays at its default 0 — the line is simply there. */
.aspect-line {
  animation: aspect-draw 0.9s cubic-bezier(0.33, 1, 0.68, 1) both;
  animation-delay: calc(0.45s + var(--a-i, 0) * 45ms);
}

@keyframes aspect-draw {
  from { stroke-dashoffset: var(--len, 0); }
  to { stroke-dashoffset: 0; }
}

/* Dashed (quincunx) lines cannot dash-draw without marching; they fade in. */
.aspect-fade {
  animation: aspect-fade-in 0.9s ease-out both;
  animation-delay: calc(0.45s + var(--a-i, 0) * 45ms);
}

@keyframes aspect-fade-in {
  from { opacity: 0; }
  to { opacity: 0.8; }
}

/* ---------- 3D chart wheel ---------- */

/* The stage is the perspective viewport AND the only touch surface:
   touch-action lives here and nowhere else, so dragging the wheel never
   scrolls the page and every pixel outside the wheel keeps native
   scrolling. Keyboard: arrows tilt/turn, Escape resets (wheel3d.js). */
.wheel-stage {
  position: relative;
  width: 100%;
  max-width: 640px;
  margin-inline: auto;
  perspective: 1100px;
  touch-action: none;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  /* Confines style/layout invalidation to the wheel: the per-frame pose
     writes can never re-layout or repaint the sections around it. */
  contain: layout style;
}

.wheel-stage.dragging { cursor: grabbing; }

/* While a drag owns the wheel the ambient loops (comets, twinkle, aspect
   breathe) pause, leaving all compositor headroom to the pose; they resume
   on release. */
.wheel-stage.dragging .orbit-spin,
.wheel-stage.dragging .orbit-spin-rev,
.wheel-stage.dragging .planet,
.wheel-stage.dragging .aspects,
.wheel-stage.dragging .aspect-line,
.wheel-stage.dragging .aspect-fade {
  animation-play-state: paused;
}

.wheel-stage:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 10px;
  border-radius: 20px;
}

/* The tilting disc. 88% of the stage buys the slack a perspective tilt
   needs: at the 46° tilt clamp the near edge projects ~8% wider, and that
   must stay inside the column or the phone overflows. JS (wheel3d.js) owns
   the live pose; the rotateX here is the no-JS default facing. */
.wheel-3d {
  position: relative;
  width: 88%;
  margin-inline: auto;
  aspect-ratio: 1;
  transform-style: preserve-3d;
  transform: rotateX(14deg);
  /* No will-change here: the disc and the turnable group are promoted to
     their own compositor layers only while a pose animation actually runs
     (.wheel-stage.moving), so at rest no full-resolution layers hold GPU
     texture memory on phones. */
}

.wheel-3d svg { display: block; }

/* The chart content the user turns (built in chart.js): rotates about the
   hub exactly like the ambient orbit groups, so every element box stays
   inside the viewBox at any angle. */
.wheel-spin {
  transform-box: view-box;
  transform-origin: 50% 50%;
}

/* Layer promotion, scoped to real motion: wheel3d.js toggles .moving while
   a drag, its release coast, or the demo turn writes the pose every frame.
   Compositor layers are expensive full-resolution textures on phones, so
   they exist only for the duration of the animation, never while the wheel
   just sits there. */
.wheel-stage.moving .wheel-3d,
.wheel-stage.moving .wheel-spin {
  will-change: transform;
}

/* Depth plates: discs translated behind the chart face give the wheel
   thickness; the near edge peeks out underneath when it tilts back. */
.wheel-plate {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
}

.plate-a {
  transform: translateZ(-12px);
  background: radial-gradient(circle at 35% 30%, #1c2438, #10152a 72%);
  border: 1px solid #2a3350;
}

.plate-b {
  transform: translateZ(-24px);
  background: #0b0f1d;
  box-shadow: 0 42px 60px -28px rgba(0, 0, 0, 0.8);
}

/* Ground shadow, flat in the stage (outside the 3D disc) so it reads as a
   floor under the model at any tilt. Pressing the wheel shrinks it, the
   "picked up" cue for a drag. */
.wheel-shadow {
  position: absolute;
  left: 4%;
  right: 4%;
  bottom: -3%;
  height: 12%;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.55), transparent);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.wheel-stage.dragging .wheel-shadow {
  opacity: 0.65;
  transform: scaleX(0.93);
}

/* Reset control: compact pill, revealed only once the pose has moved off
   the default facing (wheel3d.js toggles it). The ::after extends the hit
   area past the pill to a ~44px touch target. */
.wheel-reset {
  position: absolute;
  top: 2px;
  right: 2px;
  z-index: 2;
  min-height: 32px;
  padding: 4px 12px;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  background: rgba(20, 25, 38, 0.78);
  color: var(--ink-dim);
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  visibility: hidden;
}

.wheel-reset::after { content: ""; position: absolute; inset: -6px; }

.wheel-reset:hover { color: var(--ink); border-color: var(--ink-dim); filter: none; }

/* Planets are tappable detail points: hover hints on desktop (touch taps
   fire a synthetic :hover that would force a filter re-raster, so the glow
   is gated to real hover devices), and the tap highlight (planet-hot, set
   by wheel3d.js) glows the marker and glyph. */
.planet-mark,
.planet-glyph {
  transition: stroke-width 0.2s ease, filter 0.2s ease;
}

@media (hover: hover) and (pointer: fine) {
  .wheel-stage:not(.dragging) .planet:hover .planet-glyph {
    filter: drop-shadow(0 0 5px currentColor);
  }
}

.planet.planet-hot .planet-mark {
  stroke-width: 2.6;
}

.planet.planet-hot .planet-glyph {
  filter: drop-shadow(0 0 5px currentColor);
}

/* A drag that starts on a planet must not carry its glow filters: a
   drop-shadow re-rasterises the glyph on every frame it paints, and the
   filter transition would animate alongside the pose writes. During the
   drag both are gone; they return on release. */
.wheel-stage.dragging .planet-mark,
.wheel-stage.dragging .planet-glyph {
  filter: none;
  transition: none;
}

/* Planet tap tooltip: a compact card floating inside the stage (wheel3d.js
   clamps it on-screen), invisible until a tap targets a planet. */
.wheel-tooltip {
  position: absolute;
  z-index: 3;
  max-width: min(200px, 78%);
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: rgba(16, 21, 42, 0.95);
  border: 1px solid var(--card-edge);
  border-radius: 12px;
  color: var(--ink);
  font-size: 0.78rem;
  line-height: 1.45;
  letter-spacing: 0.02em;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
}

.wheel-tooltip.show {
  visibility: visible;
  opacity: 1;
  transform: none;
}

.wheel-tooltip-name { font-weight: 600; }
.wheel-tooltip-sign,
.wheel-tooltip-house { color: var(--ink-dim); }

.wheel-hint {
  margin: 10px 0 0;
  color: var(--ink-dim);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
}

/* Programmatic pose changes (reset, arrow-key nudges) ease in; raw drags
   and inertia write the transform every frame and must never transition. */
.settle .wheel-3d,
.settle .wheel-spin {
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

/* ---------- computing skeleton ---------- */

@keyframes shimmer { to { background-position: -200% 0; } }

.skeleton { display: flex; flex-direction: column; gap: 16px; }

.skeleton::before {
  content: "";
  width: min(76vw, 330px);
  aspect-ratio: 1;
  margin: 0 auto;
  border-radius: 50%;
  background: linear-gradient(100deg, var(--bg-soft) 40%, #232b40 50%, var(--bg-soft) 60%);
  background-size: 200% 100%;
  animation: shimmer 1.3s linear infinite;
}

.skeleton > div {
  height: 14px;
  border-radius: 8px;
  background: linear-gradient(100deg, var(--bg-soft) 40%, #232b40 50%, var(--bg-soft) 60%);
  background-size: 200% 100%;
  animation: shimmer 1.3s linear infinite;
}

.skeleton > div:nth-child(1) { width: 62%; }
.skeleton > div:nth-child(2) { width: 88%; }
.skeleton > div:nth-child(3) { width: 74%; }

/* ---------- saved charts ---------- */

.saved-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.saved-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: stretch;
}

.saved-load {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  text-align: left;
  background: var(--bg-soft);
  border: 1px solid var(--card-edge);
  border-radius: 10px;
  color: var(--ink);
  padding: 14px 16px;
  min-height: 44px;
  font-size: 0.9rem;
}

.saved-load:hover { filter: none; border-color: var(--accent-soft); }

/* Name > meta > chips hierarchy, all at comfortable reading sizes: the
   date/time meta used to render at 0.78rem (12.5px), unreadable on phones. */
.saved-name { font-weight: 600; font-size: 1.05rem; line-height: 1.3; overflow-wrap: anywhere; }

.saved-meta { color: var(--ink-dim); font-size: 0.95rem; line-height: 1.45; overflow-wrap: anywhere; }

.saved-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }

.chip {
  font-size: 0.85rem;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--card-edge);
  color: var(--chip-color, var(--ink-dim));
  white-space: nowrap;
}

.saved-empty { color: var(--ink-dim); font-size: 0.95rem; margin: 4px 0 0; }

/* ---------- button variants ---------- */

button { transition: transform 0.08s ease, filter 0.15s ease; }

button:active { transform: scale(0.97); }

button.ghost {
  background: transparent;
  border: 1px solid var(--card-edge);
  color: var(--ink-dim);
}

button.ghost:hover { color: var(--ink); filter: none; border-color: var(--ink-dim); }

button.danger {
  background: transparent;
  border: 1px solid rgba(255, 107, 107, 0.45);
  color: var(--error);
}

button.danger:hover { filter: none; border-color: var(--error); }

/* Armed = second tap confirms the destructive action. */
button.danger.armed,
button.danger.armed:hover {
  background: var(--error);
  border-color: var(--error);
  color: #fff;
  font-weight: 700;
}

.saved-delete,
.save-actions button {
  padding: 9px 14px;
  min-height: 44px;
  font-size: 0.82rem;
}

/* ---------- save bar ---------- */

.save-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  justify-content: space-between;
}

.save-badge {
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent-soft);
  border-radius: 999px;
  padding: 3px 10px;
  white-space: nowrap;
}

.save-who { font-weight: 600; overflow-wrap: anywhere; flex: 1 1 120px; }

.save-actions { display: flex; flex-wrap: wrap; gap: 8px; }

.save-hint {
  color: var(--ink-dim);
  font-size: 0.85rem;
  flex: 1 1 200px;
  min-width: 0;
}

/* ---------- toasts ---------- */

#toasts {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  z-index: 50;
  width: min(92vw, 420px);
  pointer-events: none;
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

.toast {
  max-width: 100%;
  background: #1d2536;
  border: 1px solid var(--card-edge);
  border-left: 3px solid var(--accent);
  color: var(--ink);
  padding: 12px 16px;
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  font-size: 0.9rem;
  text-align: center;
  animation: toast-in 0.25s ease both;
  overflow-wrap: anywhere;
}

.toast-warn { border-left-color: var(--error); }

.toast-out { opacity: 0; transform: translateY(8px); transition: opacity 0.25s, transform 0.25s; }

/* ---------- reduced motion ---------- */

/* Freeze to the final static state: entrance animations drop to their
   to-state values (nothing stays hidden — aspect lines default to a 0 dash
   offset), ongoing loops stop in place, and the purely decorative comet
   arcs disappear rather than sitting as stray marks. The 3D wheel keeps its
   static perspective facing, and dragging stays available as an INSTANT,
   user-initiated pose change (wheel3d.js skips inertia and settle easing) —
   self-directed manipulation, not motion the page adds on its own. */
@media (prefers-reduced-motion: reduce) {
  #results > *,
  .wheel-wrap svg,
  .skeleton::before,
  .skeleton > div,
  .toast,
  .orbit,
  .orbit-spin,
  .orbit-spin-rev,
  .planet,
  .planet-mark,
  .planet-glyph,
  .aspects,
  .aspect-line,
  .aspect-fade,
  .wheel-3d,
  .wheel-spin,
  .wheel-reset,
  .wheel-shadow,
  .wheel-tooltip {
    animation: none !important;
    transition: none !important;
  }

  .orbit { display: none; }
}

/* ---------- tablet ---------- */

@media (min-width: 480px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .actions {
    flex-direction: row;
    align-items: center;
  }

  button { width: auto; }

  .top-bar { padding: 28px 24px 8px; }
  .top-bar h1 { font-size: 2.2rem; }
  .tagline { font-size: 0.85rem; }
}

/* ---------- desktop ---------- */

@media (min-width: 960px) {
  main { margin: 24px auto 48px; padding: 0 16px; gap: 24px; }

  .card { padding: 24px 28px; }

  .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px 18px; }

  /* The planets table needs ~510px at desktop font size; the wider second
     track keeps it inside its card instead of sliding in the wrapper. */
  .layout { grid-template-columns: minmax(340px, 1fr) minmax(420px, 1.4fr); gap: 28px; }

  /* Compact on desktop but never below the 16px readability floor. */
  input, select { padding: 9px 12px; font-size: 1rem; min-height: 0; }

  button { padding: 11px 26px; min-height: 0; }

  table { font-size: 0.92rem; }

  .table-wrap th, .table-wrap td { padding: 7px 10px; }
}
