/* =========================================================================
   Mood tracker
   =========================================================================
   Token values were sampled from the supplied design preview rather than
   eyeballed, so the palette here is measured. Layout follows a three-step
   scale: a single column on phones, a two-column split from 640px, and the
   full dashboard grid from 1024px.
   ========================================================================= */

/* -------------------------------------------------------------- fonts -- */

@font-face {
  font-family: 'Reddit Sans';
  src: url('/static/assets/fonts/Reddit_Sans/RedditSans-VariableFont_wght.ttf') format('truetype-variations');
  font-weight: 100 900;
  font-style: normal;
  /* swap, not block: the layout is already correct before the font arrives,
     so a flash of fallback text beats a flash of nothing. */
  font-display: swap;
}

/* ------------------------------------------------------------- tokens -- */

:root {
  --blue-100: #e0e0ff;
  --blue-200: #c7d3f7;
  --blue-300: #89cbff;
  --blue-400: #6a8bef;
  --blue-600: #4865db;
  --blue-700: #3a55c6;

  --neutral-0: #ffffff;
  --neutral-100: #f5f5ff;
  --neutral-200: #e0e0e1;
  --neutral-300: #a8a7b5;
  --neutral-600: #57577b;
  --neutral-900: #21214d;

  --mood-very-happy: #ffc97c;
  --mood-happy: #89e780;
  --mood-neutral: #89cbff;
  --mood-sad: #b8b1ff;
  --mood-very-sad: #ff9b99;

  --red-500: #d3455b;

  --radius-sm: 8px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  --shadow-card: 0 4px 24px rgba(32, 32, 72, 0.06);
  --shadow-pop: 0 12px 32px rgba(32, 32, 72, 0.16);

  --container: 1170px;
  --gutter: clamp(1rem, 4vw, 2rem);
  --plot-height: 260px;
  --bar-width: 40px;

  --fs-display: clamp(2rem, 1.2rem + 3.6vw, 3.75rem);
  --fs-h2: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  --fs-lead: clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem);
  --fs-body: clamp(1rem, 0.95rem + 0.3vw, 1.125rem);
  --fs-sm: 1rem;
  --fs-xs: 0.8125rem;

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* -------------------------------------------------------------- reset -- */

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

* { margin: 0; }

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

body {
  min-height: 100dvh;
  font-family: 'Reddit Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: var(--fs-body);
  line-height: 1.4;
  color: var(--neutral-900);
  background: linear-gradient(180deg, var(--neutral-100) 0%, var(--blue-100) 100%) fixed;
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }

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

button { cursor: pointer; }

ul, ol { list-style: none; padding: 0; }

a { color: var(--blue-600); }

/* Alpine sets x-cloak on elements whose initial state is "hidden". Without
   this rule they flash visible for one frame before Alpine boots. */
[x-cloak] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: 0.5rem; top: -4rem;
  z-index: 100;
  padding: 0.75rem 1rem;
  background: var(--neutral-0);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-pop);
  text-decoration: none;
  font-weight: 600;
  transition: top 160ms var(--ease);
}
.skip-link:focus-visible { top: 0.5rem; }

/* One focus treatment for the whole app. :focus-visible rather than :focus so
   pointer users never see a ring they did not ask for. */
:focus-visible {
  outline: 2px solid var(--blue-600);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------ weather -- */

/* The sky sits behind everything, fixed, so scrolling the dashboard moves the
   content over a still horizon rather than dragging the weather with it. */
.weather {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background: linear-gradient(180deg, var(--neutral-100) 0%, var(--blue-100) 100%);
}

/* Two levels of fade stack here without interfering: this element carries the
   anchor's weight on the mood scale, written by the server, and the two clips
   inside it carry the loop's own crossfade, driven by the client. */
.weather__sky {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
}

/* The poster is the fallback the sky degrades to: it is already painted on as
   a background image, so a browser that blocks autoplay, a connection that
   never delivers the clip, and a user who asked for reduced motion all get a
   still sky rather than an empty one. */
.weather__sky video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.weather__rain {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Legibility, scoped and proportional.
   The greeting is small navy and grey text sitting directly on the sky. On a
   bright sky it needs almost nothing; on an overcast one, measuring showed the
   date dropping to 3.15:1 against a 4.5 requirement. So the scrim's strength
   is set from the sky's own darkness rather than fixed: a stormy day gets the
   veil it needs, a clear day keeps its sky. --scrim is written by the server
   alongside the sky weights. */
.weather__scrim {
  position: absolute;
  inset: 0;
  --scrim: 0.75;
  background:
    linear-gradient(180deg,
      rgba(245, 245, 255, var(--scrim)) 0%,
      rgba(245, 245, 255, var(--scrim)) 26%,
      rgba(245, 245, 255, calc(var(--scrim) * 0.45)) 44%,
      rgba(245, 245, 255, calc(var(--scrim) * 0.14)) 70%,
      rgba(245, 245, 255, calc(var(--scrim) * 0.06)) 100%);
}

/* The page's own gradient would sit on top of the sky and hide it. */
.page--weather { background: transparent; }
.page--weather .page__inner { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  /* The clips are removed by the script; this makes sure nothing animates even
     if it has not run yet. */
  .weather__sky video { display: none; }
}

/* ------------------------------------------------------------- layout -- */

.page {
  min-height: 100dvh;
  padding: clamp(1rem, 3vw, 2.5rem) var(--gutter) 4rem;
}

.page__inner {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
}

.page--auth {
  display: grid;
  place-items: center;
  padding-block: 2rem;
}

.card {
  background: var(--neutral-0);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

/* ------------------------------------------------------------- header -- */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: clamp(2.5rem, 6vw, 4rem);
}

.brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

/* logo.svg is a 178x40 lockup. Height is the fixed dimension and width follows
   it, so the mark never squashes when the row gets tight. */
.brand__logo {
  height: 40px;
  width: auto;
}

.brand--auth {
  margin-bottom: 2.5rem;
  justify-content: center;
  width: 100%;
}

.avatar {
  border-radius: var(--radius-pill);
  object-fit: cover;
  background: var(--blue-100);
}
.avatar--sm { width: 40px; height: 40px; }
.avatar--lg { width: 64px; height: 64px; }

/* ---------------------------------------------------------- user menu -- */

.user-menu { position: relative; }

.user-menu__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem;
  background: none;
  border: 0;
  border-radius: var(--radius-pill);
  transition: background-color 160ms var(--ease);
}
.user-menu__trigger:hover { background: rgba(72, 101, 219, 0.08); }

.user-menu__chevron { transition: transform 160ms var(--ease); }
.user-menu__trigger[aria-expanded='true'] .user-menu__chevron { transform: rotate(180deg); }

.user-menu__list {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 20;
  min-width: 220px;
  padding: 0.5rem;
  background: var(--neutral-0);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
}

.user-menu__identity {
  display: grid;
  gap: 0.125rem;
  padding: 0.5rem 0.75rem 0.75rem;
  border-bottom: 1px solid var(--neutral-200);
  margin-bottom: 0.25rem;
}
.user-menu__name { font-weight: 600; }
.user-menu__email {
  font-size: var(--fs-xs);
  color: var(--neutral-600);
  overflow-wrap: anywhere;
}

.user-menu__item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  padding: 0.625rem 0.75rem;
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
  text-align: left;
  transition: background-color 140ms var(--ease);
}
.user-menu__item:hover { background: var(--neutral-100); }

/* ----------------------------------------------------------- greeting -- */

.greeting {
  text-align: center;
  margin-bottom: clamp(2rem, 5vw, 3rem);
}

.greeting__hello {
  font-size: var(--fs-lead);
  font-weight: 600;
  color: var(--blue-600);
  margin-bottom: 0.375rem;
}

.greeting__question {
  font-size: var(--fs-display);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin-bottom: 0.75rem;
}

.greeting__date {
  color: var(--neutral-600);
  font-size: var(--fs-sm);
}

.log-prompt {
  display: grid;
  place-items: center;
  margin-bottom: clamp(2rem, 5vw, 3rem);
}

/* ------------------------------------------------------------ buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.875rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background-color 160ms var(--ease), color 160ms var(--ease),
              border-color 160ms var(--ease), transform 120ms var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; transform: none; }

.btn--primary { background: var(--blue-600); color: var(--neutral-0); }
.btn--primary:hover:not(:disabled) { background: var(--blue-700); }

.btn--outline {
  background: var(--neutral-0);
  border-color: var(--neutral-200);
  color: var(--neutral-900);
}
.btn--outline:hover:not(:disabled) { border-color: var(--blue-600); color: var(--blue-600); }

.btn--ghost { background: none; color: var(--neutral-600); }
.btn--ghost:hover:not(:disabled) { color: var(--neutral-900); }

.btn--block { flex: 1; width: 100%; }
.btn--lg { padding: 1rem 2.5rem; font-size: var(--fs-body); }
.btn--sm { padding: 0.5rem 1rem; font-size: var(--fs-xs); }

/* ---------------------------------------------------------- today row -- */

.today {
  display: grid;
  gap: 1.25rem;
  margin-bottom: 1.25rem;
}

@media (min-width: 64rem) {
  .today { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}

.today__side {
  display: grid;
  gap: 1.25rem;
  align-content: start;
}

.mood-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: clamp(1.5rem, 3vw, 2rem);
  /* The face is deliberately allowed to run past the card's right edge in the
     design, so the card clips rather than grows. */
  overflow: hidden;
  min-height: 300px;
}

.mood-card__text {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 2rem;
  min-height: 200px;
  max-width: 52%;
}

.mood-card__headline { display: grid; gap: 0.25rem; }

.mood-card__label {
  font-size: var(--fs-lead);
  color: var(--neutral-900);
  font-weight: 500;
}

.mood-card__value {
  font-size: var(--fs-h2);
  font-weight: 700;
  line-height: 1.1;
}

.quote { display: flex; gap: 0.75rem; }
.quote__mark { flex: none; margin-top: 0.35rem; }
.quote__text {
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--neutral-900);
  line-height: 1.5;
}

.mood-card__face {
  position: absolute;
  right: 1.5rem;
  /* Bleeds a little past the bottom edge, the way the design does, without
     growing so large that it crowds the text column beside it. */
  bottom: -1rem;
  width: clamp(170px, 26vw, 272px);
  height: auto;
  pointer-events: none;
}

@media (max-width: 40rem) {
  .mood-card { flex-direction: column; align-items: stretch; }
  .mood-card__text { max-width: 100%; min-height: 0; }
  .mood-card__face {
    position: static;
    width: clamp(140px, 45vw, 200px);
    margin: 1.5rem auto 0;
  }
}

.stat-card { padding: clamp(1.25rem, 2.5vw, 1.5rem); }

.stat-card__label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-sm);
  color: var(--neutral-600);
  margin-bottom: 0.75rem;
}

.stat-card__value {
  font-size: var(--fs-h2);
  font-weight: 700;
}

.stat-card__body {
  font-size: var(--fs-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-top: 1rem;
}

.tag {
  font-size: var(--fs-xs);
  font-style: italic;
  color: var(--neutral-600);
}

.tags--sm { margin-top: 0.5rem; }

/* ----------------------------------------------------------- averages -- */

.trends {
  display: grid;
  gap: 1.25rem;
}

@media (min-width: 64rem) {
  .trends { grid-template-columns: minmax(260px, 0.62fr) minmax(0, 1.38fr); }
}

/* One card holding both averages, matching the design. The two halves share
   the height evenly so the panels fill the card the way they do in the design,
   instead of stacking at the top and leaving dead space underneath. */
.trends__averages {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 1.5rem;
  padding: clamp(1.25rem, 2.5vw, 1.5rem);
}

/* Heading sits on its own row; the panel takes whatever height is left. */
.average {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0.75rem;
}

.average__title {
  font-size: 1.125rem;
  font-weight: 700;
}

.average__window {
  /* A step down from the heading it qualifies, so "Average Mood" stays the
     thing you read first. */
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--neutral-300);
}

.average__panel {
  position: relative;
  overflow: hidden;
  padding: 1.25rem;
  border-radius: var(--radius-lg);
  min-height: 120px;
  display: grid;
  align-content: center;
  gap: 0.75rem;
  /* The decorative arc from the design, drawn as a background layer rather
     than an <img> so it never becomes a resource the layout waits on.
     Natural size, pushed far enough right that the circle's centre sits
     outside the panel: measuring the design gives a radius roughly 1.6x the
     panel's height with only about 17% of the panel's width covered, so what
     shows is a thin crescent at the right edge, not a whole circle. */
  background-image: url('/static/assets/images/bg-pattern-averages.svg');
  background-repeat: no-repeat;
  background-position: right -190px center;
  background-size: auto;
}

.average__panel--mood-very-happy { background-color: var(--mood-very-happy); }
.average__panel--mood-happy { background-color: var(--mood-happy); }
.average__panel--mood-neutral { background-color: var(--mood-neutral); }
.average__panel--mood-sad { background-color: var(--mood-sad); }
.average__panel--mood-very-sad { background-color: var(--mood-very-sad); }
.average__panel--sleep { background-color: var(--blue-600); color: var(--neutral-0); }

/* The two empty panels mirror the light/dark rhythm of their filled
   counterparts, so the column reads the same either way. Neither tint is one
   of the five mood colours, which keeps an empty card from being mistaken for
   a logged one. */
.average__panel--empty-mood {
  background-color: var(--blue-200);
  /* Navy, not the muted grey: grey on this tint measures about 4.3:1, which
     misses AA for body text. It also matches the filled mood panel, which
     inherits the same navy. */
  color: var(--neutral-900);
}
.average__panel--empty-sleep {
  background-color: var(--blue-600);
  color: var(--neutral-0);
}
.average__panel--empty-sleep .average__empty-title { color: var(--neutral-0); }

.average__empty-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--neutral-900);
}

.average__value {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  font-size: 1.5rem;
  font-weight: 700;
}

.average__trend {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: var(--fs-xs);
  line-height: 1.35;
}
.average__trend img { flex: none; margin-top: 0.15rem; }

/* The trend arrows ship with a #21214D fill, which all but disappears on the
   dark sleep panel. Inverting to white is the whole fix and keeps one asset
   serving both panels. */
.average__panel--sleep .average__trend img,
.average__panel--empty-sleep .average__trend img {
  filter: brightness(0) invert(1);
}

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

/* The chart is its own container: how much room the bars have depends on the
   dashboard grid, not on the viewport, so the density decisions below are
   keyed to this element's width rather than the window's. */
.chart {
  padding: clamp(1.25rem, 3vw, 2rem);
  container-type: inline-size;
  container-name: chart;
}

.chart__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}

.chart__title {
  font-size: var(--fs-h2);
  font-weight: 700;
}

.chart__plot {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.75rem;
}

/* All five labels occupy the same grid cell, so the column still sizes itself
   to the widest one. Absolute positioning would collapse this container to
   zero width and push the labels off the card. */
.chart__axis {
  display: grid;
  height: var(--plot-height);
  font-size: var(--fs-xs);
  color: var(--neutral-300);
}

.chart__axis-item {
  grid-area: 1 / 1;
  align-self: end;
  justify-self: end;
  /* --f is the bucket's fraction of the plot, the same value that sizes its
     bars. From the baseline, move up by that fraction of the plot, then down
     by half the label's own height to centre it on the line. */
  transform: translateY(calc(50% - var(--f) * var(--plot-height)));
  display: flex;
  align-items: center;
  gap: 0.375rem;
  white-space: nowrap;
}

/* The canvas is the bars' own coordinate space. Scoping the gridlines here
   rather than to the whole plot is what stops them running through the axis
   labels on the left. */
.chart__canvas { position: relative; }

.chart__grid {
  position: absolute;
  left: 0; right: 0; top: 0;
  height: var(--plot-height);
  pointer-events: none;
  /* Four lines at 25/50/75/100% from the bottom, plus the border below for the
     fifth at 0%. The "0-2 hours" line is the floor of the plot, so no bar can
     ever extend past the lowest label - and every line on the chart names a
     real bucket. */
  background-image: repeating-linear-gradient(
    to top,
    transparent 0,
    transparent calc(25% - 1px),
    var(--neutral-200) calc(25% - 1px),
    var(--neutral-200) 25%
  );
  border-bottom: 1px solid var(--neutral-200);
}


/* No horizontal scrolling. All eleven records are always on screen; what
   changes with width is the density of each column, not how many there are.
   The previous fixed 48px column minimum overflowed by 4px even at 1440 and
   by 380px on a phone, which is where the scrollbar came from. */
.chart__bars {
  display: flex;
  align-items: flex-end;
  /* Anchored left so the timeline grows rightwards as check-ins accumulate.
     Without this, a single column stretches to the full width and its bar
     lands in the middle of an otherwise empty chart. */
  justify-content: flex-start;
  gap: clamp(0.25rem, 1.2cqi, 0.75rem);
}

.chart__column {
  /* flex-basis 0 with min-width 0 lets columns shrink as far as they need to,
     so eleven of them always fit. max-width keeps a nearly empty chart from
     turning two check-ins into two enormous columns. */
  flex: 1 1 0;
  min-width: 0;
  max-width: 84px;
  display: grid;
  justify-items: center;
}

/* The stack is exactly the plot area: the bar sits at its bottom and the
   popover positions itself against the same box, so both stay in step. */
.chart__stack {
  position: relative;
  width: 100%;
  height: var(--plot-height);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.chart__bar-button {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--radius-md);
}

.chart__bar {
  position: relative;
  display: block;
  /* Never wider than its column, so the bar thins out instead of forcing the
     row to overflow. */
  width: min(var(--bar-width), 100%);
  /* The 0-2 hours bucket sits at fraction 0, so without a floor it would have
     no height at all. One bar-width is the smallest a pill can be and still
     look deliberate: it renders as a rounded cap resting on the axis. */
  height: max(var(--bar-width), calc(var(--f) * 100%));
  border-radius: var(--radius-pill);
  transition: filter 160ms var(--ease), transform 160ms var(--ease);
  /* The bar grows from the baseline, so the height animation reads as the
     value changing rather than the element sliding. */
  transform-origin: bottom center;
}

.chart__bar--very-happy { background: var(--mood-very-happy); }
.chart__bar--happy { background: var(--mood-happy); }
.chart__bar--neutral { background: var(--mood-neutral); }
.chart__bar--sad { background: var(--mood-sad); }
.chart__bar--very-sad { background: var(--mood-very-sad); }

/* Hover feedback is colour only, never geometry. Scaling the bar moved its
   edge under the pointer, which produced spurious mouseleave/mouseenter pairs
   and made the cursor appear to jump. */
.chart__bar-button:hover .chart__bar,
.chart__bar-button:focus-visible .chart__bar,
.chart__bar-button.is-active .chart__bar {
  filter: brightness(0.94) saturate(1.08);
}

.chart__bar-face {
  position: absolute;
  top: 5px;
  left: 50%;
  transform: translateX(-50%);
  width: 30px;
  height: 30px;
}

.chart__label {
  display: grid;
  justify-items: center;
  gap: 0.125rem;
  margin-top: 0.75rem;
  font-size: var(--fs-xs);
  color: var(--neutral-300);
}
.chart__label-day { font-weight: 600; color: var(--neutral-600); }

.chart__tooltip {
  position: absolute;
  z-index: 15;
  width: max-content;
  max-width: min(250px, 90%);
  padding: 0.875rem 1rem;
  background: var(--neutral-0);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
  text-align: left;
  /* Transparent to the pointer. Without this the popover sits over the bar
     that summoned it, steals the mouseleave, hides itself, and the pointer
     lands back on the bar - an endless show/hide flicker that also makes the
     cursor appear to jump. */
  pointer-events: none;
  /* Kept in the layout while hidden so its width and height stay measurable;
     display:none would make both zero and the clamping arithmetic meaningless. */
  visibility: hidden;
  opacity: 0;
  transition: opacity 120ms var(--ease);
}

.chart__tooltip.is-visible {
  visibility: visible;
  opacity: 1;
}

.chart__tooltip-date {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--neutral-900);
  margin-bottom: 0.5rem;
}

.chart__tooltip-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-xs);
  color: var(--neutral-600);
  margin-bottom: 0.25rem;
}

.chart__tooltip-journal {
  margin-top: 0.5rem;
  font-size: var(--fs-xs);
  line-height: 1.45;
  color: var(--neutral-600);
  overflow-wrap: anywhere;
}

.chart__empty {
  color: var(--neutral-600);
  font-size: var(--fs-sm);
  padding-block: 2rem;
  text-align: center;
}

/* Density steps. Eleven columns always fit; below these widths a column is
   simply too narrow to hold the decoration, so the decoration goes rather than
   the data. Both dropped elements - the face and the month - remain in the
   popover and in each bar's accessible name, so nothing becomes unreachable. */
@container chart (max-width: 40rem) {
  .chart__bar-face { display: none; }
}

@container chart (max-width: 30rem) {
  .chart__label > span:first-child { display: none; }
  .chart__label { font-size: 0.6875rem; margin-top: 0.5rem; }
  .chart__axis { font-size: 0.6875rem; }
}

/* ------------------------------------------------------------ badges -- */

/* 1.25rem matches the gap inside .trends and the margin under .today, so the
   achievements card joins the existing vertical rhythm rather than setting its
   own. */
/* The rosette artwork standing in as the menu icon. It is a photographic PNG
   next to line-art SVGs, so it gets a touch less height to sit on the same
   optical baseline as them. */
.user-menu__badge {
  width: 18px;
  height: 18px;
  object-fit: contain;
}

/* auto-fit rather than a fixed column count: the card is as wide as the page at
   one breakpoint and half of it at another, and the badges should reflow on
   their own instead of needing a media query per layout. */
.badges__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: 1.25rem;
}

.badge {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 1rem;
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-lg);
  transition: border-color 160ms var(--ease), background-color 160ms var(--ease);
}

.badge--earned {
  border-color: var(--blue-200);
  background: var(--neutral-100);
}

.badge__art {
  flex: none;
  width: 64px;
  height: 64px;
}

/* One asset per badge, two states. Draining the colour is what marks a badge as
   unwon, so the locked look costs no extra artwork - and it stays legible,
   because the silhouette still reads at a glance. */
.badge--locked .badge__art {
  filter: grayscale(1) opacity(0.45);
}

.badge__body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.badge__name {
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.3;
}

.badge--locked .badge__name { color: var(--neutral-600); }

.badge__rule {
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: var(--neutral-600);
  overflow-wrap: anywhere;
}

.badge__status {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.125rem;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--neutral-300);
}

.badge--earned .badge__status { color: var(--blue-600); }

/* A native <progress> so it announces its own value; appearance:none then hands
   the painting to the two vendor pseudo-elements, which have to be listed
   separately because a browser drops a whole rule containing a selector it does
   not recognise. */
.badge__bar {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 6px;
  margin-top: 0.375rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--blue-100);
  color: var(--blue-400);
}

.badge__bar::-webkit-progress-bar {
  background: var(--blue-100);
  border-radius: var(--radius-pill);
}

.badge__bar::-webkit-progress-value {
  background: var(--blue-400);
  border-radius: var(--radius-pill);
}

.badge__bar::-moz-progress-bar {
  background: var(--blue-400);
  border-radius: var(--radius-pill);
}

/* ------------------------------------------------------------- modals -- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 1rem;
  overflow-y: auto;
}

.modal__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(33, 33, 77, 0.45);
}

.modal__panel {
  position: relative;
  width: min(600px, 100%);
  max-height: calc(100dvh - 2rem);
  overflow-y: auto;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  background: var(--neutral-0);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
}

.modal__panel--sm { width: min(480px, 100%); }

/* Both the wrapper and the panel can scroll, so both get styled — the panel
   when its content outgrows max-height, the wrapper when the viewport is
   shorter than the panel plus its padding. The thumb is a transparent border
   clipped to padding-box: that insets it without a wrapper element, so the
   full 12px stays grabbable while only 4px is painted. Hover thickens it
   rather than just darkening it, which reads as a response to the cursor. */
.modal,
.modal__panel {
  scrollbar-width: thin;
  scrollbar-color: var(--blue-200) transparent;
}

.modal::-webkit-scrollbar,
.modal__panel::-webkit-scrollbar { width: 12px; }

.modal::-webkit-scrollbar-track,
.modal__panel::-webkit-scrollbar-track { background: transparent; }

.modal::-webkit-scrollbar-thumb,
.modal__panel::-webkit-scrollbar-thumb {
  background: var(--blue-200);
  background-clip: padding-box;
  border: 4px solid transparent;
  border-radius: var(--radius-pill);
  transition: background-color 140ms var(--ease), border-width 140ms var(--ease);
}

.modal::-webkit-scrollbar-thumb:hover,
.modal__panel::-webkit-scrollbar-thumb:hover {
  background: var(--blue-400);
  background-clip: padding-box;
  border-width: 3px;
}

.modal__title {
  font-size: var(--fs-h2);
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.modal__subtitle {
  color: var(--neutral-600);
  font-size: var(--fs-sm);
  margin-bottom: 1.5rem;
}

.steps {
  display: flex;
  gap: 0.5rem;
  margin: 1.25rem 0 1.75rem;
}
.steps__item {
  flex: 1;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--blue-100);
  transition: background-color 200ms var(--ease);
}
.steps__item.is-done { background: var(--blue-600); }

.step { border: 0; padding: 0; margin: 0; }

.step__legend {
  font-size: var(--fs-lead);
  font-weight: 700;
  margin-bottom: 1rem;
  padding: 0;
}

.step__hint {
  font-size: var(--fs-xs);
  color: var(--neutral-600);
  margin-bottom: 0.75rem;
}

.step__error,
.form__error,
.modal__server-error {
  margin-top: 0.75rem;
  font-size: var(--fs-xs);
  color: var(--red-500);
}

.form__error {
  padding: 0.75rem 1rem;
  background: rgba(211, 69, 91, 0.08);
  border-radius: var(--radius-md);
  margin-bottom: 0.5rem;
}

.modal__actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1.75rem;
}

/* ----------------------------------------------------------- calendar -- */

.modal__panel--wide { width: min(680px, 100%); }

/* Wide enough for three badge columns. The grid below is auto-fit, so the
   count follows from the width rather than being hard-coded: at this size
   three fit and a fourth cannot, and narrower viewports drop to two and then
   one on their own. */
.modal__panel--xwide { width: min(920px, 100%); }

.modal__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.modal__close {
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--neutral-600);
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  transition: background-color 140ms var(--ease), color 140ms var(--ease);
}
.modal__close:hover { background: var(--neutral-100); color: var(--neutral-900); }

.calendar__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.calendar__nav {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  font-size: 1.25rem;
  line-height: 1;
  background: var(--neutral-0);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  transition: border-color 140ms var(--ease), color 140ms var(--ease);
}
.calendar__nav:hover:not(:disabled) { border-color: var(--blue-600); color: var(--blue-600); }
.calendar__nav:disabled { opacity: 0.35; cursor: not-allowed; }

.calendar__month {
  font-size: 1.125rem;
  font-weight: 700;
}

.calendar__summary {
  margin: 0.5rem 0 1rem;
  font-size: var(--fs-xs);
  color: var(--neutral-600);
  text-align: center;
}

.calendar { display: grid; gap: 0.375rem; }

.calendar__weekdays,
.calendar__week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0.375rem;
}

.calendar__weekday {
  text-align: center;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--neutral-300);
  padding-bottom: 0.25rem;
}

/* Square tiles regardless of dialog width. */
.calendar__day {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--neutral-900);
  background: var(--neutral-100);
  transition: transform 140ms var(--ease), box-shadow 140ms var(--ease);
}

.calendar__day--very-happy { background: var(--mood-very-happy); }
.calendar__day--happy { background: var(--mood-happy); }
.calendar__day--neutral { background: var(--mood-neutral); }
.calendar__day--sad { background: var(--mood-sad); }
.calendar__day--very-sad { background: var(--mood-very-sad); }

.calendar__day--empty { color: var(--neutral-300); cursor: default; }
/* Days from the neighbouring months are context, not content. */
.calendar__day--outside { opacity: 0.4; }
/* Future days keep their tile - dropping the background made the last row of
   the month look like the grid had simply stopped. They are just quieter,
   because there is nothing there to log yet. */
.calendar__day--future {
  background: var(--neutral-100);
  color: var(--neutral-300);
  opacity: 0.5;
}
.calendar__day--today { border-color: var(--blue-600); }

button.calendar__day:hover { transform: translateY(-1px); box-shadow: var(--shadow-card); }
button.calendar__day.is-selected {
  box-shadow: 0 0 0 2px var(--blue-600);
}

.calendar__detail {
  margin-top: 1.25rem;
  padding: 1rem 1.25rem;
  background: var(--neutral-100);
  border-radius: var(--radius-lg);
}

.calendar__detail-date { font-weight: 700; margin-bottom: 0.625rem; }

.calendar__detail-rows {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}

.calendar__detail-journal {
  margin-top: 0.625rem;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--neutral-600);
  overflow-wrap: anywhere;
}

.calendar__hint {
  margin-top: 1.25rem;
  font-size: var(--fs-xs);
  color: var(--neutral-300);
  text-align: center;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--neutral-200);
  font-size: var(--fs-xs);
  color: var(--neutral-600);
}

.legend__item { display: flex; align-items: center; gap: 0.375rem; }

.legend__swatch {
  width: 0.875rem;
  height: 0.875rem;
  border-radius: 4px;
  background: var(--neutral-100);
}
.legend__swatch--very-happy { background: var(--mood-very-happy); }
.legend__swatch--happy { background: var(--mood-happy); }
.legend__swatch--neutral { background: var(--mood-neutral); }
.legend__swatch--sad { background: var(--mood-sad); }
.legend__swatch--very-sad { background: var(--mood-very-sad); }

/* ------------------------------------------------------------ choices -- */

.choices { display: grid; gap: 0.75rem; }

.choice { display: block; cursor: pointer; }

/* The radio itself is hidden but still focusable and still the thing the
   keyboard drives - the styled body below is only a skin over it. */
.choice input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
}

.choice__body {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.875rem 1rem;
  min-height: 62px;
  background: var(--neutral-0);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  transition: border-color 140ms var(--ease), background-color 140ms var(--ease);
}

.choice:hover .choice__body { border-color: var(--blue-400); }

.choice input:checked + .choice__body {
  border-color: var(--blue-600);
  background: var(--blue-100);
}

.choice input:focus-visible + .choice__body {
  outline: 2px solid var(--blue-600);
  outline-offset: 2px;
}

.choice__label { font-weight: 600; }
.choice__icon { flex: none; }

.choices--plain .choice__body { min-height: 54px; }

.tag-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
}

.tag-choice { cursor: pointer; }

.tag-choice input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
}

.tag-choice span {
  display: inline-block;
  padding: 0.5rem 0.875rem;
  background: var(--neutral-0);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  font-size: var(--fs-xs);
  transition: border-color 140ms var(--ease), background-color 140ms var(--ease);
}

.tag-choice:hover span { border-color: var(--blue-400); }

.tag-choice input:checked + span {
  background: var(--blue-600);
  border-color: var(--blue-600);
  color: var(--neutral-0);
}

.tag-choice input:focus-visible + span {
  outline: 2px solid var(--blue-600);
  outline-offset: 2px;
}

/* A tag that cannot be picked because the limit is reached is dimmed rather
   than removed, so the group's layout does not shift under the pointer. */
.tag-choice input:disabled + span {
  opacity: 0.4;
  cursor: not-allowed;
}

.textarea {
  width: 100%;
  padding: 0.875rem 1rem;
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  resize: vertical;
  min-height: 110px;
  transition: border-color 140ms var(--ease);
}
.textarea:hover { border-color: var(--blue-400); }
.textarea:focus { outline: none; border-color: var(--blue-600); box-shadow: 0 0 0 3px rgba(72, 101, 219, 0.15); }

.textarea__count {
  margin-top: 0.5rem;
  font-size: var(--fs-xs);
  color: var(--neutral-600);
  text-align: right;
}

/* -------------------------------------------------------------- forms -- */

.auth { width: min(560px, 100%); }

.auth__card { padding: clamp(1.5rem, 5vw, 2.5rem); }
.auth__card--center { text-align: center; }

.auth__title {
  font-size: var(--fs-h2);
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.auth__subtitle {
  color: var(--neutral-600);
  font-size: var(--fs-sm);
  margin-bottom: 1.75rem;
}

.auth__switch {
  margin-top: 1.5rem;
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--neutral-600);
}

.form { display: grid; gap: 1.25rem; }

.field { display: grid; gap: 0.5rem; }

.field__label {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--neutral-900);
}

.field__input {
  width: 100%;
  padding: 0.875rem 1rem;
  background: var(--neutral-0);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  transition: border-color 140ms var(--ease), box-shadow 140ms var(--ease);
}
.field__input::placeholder { color: var(--neutral-300); }
.field__input:hover { border-color: var(--blue-400); }
.field__input:focus {
  outline: none;
  border-color: var(--blue-600);
  box-shadow: 0 0 0 3px rgba(72, 101, 219, 0.15);
}

.field__input--error { border-color: var(--red-500); }
.field__input--error:focus { box-shadow: 0 0 0 3px rgba(211, 69, 91, 0.15); }

.field__error { font-size: var(--fs-xs); color: var(--red-500); }
.field__hint { font-size: var(--fs-xs); color: var(--neutral-600); }

.avatar-upload {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.avatar-upload__actions { display: grid; gap: 0.5rem; }
.avatar-upload__hint { font-size: var(--fs-xs); color: var(--neutral-600); }
