/* css/app.css
 * Global layout + component styles. Tokens only — see css/theme.css.
 * Mobile-first: base styles target ~375px phones; tablet at 600px;
 * desktop at 1024px (per Felix's build-a-component breakpoint checklist).
 */

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: var(--leading-normal);
  color: var(--color-text);
  background: var(--color-bg);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--font-heading); margin: 0 0 var(--space-xs); line-height: var(--leading-tight); }
h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }
h3 { font-size: var(--text-h3); }
p { margin: 0 0 var(--space-sm); }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--color-accent); }
ul { margin: 0; padding: 0; list-style: none; }

/* Visible only to screen readers */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link (accessibility: bypass nav) */
.skip-link {
  position: absolute;
  left: var(--space-xs);
  top: -100px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  z-index: 100;
  transition: top var(--duration-fast) var(--easing-standard);
}
.skip-link:focus { top: var(--space-xs); }

/* Consistent visible focus ring everywhere (never outline:none without this) */
:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* 2026-07-15 QA fix: #app-main is given tabindex="-1" and is programmatically
   focused after every route change (js/app.js renderRoute()) so screen
   readers announce the new page via the aria-live region — that behavior
   must stay. But Chromium's :focus-visible fires on that programmatic focus
   too, which painted a permanent glow around the entire content area on
   every single page. Scoped override: the route container itself never
   shows the ring; every real interactive element inside it still does. */
#app-main:focus-visible {
  box-shadow: none;
}

/* ---------- App shell ---------- */
.app-shell {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm);
  background: var(--color-bg-elevated);
  border-bottom: 1px solid var(--color-border);
}
.app-header h1 {
  font-size: var(--text-h2);
  margin: 0;
}
.app-header .season-progress {
  font-size: var(--text-caption);
  color: var(--color-text-muted);
}

.xp-balance {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--color-bg-inset);
  border: 1px solid var(--color-border-strong);
  color: var(--color-accent);
  font-weight: 600;
  padding: 6px var(--space-sm);
  border-radius: var(--radius-full);
  white-space: nowrap;
}
.xp-balance .xp-icon { font-size: 1.1em; }

.app-main {
  flex: 1 1 auto;
  width: 100%;
  max-width: var(--max-content-width);
  margin: 0 auto;
  padding: var(--space-sm);
  padding-bottom: calc(var(--bottom-nav-height) + var(--space-md));
}

/* ---------- Bottom nav (mobile/tablet); becomes a sidebar on desktop ---------- */
.app-nav {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: var(--bottom-nav-height);
  display: flex;
  overflow-x: auto;
  background: var(--color-bg-elevated);
  border-top: 1px solid var(--color-border);
  z-index: 10;
}
.app-nav a {
  flex: 1 0 auto;
  min-width: 76px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 4px 8px;
  text-decoration: none;
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  border-top: 2px solid transparent;
}
.app-nav a .nav-icon { font-size: 1.25rem; }
.app-nav a[aria-current="page"] {
  color: var(--color-accent);
  border-top-color: var(--color-accent);
  background: var(--color-bg-elevated-2);
}
.app-nav a:hover { color: var(--color-text); }

/* ---------- Cards ---------- */
.card {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-md);
  margin-bottom: var(--space-sm);
  box-shadow: var(--shadow-sm);
}
.card h2, .card h3 { color: var(--color-text); }
.card + .card { margin-top: 0; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px; /* accessible tap target */
  padding: 10px var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-weight: 600;
  text-decoration: none; /* a <a class="btn"> must never look like a plain link (CP7 fix — pre-existing gap, also affected today.js's "Set up a season" link) */
  cursor: pointer;
  transition: background var(--duration-fast) var(--easing-standard),
              transform var(--duration-fast) var(--easing-standard);
}
.btn:active { transform: scale(0.98); }
.btn-primary { background: var(--color-primary); color: var(--color-on-primary); }
.btn-primary:hover { background: var(--color-primary-hover); }
.btn-secondary { background: var(--color-secondary); color: var(--color-on-secondary); }
.btn-secondary:hover { background: var(--color-secondary-hover); }
.btn-accent { background: var(--color-accent); color: var(--color-on-accent); }
.btn-accent:hover { background: var(--color-accent-hover); }
.btn-ghost {
  background: transparent;
  border-color: var(--color-border-strong);
  color: var(--color-text);
}
.btn-ghost:hover { background: var(--color-bg-elevated-2); }
.btn-danger { background: var(--color-error); color: var(--color-text-inverse); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}
.btn-block { width: 100%; }
.btn-sm { min-height: 36px; padding: 6px var(--space-sm); font-size: var(--text-caption); }

/* ---------- Habit tick rows (Today page) ---------- */
.habit-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--color-border);
}
.habit-row:last-child { border-bottom: none; }
.habit-info { flex: 1 1 auto; min-width: 0; }
.habit-name { font-weight: 600; }
.habit-meta { font-size: var(--text-caption); color: var(--color-text-muted); }

.tick-button {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-full);
  border: 2px solid var(--color-accent);
  background: var(--color-bg-elevated-2);
  color: var(--color-text-muted);
  font-size: 1.4rem;
  cursor: pointer;
  transition: all var(--duration-fast) var(--easing-standard);
}
.tick-button[aria-pressed="true"] {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}
.tick-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.tick-button:hover:not(:disabled) { border-color: var(--color-accent); }

/* ---------- Progress bars ---------- */
.progress-track {
  width: 100%;
  height: 10px;
  background: var(--color-bg-inset);
  border-radius: var(--radius-full);
  overflow: hidden;
  border: 1px solid var(--color-border);
}
.progress-fill {
  height: 100%;
  background: var(--color-accent);
  transition: width var(--duration-base) var(--easing-standard);
}
.progress-fill.is-complete { background: var(--color-success); }
.progress-label {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-caption);
  color: var(--color-text-muted);
  margin-bottom: 4px;
}

/* ---------- Badges ---------- */
.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: var(--radius-full);
  font-size: var(--text-caption);
  font-weight: 600;
}
.badge-success { background: var(--color-success); color: var(--color-text-inverse); }
.badge-warning { background: var(--color-warning); color: var(--color-text-inverse); }
.badge-error { background: var(--color-error); color: var(--color-text-inverse); }
.badge-info { background: var(--color-info); color: var(--color-text-inverse); }
.badge-neutral { background: var(--color-bg-elevated-2); color: var(--color-text-muted); border: 1px solid var(--color-border-strong); }

/* ---------- Forms ---------- */
.field { margin-bottom: var(--space-sm); }
.field label {
  display: block;
  font-size: var(--text-caption);
  color: var(--color-text-muted);
  margin-bottom: 4px;
}
.field input[type="text"],
.field input[type="number"],
.field input[type="date"],
.field select,
.field textarea {
  width: 100%;
  min-height: 44px;
  padding: 8px var(--space-sm);
  background: var(--color-bg-elevated-2);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text);
}
.field textarea { min-height: 96px; resize: vertical; }
/* 2026-07-15 QA fix (WCAG 1.4.11, non-text contrast >= 3:1): the shared
   --focus-ring token composited over --color-bg-elevated-2 (the actual
   background of these controls) measured only 2.82:1. Form controls get a
   solid, fully-opaque accent border on focus in addition to the usual
   glow — a solid border can't lose contrast to alpha-compositing the way
   the glow alone does. Solid accent vs bg-elevated-2 measures 5.14:1.
   (Placed after the base .field input/select/textarea rule on purpose:
   same specificity, needs to win on source order.) */
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  border-color: var(--color-accent);
}
.checkbox-field { display: flex; align-items: center; gap: 8px; }
.checkbox-field input { width: 20px; height: 20px; accent-color: var(--color-primary); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--color-primary); }
.field-hint { font-size: var(--text-caption); color: var(--color-text-faint); margin-top: 4px; }
.field-warning { font-size: var(--text-caption); color: var(--color-warning-text); margin-top: 4px; }

/* ---------- Week grid ---------- */
/* 2026-07-15 QA fix (MEDIUM): mobile horizontal-scroll edge-fade affordance,
   classic 4-layer "scroll shadow" trick — two background gradients scroll
   WITH the content (background-attachment: local) to mask the table edges
   at the wrap's start/end, two more stay fixed to the viewport (scroll) and
   only render the shadow when there's actually more content in that
   direction, since the scrolling gradient layer covers them once you've
   scrolled all the way. No JS needed; shadows disappear naturally at the
   scroll boundaries. */
.week-grid-wrap {
  overflow-x: auto;
  background-image:
    linear-gradient(to right, var(--color-bg-elevated) 30%, rgba(0, 0, 0, 0)),
    linear-gradient(to left, var(--color-bg-elevated) 30%, rgba(0, 0, 0, 0)),
    linear-gradient(to right, var(--scroll-shadow), rgba(0, 0, 0, 0)),
    linear-gradient(to left, var(--scroll-shadow), rgba(0, 0, 0, 0));
  background-repeat: no-repeat;
  background-color: var(--color-bg-elevated);
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-attachment: local, local, scroll, scroll;
}
.week-grid {
  border-collapse: collapse;
  width: 100%;
  min-width: 620px; /* was 560px — grown to match the .cell-toggle bump below */
}
.week-grid th, .week-grid td {
  border: 1px solid var(--color-border);
  text-align: center;
  padding: 6px;
}
.week-grid th {
  background: var(--color-bg-elevated-2);
  font-weight: 600;
  font-size: var(--text-caption);
  color: var(--color-text-muted);
}
.week-grid .habit-label {
  text-align: left;
  font-weight: 600;
  min-width: 140px;
}
.week-grid .cell-toggle {
  /* 2026-07-15 QA fix (HIGH): was 40x40px, below the team's 44px tap-target
     floor and inconsistent with .tick-button (52px) for the identical
     tick/untick action on the Today page. Matched to 52px. */
  width: 52px;
  height: 52px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--color-accent);
  background: var(--color-bg-elevated-2);
  color: transparent;
  cursor: pointer;
}
.week-grid .cell-toggle[aria-pressed="true"] {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}
.week-grid .cell-toggle:disabled { opacity: 0.4; cursor: not-allowed; }

/* ---------- Review wizard ---------- */
.review-steps {
  display: flex;
  gap: 6px;
  margin-bottom: var(--space-md);
  flex-wrap: wrap;
}
.review-step-dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--color-border-strong);
}
.review-step-dot.is-active { background: var(--color-accent); }
.review-step-dot.is-done { background: var(--color-success); }
.award-line {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  border-bottom: 1px dashed var(--color-border);
}
.award-line:last-child { border-bottom: none; }
.award-xp { color: var(--color-accent); font-weight: 700; }

/* ---------- Store / rewards (CP5) ---------- */
.store-tier-heading {
  margin: var(--space-md) 0 var(--space-xs);
}
.reward-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}
.reward-card { margin-bottom: 0; }
.reward-card.is-unaffordable { opacity: 0.85; }
.reward-card.is-archived { opacity: 0.6; }
/* CP9: same dimmed-archived treatment, generalized to any card (habits
   reuse this for their own archive/restore section in Season Setup). */
.card.is-archived { opacity: 0.6; }
.reward-icon { margin-right: 6px; }
.reward-actions { margin-top: var(--space-sm); }
.reward-buy-btn:disabled { opacity: 0.45; }

@media (min-width: 600px) {
  .reward-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .reward-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- Stats charts + export (CP6) ---------- */
.chart-canvas {
  display: block;
  width: 100%;
  border-radius: var(--radius-sm);
}
.stats-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--space-xs);
  font-size: var(--text-caption);
}
.stats-table th, .stats-table td {
  border-bottom: 1px solid var(--color-border);
  padding: 4px 6px;
  text-align: left;
}
.stats-table th { color: var(--color-text-muted); font-weight: 600; }
details {
  margin-top: var(--space-xs);
}
details > summary {
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  padding: 4px 0;
}
details > summary:hover { color: var(--color-text); }
#md-preview {
  font-family: var(--font-mono);
  min-height: 220px;
  white-space: pre;
  overflow-x: auto;
}

/* ---------- Stub / coming soon ---------- */
.stub-page {
  text-align: center;
  padding: var(--space-2xl) var(--space-md);
  color: var(--color-text-muted);
}
.stub-page .stub-icon { font-size: 2.5rem; margin-bottom: var(--space-sm); }

/* ---------- Utility ---------- */
.stack > * + * { margin-top: var(--space-sm); }
.flex-row { display: flex; align-items: center; gap: var(--space-sm); }
.flex-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); }
.text-muted { color: var(--color-text-muted); }
.text-caption { font-size: var(--text-caption); color: var(--color-text-muted); }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }

/* ---------- Toast (lightweight feedback, replaces window.alert) ---------- */
.toast-region {
  position: fixed;
  /* 2026-07-15 QA fix (MEDIUM): raised from +space-sm and narrowed from
     420px — a toast could fully cover a reward card's Buy button on the
     Store page for its ~3s lifetime. Sits higher and narrower now, so it
     overlaps less of the card beneath it while staying easy to read. */
  bottom: calc(var(--bottom-nav-height) + var(--space-lg));
  left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: calc(100% - var(--space-md) * 2);
  max-width: 320px;
  pointer-events: none;
}
.toast {
  background: var(--color-bg-elevated-2);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text);
  padding: 10px var(--space-sm);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font-size: var(--text-caption);
  text-align: center;
}
.toast.is-error { border-color: var(--color-error); color: var(--color-error-text); }
.toast.is-success { border-color: var(--color-success); color: var(--color-success-text); }

/* ==================================================================
   Tablet breakpoint (>= 600px)
   ================================================================== */
@media (min-width: 600px) {
  .app-main { padding: var(--space-md); }
  .app-header { padding: var(--space-md) var(--space-lg); }
  :root { --max-content-width: 720px; }
}

/* ==================================================================
   Desktop breakpoint (>= 1024px): nav becomes a left sidebar
   ================================================================== */
@media (min-width: 1024px) {
  .app-shell {
    flex-direction: row;
  }
  .app-nav {
    position: sticky;
    top: 0;
    left: 0;
    height: 100vh;
    width: 220px;
    flex-direction: column;
    overflow-x: visible;
    overflow-y: auto;
    border-top: none;
    border-right: 1px solid var(--color-border);
  }
  .app-nav a {
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--space-xs);
    min-width: 0;
    padding: 12px var(--space-md);
    border-top: none;
    border-left: 3px solid transparent;
  }
  .app-nav a[aria-current="page"] {
    border-left-color: var(--color-accent);
    border-top-color: transparent;
  }
  .app-content-col {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  .app-main {
    padding-bottom: var(--space-md);
    max-width: 880px;
  }
}
