/* The public accountability page.
 *
 * Not the ESS app shell: this is a page people visit, not one Fredrick works in, so it has a
 * top nav rather than a sidebar. The tokens, cards, chips and type scale are still the ESS
 * ones, so it reads as the same product.
 *
 * Layout ideas taken from the dashboards in Downloads/firstdash: the pill-tab top nav, the
 * greeting with an inline stat strip, the streak panel with a day-of-week strip, and generous
 * rounded cards.
 */

/* The contribution ramp and the ratio colours are the one place a token from tokens.css will
   not do, because a ramp has to run in opposite directions in the two themes. On white, more
   evidence means darker. On near-black, more evidence means brighter. Reusing the light ramp in
   dark mode made a day with one piece of evidence the brightest square on the calendar, which
   is the opposite of what the chart is for. */
:root {
  --sq-1: #cfdcfb;
  --sq-2: #8fb0f5;
  --sq-3: #4779e6;
  --split-learning: #8fb0f5;
}

:root[data-theme="dark"] {
  --sq-1: #1c2f63;
  --sq-2: #2a4c9e;
  --sq-3: #3a68d6;
  /* Lighter than --brand rather than darker. On a dark ground a deeper blue sat too close to
     the Making segment to tell apart at legend-dot size. */
  --split-learning: #a8bff5;
}

.public-body {
  background: var(--canvas);
}

/* Full width, with a generous ceiling so the calendar's 53 columns have room and the four
 * detail cards do not stretch into letterboxes on an ultrawide. */
.board {
  max-width: 1760px;
  margin: 0 auto;
  padding: 20px 32px 64px 32px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ---------- Row grids ---------- */

.row-hero {
  display: grid;
  grid-template-columns: 2fr 0.85fr 1fr;
  gap: 16px;
  align-items: stretch;
}

/* `stretch`, not `start`. With `start` each card kept its natural height and the row's
 * bottom edge came out ragged: 246, 217 and 351 in one row, 162, 145 and 407 in the next.
 * Equal-height cards with a little breathing room inside read as a grid; unequal ones read
 * as a mistake. */
.row-activity {
  display: grid;
  grid-template-columns: 1.55fr 1fr;
  gap: 16px;
  align-items: stretch;
}

.row-detail {
  display: grid;
  grid-template-columns: 1.5fr 0.95fr 1.15fr;
  gap: 16px;
  align-items: stretch;
}

/* A column of cards inside a stretched row track still has to fill it, or the nested stack
 * ends short and reopens the gap it was meant to close. */
.row-detail > .stack {
  height: 100%;
}

.row-detail > .stack > .card:last-child {
  flex: 1;
}

/* The bar chart bottom-aligns so its baseline matches the row. The split does not: pushing
 * a two-part chart down opens a gap under its subtitle, which reads as a mistake rather than
 * as alignment. Its spare height goes to the bottom of the card instead. */
.row-activity .card > #bars {
  margin-top: auto;
}

/* ---------- Top nav ---------- */

.topnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 10px 14px 10px 18px;
  position: sticky;
  top: 12px;
  z-index: 20;
  box-shadow: 0 10px 30px -22px rgba(16, 21, 40, 0.35);
}

.topnav-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  flex: 0 0 auto;
}

.topnav-brand:hover {
  text-decoration: none;
}

.topnav-brand .brand-mark {
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  font-size: var(--fs-13);
}

/* Pill tabs, dark when active, as in every reference dashboard. */
.topnav-tabs {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--canvas-2);
  border-radius: var(--r-pill);
  padding: 4px;
  overflow-x: auto;
  scrollbar-width: none;
}

.topnav-tabs::-webkit-scrollbar {
  display: none;
}

.tab {
  padding: 7px 15px;
  border-radius: var(--r-pill);
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--speed) ease-out, color var(--speed) ease-out;
}

.tab:hover {
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
}

.tab.is-on {
  background: var(--ink);
  color: var(--surface);
}

/* Leaves the board rather than scrolling it, so it is deliberately not a .tab: the scrollspy
 * reads every tab's href as a CSS selector and a real URL would throw. */
.tab-out {
  padding: 7px 15px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-3);
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--speed) ease-out, color var(--speed) ease-out;
}

.tab-out:hover {
  background: var(--surface);
  color: var(--brand);
  text-decoration: none;
}

/* Both background and colour restated, so hovering the active tab cannot half-apply the
 * plain hover state and leave it looking neither on nor off. */
.tab.is-on:hover {
  background: var(--ink);
  color: var(--surface);
}

.topnav-end {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.who {
  display: flex;
  align-items: center;
  gap: 9px;
}

.who-avatar {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border-radius: 50%;
  background: var(--brand-tint-2);
  color: var(--brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-12);
  font-weight: 800;
  border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px var(--line);
}

.who-text {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

.who-name {
  font-size: var(--fs-12);
  font-weight: 700;
  color: var(--ink);
}

.who-role {
  font-size: var(--fs-10);
  font-weight: 500;
  color: var(--faint);
}

/* ---------- Hero ---------- */

.hero-main {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-22);
  padding: 28px 30px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.hero-eyebrow {
  font-size: var(--fs-11);
  font-weight: 500;
  color: var(--mono-label);
  letter-spacing: 1.4px;
  margin: 0;
}

.hero-title {
  font-size: 34px;
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -1.1px;
  line-height: 1.1;
  margin: 0;
  text-wrap: balance;
}

.hero-wave {
  color: var(--brand);
}

.hero-lede {
  font-size: var(--fs-13);
  font-weight: 500;
  color: var(--ink-3);
  line-height: 1.55;
  margin: 0;
  max-width: 60ch;
}

/* The inline stat strip from the SmartLearn reference. */
.hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 26px;
  margin-top: 8px;
  padding-top: 16px;
  border-top: 1px solid var(--line-2);
}

.hero-stat-value {
  font-size: 26px;
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.9px;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.hero-stat-value .unit {
  font-size: var(--fs-15);
  color: var(--faint);
  font-weight: 700;
}

.hero-stat-label {
  font-size: var(--fs-11);
  font-weight: 600;
  color: var(--faint);
  margin-top: 2px;
}

/* ---------- Streak panel ---------- */

/* The sky follows the clock in Accra, not the visitor's clock, because this card is about
   whether Fredrick is awake and working. `data-sky` is set by public.js; the day palette is
   the default so the card never flashes the wrong time of day before the script runs.

   Every palette stays mid-to-dark. A real dawn is bright, but text sits at the top and the
   bottom of this card, so the time of day is carried by hue and by the sun, moon and stars
   rather than by brightness. That keeps white type legible in all four states. */
.streak-card {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: linear-gradient(175deg, #3f7ae8 0%, #2a5fdd 42%, #1d4ed8 78%, #1b45bd 100%);
  border-radius: var(--r-22);
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  color: #ffffff;
  box-shadow: 0 20px 40px -24px rgba(29, 78, 216, 0.8);
  transition: background 1.5s linear;
}

/* A touch of mauve at the top is what separates dawn from plain daytime blue. Without it the
   two palettes are close enough that the card looks broken rather than early. */
.streak-card[data-sky="dawn"] {
  background: linear-gradient(175deg, #5c5493 0%, #414a8c 30%, #2e3f78 66%, #22315f 100%);
  box-shadow: 0 20px 40px -24px rgba(50, 55, 130, 0.8);
}

.streak-card[data-sky="dusk"] {
  background: linear-gradient(175deg, #6b3f8a 0%, #4b3379 32%, #33265f 66%, #221a44 100%);
  box-shadow: 0 20px 40px -24px rgba(70, 45, 120, 0.85);
}

.streak-card[data-sky="night"] {
  background: linear-gradient(175deg, #16224b 0%, #0f1936 40%, #0a1128 100%);
  box-shadow: 0 20px 40px -24px rgba(10, 20, 60, 0.9);
}

/* Everything that is not the sky sits above it. */
.streak-card > *:not(.sky) {
  position: relative;
  z-index: 1;
}

.sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* The sun is a glow rather than a disc. A disc would compete with the streak number, which is
   the one thing on this card that has to be read. */
.sky-sun {
  position: absolute;
  top: -34%;
  right: -14%;
  width: 60%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 212, 121, 0.34) 0%,
    rgba(255, 212, 121, 0.14) 42%,
    rgba(255, 212, 121, 0) 70%
  );
  transition:
    top 1.5s linear,
    right 1.5s linear,
    opacity 1.5s linear;
}

/* Rising: lower in the frame and pink rather than gold. */
[data-sky="dawn"] .sky-sun {
  top: 0%;
  right: -20%;
  width: 62%;
  background: radial-gradient(
    circle,
    rgba(255, 176, 140, 0.55) 0%,
    rgba(255, 146, 128, 0.22) 44%,
    rgba(255, 146, 128, 0) 70%
  );
}

/* Setting: lower still, and burnt orange. */
[data-sky="dusk"] .sky-sun {
  top: 26%;
  right: -24%;
  width: 54%;
  background: radial-gradient(
    circle,
    rgba(255, 137, 76, 0.4) 0%,
    rgba(240, 110, 90, 0.16) 44%,
    rgba(240, 110, 90, 0) 70%
  );
}

[data-sky="night"] .sky-sun {
  opacity: 0;
}

/* The moon is the one solid disc allowed, and it is small enough not to compete. */
.sky-moon {
  position: absolute;
  top: 13%;
  right: 11%;
  width: 11%;
  aspect-ratio: 1;
  border-radius: 50%;
  opacity: 0;
  background: radial-gradient(circle at 36% 34%, #ffffff 0%, #dde5fb 62%, #c3cff2 100%);
  box-shadow:
    0 0 18px 6px rgba(197, 214, 255, 0.28),
    0 0 44px 18px rgba(150, 175, 245, 0.16);
  transition: opacity 1.5s linear;
}

[data-sky="night"] .sky-moon {
  opacity: 1;
}

[data-sky="dusk"] .sky-moon {
  opacity: 0.45;
}

/* Stars are fixed points, so no repeating tile and no drift. Two layers breathe out of phase
   with each other, which is what stops thirty dots pulsing in unison like a warning light. */
.sky-stars {
  position: absolute;
  inset: 0;
  opacity: 0;
  background-repeat: no-repeat;
  transition: opacity 1.5s linear;
  animation: sky-twinkle 7s ease-in-out infinite;
}

.sky-stars-a {
  background-image:
    radial-gradient(circle 1.2px at 8% 12%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 19% 27%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.4px at 31% 9%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 44% 19%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.3px at 57% 7%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 66% 22%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.2px at 79% 14%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 91% 30%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.1px at 13% 44%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 37% 52%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.3px at 72% 47%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 88% 58%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.1px at 26% 71%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 61% 78%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.2px at 94% 84%, #ffffff, rgba(255, 255, 255, 0) 100%);
}

.sky-stars-b {
  animation-duration: 4.5s;
  animation-delay: -2.2s;
  background-image:
    radial-gradient(circle 0.9px at 4% 33%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.1px at 24% 16%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 0.9px at 41% 35%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 52% 26%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 0.9px at 69% 34%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.1px at 84% 8%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 0.9px at 97% 20%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 7% 62%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 0.9px at 48% 66%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1.1px at 80% 69%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 0.9px at 17% 88%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 1px at 43% 91%, #ffffff, rgba(255, 255, 255, 0) 100%),
    radial-gradient(circle 0.9px at 74% 93%, #ffffff, rgba(255, 255, 255, 0) 100%);
}

[data-sky="night"] .sky-stars {
  opacity: 1;
}

[data-sky="dusk"] .sky-stars {
  opacity: 0.4;
}

@keyframes sky-twinkle {
  0%,
  100% {
    filter: brightness(1);
  }
  50% {
    filter: brightness(0.55);
  }
}

/* Two cloud bands. Each is twice the card's width and carries a pattern that repeats every
   card-width, so translating it by exactly half its own width returns it to where it started.
   That is what makes the loop seamless rather than a visible jump. The ellipse sizes are
   percentages of the tile, so all the puffs in a band scale together and stay in formation. */
.sky-clouds {
  position: absolute;
  top: 0;
  left: 0;
  width: 200%;
  height: 100%;
  background-repeat: repeat-x;
  background-size: 50% 100%;
  will-change: transform;
  animation: sky-drift linear infinite;
  transition: opacity 1.5s linear;
}

/* Every puff is kept clear of the tile edges on purpose. A background tile clips whatever
   overflows it, and a clipped cloud is a rectangle, which is exactly what a sky must not
   contain. Puffs in one cloud share a centre line so the base reads flat, and each holds
   near-solid white to 55% before fading, because a gradient that fades from the centre is a
   smudge rather than a cloud. */
.sky-clouds-far {
  opacity: 0.3;
  animation-duration: 96s;
  background-image:
    radial-gradient(ellipse 11% 8% at 22% 22%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 7% 6% at 30% 22.5%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 8% 6.5% at 14% 23%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 10% 7.5% at 66% 46%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 6.5% 5.5% at 73% 46.5%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 7% 6% at 59% 47%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 7% 5.5% at 42% 68%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 4.5% 4% at 47% 68.5%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%);
}

.sky-clouds-near {
  opacity: 0.32;
  animation-duration: 58s;
  background-image:
    radial-gradient(ellipse 16% 12% at 32% 62%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 10% 8.5% at 43% 63%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 11% 9% at 21% 63.5%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 13% 10% at 80% 26%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 8% 7% at 88% 27%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 9% 7.5% at 71% 27%, rgba(255, 255, 255, 0.98) 55%, rgba(255, 255, 255, 0) 100%);
}

/* Clouds do not go away after sunset, they stop being lit. Dimming rather than hiding them is
   what keeps the night sky from looking like a different component. */
[data-sky="dawn"] .sky-clouds-far {
  opacity: 0.2;
}

[data-sky="dawn"] .sky-clouds-near {
  opacity: 0.24;
}

[data-sky="dusk"] .sky-clouds-far {
  opacity: 0.15;
}

[data-sky="dusk"] .sky-clouds-near {
  opacity: 0.18;
}

[data-sky="night"] .sky-clouds-far {
  opacity: 0.07;
}

[data-sky="night"] .sky-clouds-near {
  opacity: 0.09;
}

/* Keeps the note legible when a bright cloud drifts under it. */
.sky-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(14, 42, 112, 0.5) 0%, rgba(14, 42, 112, 0) 46%);
}

[data-sky="dusk"] .sky-scrim {
  background: linear-gradient(to top, rgba(24, 16, 54, 0.5) 0%, rgba(24, 16, 54, 0) 46%);
}

[data-sky="night"] .sky-scrim {
  background: linear-gradient(to top, rgba(6, 10, 28, 0.5) 0%, rgba(6, 10, 28, 0) 46%);
}

@keyframes sky-drift {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sky-clouds,
  .sky-stars {
    animation: none;
  }
}

/* In dark mode the daytime sky is the only one worth toning down. Dusk and night are already
   dark, and dimming them again would erase the difference between them. */
[data-theme="dark"] .streak-card:not([data-sky]),
[data-theme="dark"] .streak-card[data-sky="day"] {
  background: linear-gradient(175deg, #1e3a8a 0%, #17307a 44%, #12245c 100%);
}

[data-theme="dark"] .streak-card[data-sky="dawn"] {
  background: linear-gradient(175deg, #2c4173 0%, #26386a 32%, #1f2d56 66%, #182242 100%);
}

/* Cloud opacity is deliberately not overridden for dark mode. The `[data-sky]` rules above
   already set it per time of day at the same specificity, and a blanket dark-mode value would
   win on source order and make the night sky cloudier than the afternoon. */
[data-theme="dark"] .streak-card:not([data-sky="night"]) .sky-sun {
  opacity: 0.55;
}

/* On a near-black page the night sky is close enough to the canvas that the card loses its
   edge and stops reading as a card. The shadow cannot rescue it, because a shadow needs
   something lighter behind it, so the edge is drawn instead. */
[data-theme="dark"] .streak-card {
  border: 1px solid var(--line-3);
  box-shadow: none;
}

.streak-head {
  display: flex;
  align-items: center;
  gap: 14px;
}

.streak-flame {
  font-size: 34px;
  color: #ffd479;
}

.streak-count {
  font-size: 40px;
  font-weight: 800;
  letter-spacing: -1.6px;
  line-height: 1;
}

.streak-label {
  font-size: var(--fs-11-5);
  font-weight: 600;
  color: rgba(255, 255, 255, 0.75);
}

.streak-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}

.streak-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.streak-pip {
  width: 100%;
  aspect-ratio: 1;
  max-height: 34px;
  border-radius: var(--r-10);
  background: rgba(255, 255, 255, 0.16);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  color: rgba(255, 255, 255, 0.5);
  transition: transform var(--speed) ease-out;
}

.streak-pip.is-on {
  background: #ffffff;
  color: #f0a417;
}

.streak-pip.is-today {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 2px;
}

.streak-day:hover .streak-pip {
  transform: translateY(-2px);
}

.streak-dow {
  font-size: var(--fs-9);
  font-weight: 700;
  color: rgba(255, 255, 255, 0.65);
  letter-spacing: 0.4px;
}

.streak-note {
  font-size: var(--fs-11);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.72);
  margin: 0;
}

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

.calendar-card {
  padding: 22px 24px 20px 24px;
  border-radius: var(--r-22);
}

.legend {
  display: flex;
  align-items: center;
  gap: 4px;
}

.legend-text {
  font-size: var(--fs-10-5);
  font-weight: 600;
  color: var(--faint);
  margin: 0 4px;
}

.calendar-scroll {
  overflow-x: auto;
  padding-bottom: 6px;
}

/* One shared column width so the month strip lines up with the grid beneath it. Both grids
 * are built from --cell, and public.js recomputes it from the container width on load and on
 * resize, so the year always fills its card exactly.
 *
 * The values here are only the fallback for the moment before that runs. Breakpoints alone
 * cannot do this: 53 columns never divide evenly into an arbitrary viewport, so a stepped
 * size always leaves a strip of dead card on the right. */
.calendar-inner {
  --cell: 13px;
  --cell-gap: 4px;
  --dow-w: 32px;
  min-width: min-content;
}

.calendar-months {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc(var(--cell) + var(--cell-gap));
  margin-left: var(--dow-w);
  height: 16px;
}

.calendar-month {
  font-size: var(--fs-10-5);
  font-weight: 700;
  color: var(--faint);
  white-space: nowrap;
  grid-row: 1;
}

.calendar-body {
  display: flex;
  gap: 0;
}

.calendar-dow {
  display: grid;
  grid-template-rows: repeat(7, var(--cell));
  gap: var(--cell-gap);
  width: var(--dow-w);
  flex: 0 0 var(--dow-w);
}

.calendar-dow span {
  font-size: var(--fs-9);
  font-weight: 700;
  color: var(--faint);
  line-height: var(--cell);
}

.calendar {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, var(--cell));
  grid-auto-columns: var(--cell);
  gap: var(--cell-gap);
  min-width: min-content;
}

.cell {
  width: var(--cell, 13px);
  height: var(--cell, 13px);
  border-radius: 4px;
  background: var(--line-2);
  transition: transform 120ms ease-out, outline-color 120ms ease-out;
  outline: 2px solid transparent;
  outline-offset: 1px;
}

.cell.is-pad {
  background: transparent;
}

.cell.level-0 {
  background: var(--line-2);
}
.cell.level-1 {
  background: var(--sq-1);
}
.cell.level-2 {
  background: var(--sq-2);
}
.cell.level-3 {
  background: var(--sq-3);
}
.cell.level-4 {
  background: var(--brand);
}

.cell.is-today {
  outline-color: var(--ink);
}

.calendar .cell:not(.is-pad):hover {
  transform: scale(1.45);
  outline-color: var(--ink);
}

.calendar-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  padding-top: 14px;
  margin-top: 4px;
  border-top: 1px solid var(--line-2);
}

.foot-stat-value {
  font-size: var(--fs-19);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
}

.foot-stat-label {
  font-size: var(--fs-11);
  font-weight: 600;
  color: var(--faint);
}

/* Hover tooltip for a calendar square. */
.tip {
  position: fixed;
  z-index: 60;
  pointer-events: none;
  background: var(--ink);
  color: var(--surface);
  border-radius: var(--r-9);
  padding: 7px 11px;
  font-size: var(--fs-11-5);
  font-weight: 600;
  line-height: 1.4;
  box-shadow: 0 12px 28px -14px rgba(16, 21, 40, 0.6);
  white-space: nowrap;
}

.tip .tip-date {
  color: rgba(255, 255, 255, 0.6);
  font-weight: 500;
}

/* ---------- Body split ---------- */

.board-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding-top: 10px;
}

/* ---------- Rules list ---------- */

.rules-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rule {
  display: flex;
  gap: 13px;
  padding: 11px 12px;
  border-radius: var(--r-14);
  transition: background var(--speed) ease-out;
}

.rule:hover {
  background: var(--canvas-2);
}

.rule-num {
  font-size: var(--fs-11);
  font-weight: 500;
  color: var(--faint-2);
  padding-top: 1px;
}

.rule-title {
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--ink);
}

.rule-note {
  font-size: var(--fs-11-5);
  font-weight: 500;
  color: var(--faint);
  padding-top: 2px;
}

/* ---------- Ladder ---------- */

.ladder {
  display: flex;
  flex-direction: column;
}

.rung {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 5px 0;
}

.rung-dot {
  width: 11px;
  height: 11px;
  flex: 0 0 11px;
  border-radius: 50%;
  background: var(--line-3);
  position: relative;
}

.rung-dot.is-on {
  background: var(--brand);
}

/* The connector between rungs, drawn as a pseudo-element so no extra nodes are needed. */
.rung:not(:last-child) .rung-dot::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 11px;
  transform: translateX(-50%);
  width: 2px;
  height: 12px;
  background: var(--line-3);
}

.rung.is-on:not(:last-child) .rung-dot::after {
  background: var(--brand);
}

.rung-label {
  font-size: var(--fs-12-5);
  font-weight: 500;
  color: var(--faint);
}

.rung.is-on .rung-label {
  font-weight: 700;
  color: var(--ink);
}

/* ---------- Card hover, for the whole board ---------- */

.board .card,
.board .hero-main {
  transition: box-shadow var(--speed) ease-out, border-color var(--speed) ease-out;
}

.board .card:hover,
.board .hero-main:hover {
  border-color: var(--line-3);
  box-shadow: 0 18px 40px -30px rgba(16, 21, 40, 0.4);
}

/* ---------- Responsive ---------- */

@media (max-width: 1400px) {
  .row-hero {
    grid-template-columns: 1fr 1fr;
  }
  .row-hero .hero-main {
    grid-column: 1 / -1;
  }
  .row-detail {
    grid-template-columns: 1fr 1fr;
  }
  .row-detail > .stack {
    grid-column: 1 / -1;
  }
}

@media (max-width: 900px) {
  .row-hero,
  .row-activity,
  .row-detail {
    grid-template-columns: 1fr;
  }
  .row-hero .hero-main,
  .row-detail > .stack {
    grid-column: auto;
  }
}

@media (max-width: 760px) {
  .board {
    padding: 12px 12px 48px 12px;
  }
  .topnav {
    flex-wrap: wrap;
    border-radius: var(--r-22);
    position: static;
  }
  .topnav-tabs {
    order: 3;
    width: 100%;
  }
  .who-text {
    display: none;
  }
  .hero-main {
    padding: 22px 20px;
  }
  .hero-title {
    font-size: 26px;
    letter-spacing: -0.8px;
  }
  .hero-stats {
    gap: 18px;
  }
}

/* ---------- Gauge ---------- */

.gauge-card {
  border-radius: var(--r-22);
  justify-content: space-between;
}

.gauge-wrap {
  position: relative;
  padding: 4px 0 0 0;
}

.gauge-track {
  stroke: var(--line-2);
}

.gauge-fill {
  stroke: var(--brand);
  transition: stroke-dasharray 600ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The value sits inside the arc's mouth. Absolute rather than an SVG <text> so it can use
 * the same type tokens as every other number on the page. */
.gauge-centre {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  text-align: center;
}

.gauge-value {
  font-size: 32px;
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -1.2px;
  line-height: 1;
}

.gauge-label {
  font-size: var(--fs-11);
  font-weight: 600;
  color: var(--faint);
  margin-top: 3px;
}

.gauge-foot {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--line-2);
}

.gauge-foot .foot-stat-value {
  font-size: var(--fs-15);
}

/* ---------- Activity bars ---------- */

.bars {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 6px;
  align-items: end;
}

.bar-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.bar-track {
  width: 100%;
  display: flex;
  align-items: flex-end;
  background: var(--canvas-2);
  border-radius: var(--r-8);
  overflow: hidden;
}

.bar-fill-v {
  width: 100%;
  background: var(--brand-pale);
  border-radius: var(--r-8);
  position: relative;
  transition: background var(--speed) ease-out, height 500ms cubic-bezier(0.22, 1, 0.36, 1);
}

.bar-fill-v.is-full {
  background: var(--brand);
}

.bar-col:hover .bar-fill-v {
  background: var(--brand);
}

/* The value badge, from the LearnIQ reference. Hidden until hover so fourteen numbers do
 * not compete with the shape of the chart. */
.bar-badge {
  position: absolute;
  top: -22px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--surface);
  font-size: var(--fs-10);
  font-weight: 700;
  padding: 2px 6px;
  border-radius: var(--r-6);
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--speed) ease-out;
  pointer-events: none;
}

.bar-col:hover .bar-badge {
  opacity: 1;
}

.bar-label {
  font-size: var(--fs-9);
  font-weight: 600;
  color: var(--faint);
  white-space: nowrap;
}

.bar-col.is-today .bar-label {
  color: var(--brand);
  font-weight: 800;
}

/* ---------- Stacked split ---------- */

.stack-chart {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.stack-bar {
  display: flex;
  gap: 3px;
  height: 34px;
}

.stack-seg {
  border-radius: var(--r-8);
  transition: flex-basis 500ms ease-out, opacity var(--speed) ease-out;
  min-width: 4px;
}

.stack-seg:hover {
  opacity: 0.82;
}

.stack-seg.is-empty {
  background: var(--line-2) !important;
}

.stack-seg.making {
  background: var(--brand);
}
.stack-seg.learning {
  background: var(--split-learning);
}
.stack-seg.exploring {
  background: var(--warn);
}

.stack-legend {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.stack-item {
  display: flex;
  align-items: center;
  gap: 9px;
}

.stack-dot {
  width: 10px;
  height: 10px;
  flex: 0 0 10px;
  border-radius: 3px;
}

.stack-dot.making {
  background: var(--brand);
}
.stack-dot.learning {
  background: var(--split-learning);
}
.stack-dot.exploring {
  background: var(--warn);
}

.stack-text {
  font-size: var(--fs-12-5);
  font-weight: 600;
  color: var(--ink-2);
  flex: 1;
}

.stack-value {
  font-size: var(--fs-12);
  font-weight: 700;
  color: var(--faint);
}
