/* base.css — element defaults and the app shell. Components live in
 * components.css; per-screen rules in pages.css. */

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overscroll-behavior-y: none;   /* no rubber-band on a logging screen */
}

/* Reserved whether or not the page currently scrolls. An open sheet locks the
   page behind it (see html:has(.sheet-backdrop) in pages.css), which takes the
   scrollbar away and widens the viewport by its width — shifting every
   right-aligned thing sideways, in plain view through a backdrop that is only
   60% opaque. Reserving the gutter permanently means nothing moves either way. */
html { scrollbar-gutter: stable; }

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* Every digit that can change should hold its column. */
.num, input[type="number"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; text-wrap: balance; }
p { margin: 0; }

a { color: var(--color-primary-hover); text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; }

/* iOS Safari zooms the page in whenever a focused field's text is smaller than
   16px, and it does not zoom back out when the field is blurred. The app is left
   slightly enlarged and pannable, which is why an edge was being clipped and why
   which edge kept changing — it was wherever the page had last been panned.
   --text-base is 15.2px, so `font: inherit` above was enough to trigger it on
   every field in the app. 16px is the threshold, so 16px is the floor. A field
   that wants to be bigger still can, by its own class. */
input, select, textarea { font-size: 16px; }

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  touch-action: manipulation;
}

:focus-visible {
  outline: 2px solid var(--color-warning);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

[x-cloak] { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ---- shell ---------------------------------------------------------------- */

.app {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  padding-top: var(--inset-top);
}

.app-bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-2);
  flex: none;
}

.app-bar h1 {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  letter-spacing: -.02em;
}

/* A bar carrying a back button aligns on the box, not the baseline — an icon
   button has no text baseline to sit on and drops below the title otherwise. */
.app-bar:has(> .tap) {
  align-items: center;
}

.app-bar .app-bar-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--color-text-dim);
  /* It opens Settings, so it needs a finger-sized target and the button reset. */
  background: none;
  border: 0;
  cursor: pointer;
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  min-height: 44px;
}

.app-main {
  flex: 1;
  /* The page is swiped sideways to change tab (swipeTabs in app.js), so the
     browser has to be told to keep vertical panning and hand us the horizontal.
     Otherwise it can claim the gesture and cancel the pointer, and the swipe
     silently never fires. The same bargain .row-swipe and .sheet strike, for
     the same reason. */
  touch-action: pan-y;
  /* Top padding matters: the app bar above is sticky, so without it the first
     control on every screen sits flush against it — the date arrows on Today,
     the collapse chevron on Train. */
  padding: var(--space-3) var(--space-4) var(--space-8);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* A group of related things — usually a section heading and its content.
   The screens themselves are gapped flex columns; these inner wrappers were
   plain divs, so a heading's buttons touched whatever followed. */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* What arrives after a swipe comes in from the side the swipe was going, so the
   movement reads as continuous with the finger that started it. Kept short and
   small — this is punctuation on a gesture that has already happened, not a
   transition to sit through. The reduced-motion block above collapses both. */
@keyframes swipe-in-forward {
  from { transform: translateX(32px);  opacity: 0; }
  to   { transform: none;              opacity: 1; }
}

@keyframes swipe-in-back {
  from { transform: translateX(-32px); opacity: 0; }
  to   { transform: none;              opacity: 1; }
}

/* The shell carries the direction, so the rule is already in place whenever the
   arriving tab mounts — :not(.log-panel) because the log overlay shares the
   .app-main class and is not what was swiped to. */
.app[data-enter="forward"] .app-main:not(.log-panel) { animation: swipe-in-forward 200ms ease-out; }
.app[data-enter="back"]    .app-main:not(.log-panel) { animation: swipe-in-back    200ms ease-out; }

/* Changing day redraws the card in place. */
.readout.is-entering-forward { animation: swipe-in-forward 200ms ease-out; }
.readout.is-entering-back    { animation: swipe-in-back    200ms ease-out; }

/* Centred single-purpose screens: sign-in, access denied, loading. */
.screen-centered {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  text-align: center;
  padding: var(--space-8) var(--space-4);
  min-height: 70vh;
}

.screen-centered .mark {
  font-family: var(--font-mono);
  font-size: var(--text-3xl);
  letter-spacing: -.04em;
}

.screen-centered p {
  color: var(--color-text-muted);
  max-width: 34ch;
}

/* ---- scrollbars ------------------------------------------------------------ */
/* The default chrome is a bright light-mode artefact sitting inside a dark app.
   Thin, on-palette, and out of the way until there's something to scroll. */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }

::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: var(--radius-pill);
  /* Transparent border plus padding-box gives the thumb breathing room without
     needing a wider track. */
  border: 3px solid transparent;
  background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-text-dim);
  background-clip: padding-box;
}

/* ---- boot screen ----------------------------------------------------------- */
/* Shown by the parser, removed by app.js once Alpine has mounted. Its only job
   is that the first frame isn't blank. */

#boot {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--color-bg);
  z-index: 50;
}

.boot-mark {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--color-text);
  opacity: .9;
}

/* Fade rather than cut, so a fast boot doesn't flash. */
#boot.is-done {
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}

/* The Add food bar leads with its controls rather than spacing them apart:
   back, menu, then the title. */
.app-bar-left {
  justify-content: flex-start;
  gap: var(--space-2);
}

.app-bar-left h1 { margin-left: var(--space-1); }
