/* ---------------------------------------------------------------------------
   Phase 8 — polish without changing the product model.

   This file intentionally loads after style.css/setup.css. It is a final layer
   for accessibility, motion, empty/error states, zoom resilience and platform
   behaviour. Keeping it separate makes the Phase 8 contract easy to audit.
   --------------------------------------------------------------------------- */

:root {
  /* Rem-based scale respects browser/user text sizing. The visual size at the
     default 16px root is unchanged from the original 11/13/15/20/28/34 scale. */
  --text-xs: 0.6875rem;
  --text-sm: 0.8125rem;
  --text-md: 0.9375rem;
  --text-lg: 1.25rem;
  --text-xl: 1.75rem;
  --text-xxl: 2.125rem;
}

html {
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body { min-width: 280px; }

/* The app owns scrolling. Constraining the shell to the visual viewport fixes a
   class of iOS bugs where long screens visually moved but the actual hit-test
   surface did not. It also guarantees content can scroll clear of the floating
   tab bar instead of ending underneath it. */
html,
body,
#app,
.app {
  height: 100%;
  max-height: 100%;
}

body { overflow: hidden; }

.app {
  min-height: 0;
  height: 100dvh;
  max-height: 100dvh;
  overflow: hidden;
  grid-template-rows: minmax(0, 1fr) auto;
}

/* iOS standalone reports dynamic viewport units short of the physical screen
   when viewport-fit=cover is active. Use the full viewport for the root shell;
   the tab labels still stay clear of the home indicator via safe-area padding. */
@media (display-mode: standalone) {
  html,
  body,
  #app,
  .app {
    height: 100vh;
    max-height: 100vh;
  }
}

.app__body {
  min-height: 0;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  scroll-padding-top: calc(var(--s4) + env(safe-area-inset-top));
  scroll-padding-bottom: calc(96px + env(safe-area-inset-bottom));
}

.settings-access { z-index: 50; }

/* Keyboard and switch-control focus must be impossible to lose against the
   illustrated background. Focus is deliberately acid because it is an active
   state, one of the accent's allowed roles. */
:where(button, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--acid);
  outline-offset: 3px;
}

:where(button, input, select, textarea) {
  touch-action: manipulation;
}

:where(button, input, select, textarea):disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* iOS zooms text fields smaller than 16px. A mid-set input should never throw
   the whole interface into a zoomed viewport. */
:where(input, select, textarea) { font-size: max(1rem, var(--text-md)); }

/* Phase 8 closes the remaining undersized controls from earlier dense layouts.
   Visual density can stay compact, but every independent tap target is at least
   44px on its narrowest dimension. */
.quick__add,
.iconbutton,
.worked__toggle,
.today-item__body,
.today-item__quick,
.today-item__expand,
.setup__cadence,
.setup__weeklyselect,
.progress-range__button,
.battle__back,
.calendar__nav,
.dayrail__nav,
.planner__add,
.today-planner__add {
  min-width: 44px;
  min-height: 44px;
}

.today-item__expand,
.calendar__nav,
.dayrail__nav {
  min-width: 44px;
}

.today-item__body { padding-block: 6px; }

/* The small cadence pills are used dozens of times in Settings, so an extra
   four pixels of height removes a lot of missed taps without changing layout. */
.progress-range__button { min-width: 44px; }

/* Screen changes get one short spatial cue. Existing completion motion remains
   untouched. Reduced-motion below removes both. */
@keyframes phase8-screen-enter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.screen {
  /* backwards, not both: a transform left on .screen after the animation keeps it
     composited, and iOS WebKit then renders sticky header text blurry. */
  animation: phase8-screen-enter var(--motion-screen) ease-out backwards;
}

.button:active,
.actionpill:active,
.programday:active,
.libraryrow:active,
.tabbar__tab:active,
.row__act:active,
.quick__add:active {
  transform: scale(0.97);
}

/* Visually hidden, still available to VoiceOver and other AT. */
.sr-only {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* Empty is a state, not an apology. */
.empty-state {
  display: grid;
  gap: var(--s2);
  padding: var(--s5) var(--s4);
  border-radius: var(--r-lg);
  background: var(--surface-1);
  text-align: center;
}
.empty-state__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 800;
  
  letter-spacing: 0;
}
.empty-state__detail { margin: 0; color: var(--text-2); font-size: var(--text-sm); }

/* Errors stay inside the app's visual language and always provide recovery. */
.error-state {
  width: min(100%, 520px);
  margin: auto;
  align-self: center;
}
.error-state__eyebrow {
  margin: 0;
  color: var(--text-3);
  font-size: var(--text-xs);
  font-weight: 800;
  letter-spacing: 0;
}
.error-state__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 800;
  line-height: 1.05;
}
.error-state__detail { margin: 0; color: var(--text-2); }
.error-state__actions { display: flex; gap: var(--s2); flex-wrap: wrap; }

[aria-busy='true'] { cursor: progress; }

/* Dynamic type / zoom: content may grow instead of clipping. Today keeps its
   compact default, but at enlarged text the accessibility requirement wins over
   the original one-screen height budget and the screen is allowed to scroll. */
@media (min-resolution: 1dppx) {
  .row__name,
  .libraryrow__name,
  .programday__name,
  .historyrow__name,
  .recordrow__name {
    overflow-wrap: anywhere;
  }
}

@media (max-width: 380px) {
  .screen__title { font-size: var(--text-xl); }
  .block__title { font-size: var(--text-lg); }
  .error-state__actions { display: grid; }
  .error-state__actions .button { width: 100%; }
}

/* Stronger OS contrast request gets stronger structure without inventing new
   colours or changing the normal visual design. */
@media (prefers-contrast: more) {
  :root {
    --edge: var(--surface-2);
    --edge-strong: var(--surface-2);
    --text-2: var(--chart);
    --text-3: var(--chart);
  }
  .card, .row, .programday, .libraryrow { box-shadow: none; }
}

/* Windows forced-colour users should get native high-contrast controls rather
   than a canvas that fights the OS. */
@media (forced-colors: active) {
  :where(button, input, select, textarea) { forced-color-adjust: auto; }
  .tile, .strip__fill, .weekday__bar::after, .guide__bar::after { forced-color-adjust: auto; }
  body::before { display: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .settings-access { backdrop-filter: none; -webkit-backdrop-filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .screen { animation: none !important; }
  .button:active,
  .actionpill:active,
  .programday:active,
  .libraryrow:active,
  .tabbar__tab:active,
  .row__act:active,
  .quick__add:active { transform: none; }
}
