/* =====================================================================
   DAILY GIFT — the reveal
   ---------------------------------------------------------------------
   The contents used to be printed above a chest that had not been opened
   yet. The JS was correct — it set `hidden` — but
   `.daily-gift-headline strong { display: block }` beat the UA's
   `[hidden] { display: none }`, so the attribute did nothing and the
   surprise was given away before the player touched anything.

   Every rule below that hides something states its [hidden] case
   explicitly, so the attribute can never be silently overridden again.
   ===================================================================== */

.daily-gift-line { display: block; }
.daily-gift-line[hidden] { display: none; }

/* The line that names the prize, once it is earned. */
.daily-gift-prize {
  margin-top: 8px;
  color: var(--gold-bright, #ffd94d);
  font-size: 1.12em;
  font-weight: 700;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.5), 0 0 20px rgba(255, 217, 77, 0.75);
  /* Arrives rather than appearing. */
  animation: giftPrizeIn 620ms cubic-bezier(0.16, 1.06, 0.3, 1.4) both;
}
@keyframes giftPrizeIn {
  0%   { opacity: 0; transform: translateY(10px) scale(0.9); filter: blur(4px); }
  60%  { opacity: 1; filter: blur(0); }
  100% { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------
   THE CHEST — before it is opened it should look like it WANTS opening.
   --------------------------------------------------------------------- */

.gift-chest {
  position: relative;
  border: none;
  background: none;
  cursor: pointer;
  /* Breathing, with a slow tilt. A perfectly still object reads as a
     picture; this reads as something waiting. */
  animation: giftBreathe 2600ms ease-in-out infinite;
  transition: transform 200ms ease;
}
.gift-chest:hover { transform: scale(1.05); }
@keyframes giftBreathe {
  0%, 100% { transform: translateY(0) rotate(-1.2deg) scale(1); }
  50%      { transform: translateY(-8px) rotate(1.2deg) scale(1.03); }
}

/* A halo underneath, pulsing out of phase with the breath so the two
   never look like one animation. */
.gift-chest::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 55%;
  width: 130%;
  height: 130%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 217, 77, 0.34), transparent 62%);
  animation: giftHalo 3200ms ease-in-out infinite;
  pointer-events: none;
  z-index: -1;
}
@keyframes giftHalo {
  0%, 100% { opacity: 0.45; transform: translate(-50%, -50%) scale(0.92); }
  50%      { opacity: 0.9;  transform: translate(-50%, -50%) scale(1.12); }
}

/* The shake before it goes — winding up, not just vibrating. */
.gift-chest.shaking {
  animation: giftWindUp 700ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes giftWindUp {
  0%   { transform: rotate(0deg) scale(1); }
  12%  { transform: rotate(-7deg) scale(1.04); }
  26%  { transform: rotate(7deg) scale(1.05); }
  40%  { transform: rotate(-9deg) scale(1.07); }
  54%  { transform: rotate(9deg) scale(1.09); }
  68%  { transform: rotate(-6deg) scale(1.12); }
  82%  { transform: rotate(4deg) scale(1.16); }
  100% { transform: rotate(0deg) scale(1.22); }
}

/* The moment it opens: it launches, spins, and is gone — the burst it
   leaves behind is what the rewards come out of. */
.gift-chest.opened {
  animation: giftLaunch 760ms cubic-bezier(0.22, 0.9, 0.24, 1) forwards;
  pointer-events: none;
}
@keyframes giftLaunch {
  0%   { transform: scale(1.22) rotate(0deg);      opacity: 1; }
  30%  { transform: scale(1.5) rotate(-14deg);     opacity: 1; }
  100% { transform: scale(0.15) rotate(28deg) translateY(-26%); opacity: 0; }
}
.gift-chest.opened::before { animation: none; opacity: 0; transition: opacity 300ms ease; }

/* The burst it leaves. */
.gift-chest-burst {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, var(--gold-bright, #ffd94d) 40%, transparent 72%);
  opacity: 0;
  pointer-events: none;
}
.gift-chest.opened .gift-chest-burst {
  animation: giftBurst 900ms cubic-bezier(0.1, 0.8, 0.2, 1) 180ms forwards;
}
@keyframes giftBurst {
  0%   { opacity: 1; transform: scale(1); }
  70%  { opacity: 0.55; }
  100% { opacity: 0; transform: scale(46); }
}

/* A second ring, slower and thinner, so the burst has depth rather than
   being one expanding disc. */
.gift-chest.opened::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.9);
  opacity: 0;
  pointer-events: none;
  animation: giftRing 1100ms cubic-bezier(0.1, 0.8, 0.2, 1) 240ms forwards;
}
@keyframes giftRing {
  0%   { opacity: 0.9; transform: scale(1); border-width: 3px; }
  100% { opacity: 0; transform: scale(26); border-width: 0.5px; }
}

/* The rays behind everything spin up as the chest opens. */
.daily-gift-modal.gift-opened .daily-gift-rays {
  animation: giftRaysSpin 6s linear infinite;
}
@keyframes giftRaysSpin { to { transform: rotate(360deg); } }

/* The tiles arrive one after another, thrown out of the burst. */
.daily-gift-modal.gift-opened .gift-icon {
  animation: giftTileOut 640ms cubic-bezier(0.16, 1.1, 0.3, 1.35) both;
}
@keyframes giftTileOut {
  0%   { opacity: 0; transform: scale(0.2) translateY(-40px) rotate(-12deg); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: none; }
}

/* COLLECT arrives last and asks to be pressed. */
.daily-gift-modal.gift-opened .daily-gift-collect {
  animation: giftCollectIn 520ms cubic-bezier(0.16, 1.06, 0.3, 1.4) 360ms both,
             giftCollectPulse 1800ms ease-in-out 900ms infinite;
}
@keyframes giftCollectIn {
  from { opacity: 0; transform: translateY(14px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}
@keyframes giftCollectPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 217, 77, 0); }
  50%      { box-shadow: 0 0 22px 3px rgba(255, 217, 77, 0.55); }
}

@media (prefers-reduced-motion: reduce) {
  .gift-chest,
  .gift-chest::before,
  .gift-chest.shaking,
  .gift-chest.opened,
  .gift-chest.opened::after,
  .gift-chest.opened .gift-chest-burst,
  .daily-gift-modal.gift-opened .daily-gift-rays,
  .daily-gift-modal.gift-opened .gift-icon,
  .daily-gift-modal.gift-opened .daily-gift-collect,
  .daily-gift-prize { animation: none; }
  /* The launch must still clear the chest out of the way, or the tiles
     appear behind it. */
  .gift-chest.opened { opacity: 0; }
}

@media (max-width: 720px) {
  /* The idle breath and halo run forever on a page that also has to spin
     reels; the reveal itself is what matters and it stays. */
  .gift-chest, .gift-chest::before { animation: none; }
  .daily-gift-modal.gift-opened .daily-gift-collect { animation: giftCollectIn 520ms ease 360ms both; }
}

/* --- the daily gift clock, above SHOP --- */

.daily-timer[hidden] { display: none; }
.daily-timer {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
  padding: 4px 10px 4px 5px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(255, 214, 138, 0.34);
  background: rgba(10, 6, 20, 0.72);
  color: rgba(255, 255, 255, 0.9);
  cursor: default;
  transition: border-color 200ms ease, background 200ms ease, transform 200ms ease;
}
.daily-timer-icon {
  width: 20px;
  height: 20px;
  object-fit: contain;
  /* Greyed while it is only a countdown — it is not yours yet. */
  filter: grayscale(1) brightness(0.8);
  transition: filter 260ms ease;
}
.daily-timer-body {
  display: flex;
  align-items: baseline;
  gap: 5px;
  line-height: 1;
}
.daily-timer-label {
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.5);
}
.daily-timer-value {
  font-family: 'Orbitron', sans-serif;
  font-size: 0.74rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;   /* the row must not jitter */
  color: var(--gold-bright, #ffd94d);
}

/* A gift actually waiting. Now it is a button, and it says so. */
.daily-timer.is-ready {
  cursor: pointer;
  border-color: var(--gold-bright, #ffd94d);
  background: linear-gradient(180deg, rgba(255, 214, 138, 0.22), rgba(255, 184, 0, 0.08));
  animation: dailyReadyPulse 2000ms ease-in-out infinite;
}
.daily-timer.is-ready .daily-timer-icon { filter: none; }
.daily-timer.is-ready:hover { transform: scale(1.05); }
@keyframes dailyReadyPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 217, 77, 0); }
  50%      { box-shadow: 0 0 16px 2px rgba(255, 217, 77, 0.5); }
}

@media (prefers-reduced-motion: reduce) {
  .daily-timer.is-ready { animation: none; }
  .daily-timer.is-ready:hover { transform: none; }
}
@media (max-width: 720px) {
  /* The pulse STAYS on mobile. It was switched off here, which is where
     the pill matters most now that the board no longer opens itself. */
  .daily-timer { padding: 3px 8px 3px 4px; }
  .daily-timer-icon { width: 17px; height: 17px; }
  .daily-timer-value { font-size: 0.68rem; }
}

/* --- the rays were manufacturing both of the modal's scrollbars ---
   `inset: -40%` deliberately bleeds them past the modal so the conic
   fills the corners — but an absolutely-positioned child that overflows
   still counts toward scrollWidth/scrollHeight, and the modal scrolls
   rather than clips. Measured: a 1147px rays box inside a 469px modal,
   with 859px of scroll height against 518px of client height.

   The element becomes the CLIPPER and the oversized burst moves into its
   ::before, so the bleed survives and the scroll area does not. */
.daily-gift-rays {
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  background: none;
  -webkit-mask-image: none;
  mask-image: none;
  animation: none;
}
.daily-gift-rays::before {
  content: '';
  position: absolute;
  inset: -40%;
  background: repeating-conic-gradient(
    from 0deg,
    rgba(255, 217, 77, 0.14) 0deg 6deg,
    transparent 6deg 12deg);
  -webkit-mask-image: radial-gradient(circle, #000 10%, transparent 60%);
  mask-image: radial-gradient(circle, #000 10%, transparent 60%);
  animation: showcaseSpin 22s linear infinite;
}
/* The spin-up on opening moves to the child with everything else. */
.daily-gift-modal.gift-opened .daily-gift-rays { animation: none; }
.daily-gift-modal.gift-opened .daily-gift-rays::before {
  animation: showcaseSpin 6s linear infinite;
}

/* Belt and braces: nothing decorative should ever be able to scroll this
   sideways. The vertical ceiling stays, because real content can exceed
   it and the COLLECT button has to stay reachable. */
.daily-gift-modal { overflow-x: hidden; }

@media (prefers-reduced-motion: reduce) {
  .daily-gift-rays::before,
  .daily-gift-modal.gift-opened .daily-gift-rays::before { animation: none; }
}

/* --- room for the chest to move in ---
   The chest sat at 372px inside a 371px content box: exact, and fine
   while it was static. The idle breathe (scale 1.03 plus a 1.2deg tilt)
   and the launch peak then pushed it past the modal, which clips on the
   x-axis — so the most important beat in the animation was the one
   getting its edges cut off.

   The button is capped below the content box so its whole animated
   envelope fits. The impact does not come from the chest being large;
   it comes from the burst, the rings and the screen-level effects
   (flash, edge pulse, fireworks, coin shower), none of which this
   modal clips. */
.gift-chest {
  display: block;
  width: min(84%, 320px);
  margin: 0 auto;
  max-width: 84%;
}
.gift-chest-img { max-width: 100%; }

/* The launch stays inside the frame. 1.5 overflowed a narrow modal by a
   third of its width; at 1.18 the lunge still reads and nothing is cut. */
@keyframes giftLaunch {
  0%   { transform: scale(1.14) rotate(0deg);   opacity: 1; }
  30%  { transform: scale(1.18) rotate(-14deg); opacity: 1; }
  100% { transform: scale(0.15) rotate(28deg) translateY(-26%); opacity: 0; }
}
/* The wind-up peak comes down to match. */
@keyframes giftWindUp {
  0%   { transform: rotate(0deg) scale(1); }
  12%  { transform: rotate(-7deg) scale(1.02); }
  26%  { transform: rotate(7deg) scale(1.03); }
  40%  { transform: rotate(-9deg) scale(1.05); }
  54%  { transform: rotate(9deg) scale(1.07); }
  68%  { transform: rotate(-6deg) scale(1.09); }
  82%  { transform: rotate(4deg) scale(1.11); }
  100% { transform: rotate(0deg) scale(1.14); }
}

/* --- COLLECT must always be reachable ---
   Opening the gift adds the reward tiles, which makes the modal taller
   than the viewport ceiling — and COLLECT, being last, went below the
   fold behind a scrollbar. The same fault the chest overlay had, and the
   same fix: the modal becomes a column, the middle scrolls, and the
   button that finishes the interaction is pinned where it cannot move. */
.daily-gift-modal {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  overflow-y: hidden;          /* the SCROLLER is the icons row, not this */
}
/* The rays are decoration and must not take part in the column. */
.daily-gift-rays { position: absolute; }

.daily-gift-modal .daily-gift-headline,
.daily-gift-modal .gift-chest,
.daily-gift-modal .daily-gift-streak,
.daily-gift-modal .daily-gift-collect { flex: 0 0 auto; }

.daily-gift-icons {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  align-content: flex-start;
}
.daily-gift-collect { margin-top: 12px; }

/* =====================================================================
   DAILY GIFT — the opened state, corrected
   ---------------------------------------------------------------------
   Two faults, both mine.

   1. The chest launched itself off the screen. It should stay where it
      is, exactly like a regular chest: the chest is the thing you opened
      and it belongs in the middle of the panel with its rewards beneath.

   2. `flex: 1 1 auto` on the rewards row let it shrink to whatever was
      left over — measured at 79px holding tiles crushed to 22px, with
      the rest clipped. The rewards are the POINT of the panel; they take
      their natural height and nothing is allowed to squash them.
   ===================================================================== */

/* The chest stays. It settles rather than leaving. */
.gift-chest.opened {
  animation: giftSettle 620ms cubic-bezier(0.22, 0.9, 0.24, 1) forwards;
  pointer-events: none;
}
@keyframes giftSettle {
  0%   { transform: scale(1.14) rotate(0deg); }
  35%  { transform: scale(1.2) rotate(-5deg); }
  100% { transform: scale(1) rotate(0deg); }
}
/* Opened, it is lit from inside rather than merely sitting there. */
.gift-chest.opened .gift-chest-img {
  filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.75))
          drop-shadow(0 0 34px rgba(255, 217, 77, 0.85))
          brightness(1.12);
}
/* Smaller once open, so the rewards below get the room they need. */
.daily-gift-modal.gift-opened .gift-chest {
  width: min(58%, 210px);
  transition: width 520ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The rewards row takes the space it needs. */
.daily-gift-icons {
  flex: 0 0 auto;
  min-height: 0;
  overflow: visible;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 4px;
}
.daily-gift-icons .gift-icon { flex: 0 0 auto; }

/* The modal scrolls as a whole again, with COLLECT pinned under it.
   Scrolling the tile row on its own was what allowed the squash. */
.daily-gift-modal {
  overflow-y: auto;
  overflow-x: hidden;
}
.daily-gift-modal .daily-gift-collect {
  position: sticky;
  bottom: 0;
  z-index: 2;
  flex: 0 0 auto;
  margin-top: 14px;
}
/* A fade under the sticky button so tiles do not appear to slide out
   from behind a hard edge. */
.daily-gift-modal .daily-gift-collect::before {
  content: '';
  position: absolute;
  left: -30px;
  right: -30px;
  bottom: 100%;
  height: 18px;
  background: linear-gradient(transparent, rgba(64, 6, 12, 0.9));
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .gift-chest.opened { animation: none; }
  .daily-gift-modal.gift-opened .gift-chest { transition: none; }
}

/* --- balances shown in full ---
   The tiles size to their content, so the border genuinely grows with
   the number (measured 84px at "12" up to 155px at "128,000,000"). The
   only risk is a very large balance on a very narrow screen, so the row
   is allowed to wrap rather than push anything off the edge. */
.hud {
  flex-wrap: wrap;
  row-gap: 8px;
}
.hud-stat { min-width: 0; }
.hud-value {
  /* Digits must not reflow the tile as they tick. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}


/* =====================================================================
   THE 30-DAY REWARD CALENDAR
   ---------------------------------------------------------------------
   Warm paper rather than the arcade's neon: a calendar is a thing you
   read, and thirty cells of glowing cyan on black is a wall of noise.
   The palette is the tan/amber of the reference — parchment cells, a
   honey header on each card, a green header on the day you can take, and
   a red wax seal on the days already collected.

   Appended to daily-gift.css, which is where the overlay it lives in is
   already styled.
   ===================================================================== */

.daily-cal-modal {
  --cal-paper:  #f6e4c3;
  --cal-cell:   #e8cfa4;
  --cal-head:   #f6c66a;
  --cal-headtx: #7a4a17;
  --cal-edge:   #c79a5c;
  --cal-live:   #7ed321;
  --cal-livetx: #2c5a06;
  --cal-seal:   #d63b2f;

  width: min(60rem, 96vw);
  max-width: 96vw;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: clamp(12px, 2vmin, 22px);
  background: linear-gradient(180deg, #f2d9ad, #e2bd85);
  border: 3px solid #b07d3e;
  border-radius: 22px;
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255,255,255,0.5);
}

.daily-cal-sub {
  margin: 0;
  text-align: center;
  font-family: "Orbitron", sans-serif;
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  color: #8a5a24;
}

/* --- THE MILESTONE RAIL --------------------------------------------- */
.cal-rail {
  position: relative;
  height: 74px;
  margin: 26px 6px 4px;
  padding: 0 6px;
  border-radius: 14px;
  background: linear-gradient(180deg, #d9b47e, #c99f65);
  border: 2px solid #a97c42;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.4);
}
.cal-rail-track {
  position: absolute;
  left: 46px;
  right: 12px;
  bottom: 20px;
  height: 16px;
  border-radius: 999px;
  background: #9a6f3c;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.4);
  overflow: hidden;
}
.cal-rail-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #b6f05a, #6fbf16);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.55);
  transition: width 520ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* The wax seal that caps the start of the run. */
.cal-rail-seal {
  position: absolute;
  left: 6px;
  bottom: 8px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #ff6a5c, var(--cal-seal) 62%, #8e1a12);
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.45);
}
.cal-rail-seal::after {
  content: '✓';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 1.15rem;
  font-weight: 900;
  color: #ffd94d;
}

.cal-rail-nodes { position: absolute; left: 46px; right: 12px; bottom: 20px; height: 16px; }
.cal-node {
  position: absolute;
  bottom: -4px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
}
.cal-node-peg {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: linear-gradient(180deg, #f7e3bd, #e0c393);
  border: 2px solid #a97c42;
  font-family: "Orbitron", sans-serif;
  font-size: 0.7rem;
  font-weight: 800;
  color: #7a4a17;
}
.cal-node-gift {
  display: flex;
  gap: 2px;
  margin-bottom: 4px;
  transform: translateY(-6px);
}
.cal-node-gift .cal-reward { padding: 0; background: none; border: 0; }
.cal-node-gift img { width: 34px; height: 34px; object-fit: contain; }
.cal-node.is-claimed .cal-node-peg {
  background: linear-gradient(180deg, #b6f05a, #6fbf16);
  border-color: #4e8f0c;
  color: #2c5a06;
}
.cal-node.is-current .cal-node-peg {
  border-color: var(--cal-seal);
  box-shadow: 0 0 0 3px rgba(214, 59, 47, 0.35);
}

/* --- THE GRID -------------------------------------------------------- */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  gap: 10px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px;
  min-height: 0;
}

.cal-day {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  overflow: hidden;
  background: var(--cal-cell);
  border: 2px solid var(--cal-edge);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.18);
  min-height: 118px;
}
.cal-day-head {
  padding: 6px 8px;
  text-align: center;
  font-family: "Orbitron", sans-serif;
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--cal-headtx);
  background: linear-gradient(180deg, #fbdb9a, var(--cal-head));
  border-bottom: 2px solid var(--cal-edge);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
.cal-day-body {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 6px;
}

.cal-reward {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 0;
}
.cal-reward-art img,
.cal-reward-art { width: 42px; height: 42px; }
.cal-reward-art img { object-fit: contain; }
.cal-reward-art svg { width: 42px; height: 42px; }
.cal-reward-art > * { max-width: 100%; max-height: 100%; }
.cal-reward-amt {
  font-family: "Orbitron", sans-serif;
  font-size: 0.72rem;
  font-weight: 800;
  color: #fff;
  -webkit-text-stroke: 3px #8a4a12;
  paint-order: stroke fill;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);
}
.cal-reward.is-big .cal-reward-art,
.cal-reward.is-big .cal-reward-art img { width: 58px; height: 58px; }
.cal-reward.is-big .cal-reward-amt { font-size: 0.95rem; }

/* A milestone reward is the one on TOP of the day, so it is marked. */
.cal-reward.is-milestone {
  padding: 3px 5px 1px;
  border-radius: 10px;
  background: rgba(255, 214, 102, 0.5);
  box-shadow: inset 0 0 0 2px rgba(214, 59, 47, 0.55);
}

/* --- STATES ---------------------------------------------------------- */
/* Already taken: paper goes flat and the wax seal lands on it. */
.cal-day.is-claimed { background: #d9bd91; }
.cal-day.is-claimed .cal-day-body { opacity: 0.55; }
.cal-seal {
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #ff6a5c, var(--cal-seal) 62%, #8e1a12);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}
.cal-seal::after {
  content: '✓';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 0.9rem;
  font-weight: 900;
  color: #ffd94d;
}

/* Today: the green header of the reference, and it pulses so the eye
   finds it in thirty cells without having to read any of them. */
.cal-day.is-current {
  border-color: #4e8f0c;
  background: #fdf3df;
  box-shadow: 0 0 0 3px rgba(126, 211, 33, 0.45), 0 4px 0 rgba(0, 0, 0, 0.2);
  animation: calToday 1900ms ease-in-out infinite;
}
.cal-day.is-current .cal-day-head {
  background: linear-gradient(180deg, #a9ee52, var(--cal-live));
  border-bottom-color: #4e8f0c;
  color: var(--cal-livetx);
}
@keyframes calToday {
  0%, 100% { box-shadow: 0 0 0 3px rgba(126, 211, 33, 0.40), 0 4px 0 rgba(0,0,0,0.2); }
  50%      { box-shadow: 0 0 0 7px rgba(126, 211, 33, 0.22), 0 4px 0 rgba(0,0,0,0.2); }
}

/* Still to come. Readable — the whole point is reading ahead — just
   plainly not yours yet. */
.cal-day.is-ahead { background: #eddcbc; }
.cal-day.is-ahead .cal-day-body { opacity: 0.9; }

/* A milestone day is worth spotting from across the board. */
.cal-day.is-milestone { border-color: #d63b2f; }
.cal-day.is-milestone .cal-day-head {
  background: linear-gradient(180deg, #ffd18a, #f0a23c);
}

/* Day 30 takes the full row, the way the last cell does on the paper
   calendar this is modelled on. */
.cal-day.is-finale {
  grid-column: 1 / -1;
  min-height: 132px;
}
.cal-day.is-finale .cal-day-body { gap: 18px; }

/* --- CHROME ---------------------------------------------------------- */
.daily-cal-modal .daily-gift-streak {
  text-align: center;
  font-size: 0.78rem;
  color: #7a4a17;
}
.daily-cal-modal .daily-gift-collect { align-self: center; min-width: 15rem; }
.daily-cal-modal .daily-gift-collect:disabled { opacity: 0.55; cursor: default; }

.cal-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 2px solid #a97c42;
  background: rgba(255, 245, 225, 0.85);
  color: #8a5a24;
  font-size: 1rem;
  font-weight: 800;
  cursor: pointer;
}
.cal-close:hover { background: #fff; }

@media (max-width: 720px) {
  /* Three across, like the reference. minmax(94px) fell to two columns
     in a 300px content box once gaps were counted. */
  .cal-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; }
  .cal-day { min-height: 104px; }
  .cal-reward-art, .cal-reward-art img { width: 34px; height: 34px; }
  .cal-rail { height: 66px; margin-top: 22px; }
}

@media (prefers-reduced-motion: reduce) {
  .cal-day.is-current { animation: none; }
  .cal-rail-fill { transition: none; }
}

/* --- RAIL GEOMETRY, CORRECTED ----------------------------------------
   The gift icons are stacked ABOVE their peg, and the peg sits on the
   track near the bottom of the rail. At the first pass the rail was
   74px while that stack needed ~84px, so the gifts punched out of the
   top of the rail and collided with the title banner.

   The rail is now tall enough to contain its own contents, and the
   amounts under each gift are small enough that two rewards on one node
   (day 22 pays spins AND coins) stay legible side by side. */
.daily-cal-modal .cal-rail {
  height: 104px;
  /* The title ribbon hangs below its own box, so the rail has to start
     clear of it or the milestone gifts sit under the banner. */
  margin: 34px 6px 4px;
}
.daily-cal-modal .cal-rail-track,
.daily-cal-modal .cal-rail-nodes { bottom: 16px; }
.daily-cal-modal .cal-rail-seal { bottom: 4px; }

.daily-cal-modal .cal-node-gift {
  gap: 6px;
  margin-bottom: 6px;
  transform: none;
  align-items: flex-end;
}
.daily-cal-modal .cal-node-gift .cal-reward-art,
.daily-cal-modal .cal-node-gift .cal-reward-art img,
.daily-cal-modal .cal-node-gift img.cal-reward-art { width: 30px; height: 30px; }
.daily-cal-modal .cal-node-gift .cal-reward-amt {
  font-size: 0.56rem;
  -webkit-text-stroke: 2px #8a4a12;
}
/* The rail's own tiles are already inside a marked-out node, so the
   milestone chip that marks them apart INSIDE a day card would just be
   noise here. */
.daily-cal-modal .cal-node-gift .cal-reward.is-milestone {
  background: none;
  box-shadow: none;
  padding: 0;
}

/* Room for thirty cells. The grid is the point of the panel, so it takes
   the height the modal can spare rather than a fixed slice of it. */
.daily-cal-modal .cal-grid {
  max-height: min(52vh, 460px);
  align-content: start;
}

/* --- ROOM FOR THE RIBBON ---------------------------------------------
   .daily-gift-banner is position:absolute, so it takes no space in the
   flow and every child starts underneath it. Patching that with a
   margin on whichever element happened to collide is how the subtitle
   ended up behind the ribbon on mobile after the rail had been fixed on
   desktop — two elements, two ad-hoc margins, one of them overridden by
   the mobile block.

   The modal reserves the ribbon's height once, at the top, and nothing
   below it needs to know the ribbon exists. */
.daily-cal-modal { padding-top: 88px; }
.daily-cal-modal .cal-rail { margin-top: 6px; }
@media (max-width: 720px) {
  .daily-cal-modal { padding-top: 78px; }
  .daily-cal-modal .cal-rail { margin-top: 6px; }
}

/* --- TODAY BEATS MILESTONE ON THE HEADER -----------------------------
   `.cal-day.is-milestone .cal-day-head` sits after `.cal-day.is-current
   .cal-day-head` in this sheet and has identical specificity, so on days
   8, 15, 22 and 30 the amber milestone header won and the ONE cell the
   player can actually claim stopped looking claimable. Both classes are
   true on those days; the green has to win. */
.cal-day.is-current.is-milestone .cal-day-head {
  background: linear-gradient(180deg, #a9ee52, var(--cal-live));
  border-bottom-color: #4e8f0c;
  color: var(--cal-livetx);
}

/* --- THE CALENDAR BUTTON BESIDE THE DAILY PILL -----------------------
   The pill spends most of its life showing a countdown, which does not
   read as pressable. This says the board exists. Its own button, not a
   child of the pill: nesting one button in another is invalid and the
   inner one never receives the click. */
.daily-info {
  flex: none;
  width: 26px;
  height: 26px;
  margin-left: 6px;
  border-radius: 50%;
  cursor: pointer;
  border: 1px solid var(--border-dim, rgba(255, 255, 255, 0.22));
  background: rgba(255, 255, 255, 0.07);
  color: var(--gold-bright, #ffd94d);
  font-family: "Orbitron", sans-serif;
  font-size: 0.72rem;
  font-weight: 800;
  line-height: 1;
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
}
.daily-info:hover {
  background: var(--gold, #ffd94d);
  border-color: var(--gold, #ffd94d);
  color: #2a1c02;
}

/* --- A GIFT WAITING SHOULD BE UNMISSABLE -----------------------------
   The board no longer opens itself every day — it introduces itself once
   and after that the pill is the way in. That only works if the pill
   announces itself, and the old pulse was a faint box-shadow that was
   switched off entirely on mobile, which is where most of this is
   played.

   Three things at once: a warm glow that breathes, a gentle lift, and
   the gift icon rocking like something being shaken. All transform,
   opacity and box-shadow, so it composites and costs nothing to keep
   running — which is why the mobile switch-off goes too. */
.daily-timer.is-ready {
  animation: dailyReadyPulse 1700ms ease-in-out infinite;
}
@keyframes dailyReadyPulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(255, 217, 77, 0.0), 0 0 10px rgba(255, 217, 77, 0.25);
    transform: translateY(0) scale(1);
  }
  50% {
    box-shadow: 0 0 22px 5px rgba(255, 217, 77, 0.45), 0 0 34px rgba(255, 184, 0, 0.35);
    transform: translateY(-1px) scale(1.035);
  }
}
.daily-timer.is-ready .daily-timer-icon {
  animation: dailyReadyShake 1700ms ease-in-out infinite;
  transform-origin: 50% 80%;
}
@keyframes dailyReadyShake {
  0%, 62%, 100% { transform: rotate(0deg) scale(1); }
  70%           { transform: rotate(-9deg) scale(1.08); }
  78%           { transform: rotate(8deg)  scale(1.08); }
  86%           { transform: rotate(-5deg) scale(1.04); }
  93%           { transform: rotate(3deg)  scale(1.02); }
}
/* The label earns a colour too — "READY" in gold reads at a glance. */
.daily-timer.is-ready .daily-timer-value { color: var(--gold-bright, #ffd94d); }

/* The info button beside it stops competing while the pill is shouting. */
.daily-timer.is-ready ~ .daily-info { opacity: 0.7; }

@media (prefers-reduced-motion: reduce) {
  .daily-timer.is-ready,
  .daily-timer.is-ready .daily-timer-icon { animation: none; }
  /* Still obviously different from the resting state, just still. */
  .daily-timer.is-ready { box-shadow: 0 0 18px 3px rgba(255, 217, 77, 0.4); }
}
