/* The Explorer's own; what every page shares is in shared/geo.css, what the pages about days
   share in shared/data.css. */

/* ── one day ────────────────────────────────────────────────────────────── */

/*
  A day is its map, under the header, with the day's cards in a box down the right and its
  timeline in one along the bottom, left of it. The popup of modes opens over the map or the
  timeline, placed against the stage.
*/
.day-stage {
  --side-width: 26rem;
}

.day-side .float-head {
  flex-wrap: wrap;
}

.float-head.day-head h2 {
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
}

.float-head.day-head button.secondary {
  padding: 0.2rem 0.6rem;
}

.day-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--line);
}

.day-side .card {
  padding: 0.6rem 0.75rem;
}

.timeline-box {
  right: calc(var(--side-width) + 2 * var(--float-gap));
}

.timeline-box > :not(.float-head) {
  margin-inline: 0.75rem;
}

@media (max-width: 70rem) {
  .day-stage {
    --side-width: 22rem;
  }
}

/* Too narrow for the two side by side: the cards above, the timeline below, either folded away.
   Leaflet's attribution keeps the corner, below the timeline. */
@media (max-width: 56rem) {
  .day-stage {
    --credit-right: 0px;
  }

  .float.day-side {
    left: var(--float-gap);
    width: auto;
    bottom: auto;
    max-height: 48%;
  }

  .timeline-box {
    right: var(--float-gap);
    max-height: 42%;
  }
}

/* ── labels ─────────────────────────────────────────────────────────────── */

.label-pick {
  font-size: 0.8rem;
  padding: 0.1rem 0.2rem;
}

.label-row {
  margin: 0.35rem 0;
}

/* ── routes drawn by hand ───────────────────────────────────────────────── */

.revisit-why {
  flex-basis: 100%;
}

.route-row {
  margin: 0.35rem 0;
}

.route-row .icons {
  display: inline-flex;
  vertical-align: middle;
}

.route-row .sev {
  margin-right: 0.3rem;
}

.route-editor {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--line);
}

.route-places {
  margin: 0.4rem 0;
  padding: 0;
  list-style: none;
}

.route-leg {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

.route-leg .route-place {
  flex-basis: 100%;
}

.route-leg select {
  font-size: 0.8rem;
  padding: 0.1rem 0.2rem;
}

.route-place {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  margin: 0.25rem 0;
}

.route-actions {
  display: flex;
  gap: 0.5rem;
}

/* The line of the route being drawn, to drag a place out of. */
.leaflet-pane > svg path.route-line {
  cursor: copy;
}

/* A place of the route being drawn, dragged to move it, clicked to take it out; the gap's ends
   are filled and stay where they are. Drawn on the map's light tiles, not in the page's colours. */
.place {
  width: 12px;
  height: 12px;
  margin: -8px 0 0 -8px;
  border: 2px solid #1f6feb;
  border-radius: 50%;
  background: #fff;
  cursor: move;
}

.place.end {
  background: #1f6feb;
  cursor: default;
}

.label-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.6rem;
}

/* ── the day's timeline ─────────────────────────────────────────────────── */

.timeline-tools {
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
}

.timeline-zoom {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.timeline-save {
  display: inline-flex;
  gap: 0.5rem;
  margin-left: auto;
}

.timeline-save + button.fold {
  margin-left: 0;
}

.timeline {
  overflow-x: auto;
  overflow-y: hidden;
  padding: 0.5rem 0 0.35rem;
}

.timeline-track {
  position: relative;
  min-width: 100%;
  height: 4.4rem;
  user-select: none;
}

/* With memory on the day, a thin lane of it under the stretches. */
.timeline-track.remembered {
  height: 5.4rem;
}

.timeline-track.remembered .tl-lane {
  bottom: 1.2rem;
}

.tl-memory {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.2rem;
  height: 0.8rem;
  border-radius: 3px;
  background: var(--ground);
}

/* Greyed out: memory is never labelled, and is not the day's track. */
.tl-remembered {
  position: absolute;
  top: 0;
  bottom: 0;
  min-width: 2px;
  border-radius: 2px;
  background: repeating-linear-gradient(-45deg, #8a9ab0 0 3px, #5d6b7c 3px 6px);
}

.tl-remembered.stay {
  background: #8a9ab0;
}

.timeline-track.splitting,
.timeline-track.splitting .tl-span {
  cursor: crosshair;
}

.tl-ruler {
  position: absolute;
  inset: 0 0 auto 0;
  height: 1.5rem;
  border-bottom: 1px solid var(--line);
}

.tl-tick {
  position: absolute;
  bottom: 0;
  width: 1px;
  height: 0.3rem;
  background: var(--line-strong);
}

.tl-tick.major {
  height: 0.55rem;
  background: var(--muted);
}

.tl-tick span {
  position: absolute;
  bottom: 0.6rem;
  transform: translateX(-50%);
  color: var(--muted);
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Where no stretch lies the day has no data, and the lane shows through. */
.tl-lane {
  position: absolute;
  inset: 1.9rem 0 0.2rem 0;
  border-radius: 4px;
  background: var(--ground);
}

/* Parted by a line of the box's own colour, so two stretches of one colour still read as two. */
.tl-span {
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-right: 1px solid var(--raised);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

/* As estimated: striped, so what a label settled stands out solid beside it. */
.tl-span:not(.labelled) {
  background-image: repeating-linear-gradient(
    135deg,
    rgb(255 255 255 / 30%) 0 4px,
    transparent 4px 9px
  );
}

/* A long stretch drawn shorter than it lasted, so the rest of the day keeps its room: thin
   dark lines across all of it, the other way from the estimate's stripes. Over the colour
   rather than in the background image, which the stripes already take. */
.tl-span.shortened::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(45deg, rgb(0 0 0 / 45%) 0 1px, transparent 1px 6px);
}

.tl-icon {
  position: relative;
}

.tl-span.changed {
  outline: 2px dashed var(--ink);
  outline-offset: -2px;
}

.tl-span.picked {
  box-shadow: inset 0 0 0 3px var(--ink);
}

.tl-edge {
  position: absolute;
  top: -0.25rem;
  bottom: -0.25rem;
  z-index: 2;
  width: 12px;
  margin-left: -6px;
  cursor: col-resize;
  touch-action: none;
}

.tl-edge::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 5px;
  width: 2px;
  background: var(--ink);
  opacity: 0;
}

.tl-edge:hover::after,
.tl-edge:focus-visible::after,
.tl-edge.active::after {
  opacity: 0.9;
}

.tl-edge:focus {
  outline: none;
}

.tl-time {
  display: none;
  position: absolute;
  bottom: calc(100% + 0.2rem);
  left: 50%;
  transform: translateX(-50%);
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  background: var(--ink);
  color: var(--ground);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.tl-edge.active .tl-time,
.tl-edge:focus-visible .tl-time {
  display: block;
}

/* Over the map's boxes, below the header. */
.tl-popup {
  position: absolute;
  z-index: 1020;
  padding: 0.35rem;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--raised);
  box-shadow: var(--shadow);
}

.tl-info {
  max-width: 24rem;
  margin: 0 0 0.35rem;
  padding: 0.1rem 0.3rem 0.4rem;
  border-bottom: 1px solid var(--line);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
  user-select: text;
  cursor: text;
}

.tl-modes {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.2rem;
}

.tl-popup button {
  padding: 0.25rem 0.5rem;
  border: 1px solid transparent;
  background: none;
  color: var(--ink);
  font-weight: 400;
  text-align: left;
  white-space: nowrap;
}

.tl-popup button:hover,
.tl-popup button:focus-visible {
  border-color: var(--line);
  background: var(--tint);
}

.tl-popup button.current {
  font-weight: 600;
}

/* Apart from the modes, since they are not ones, each with the icon its action shows. */
.tl-popup button.delete,
.tl-popup button.edit-route {
  grid-column: 1 / -1;
  display: flex;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.15rem;
  border-top: 1px solid var(--line);
  border-radius: 0;
  color: var(--bad);
}

.tl-popup button.delete::before,
.tl-popup button.edit-route::before {
  content: '';
  width: 1rem;
  height: 1rem;
  background: currentColor;
  mask: var(--icon-remove) center / contain no-repeat;
}

.tl-popup button.edit-route {
  color: var(--ink);
}

.tl-popup button.edit-route::before {
  mask-image: var(--icon-edit);
}

/* ── the day's history ──────────────────────────────────────────────────── */

.history-row {
  border-left: 3px solid var(--line-strong);
  padding: 0.2rem 0.6rem;
  margin: 0.3rem 0;
  font-size: 0.85rem;
}

.history-row.failed {
  border-left-color: var(--bad);
}

/* The steps that follow from a request step back behind what caused them. */
.history-row.quiet {
  opacity: 0.6;
}

.history-stage {
  font-weight: 600;
}

/* ── heat ───────────────────────────────────────────────────────────────── */

/* The heat page is its map, or the whole screen without the header. */
body.heat-page.full {
  padding-top: 0;
}

body.heat-page.full .app-header {
  display: none;
}

/* Open, the controls take the right of the map from top to bottom, and the buttons and Leaflet's
   attribution move to their left, as the controls slide in; closed, the buttons keep the corner.
   The controls are hidden rather than folded, so the stage says whether they are open. */
.heat-page {
  --side-width: 28rem;
}

.heat-page .map-stage:not(.side-open) {
  --credit-right: 0px;
}

.heat-tools {
  overflow: visible;
}

.float.heat-tools {
  right: calc(var(--credit-right, 0px) + var(--float-gap));
  transition: right var(--motion) var(--motion-ease);
}

.heat-side.sliding {
  transition:
    transform var(--motion) var(--motion-ease),
    opacity var(--motion) var(--motion-ease);
}

.heat-side.slid-out {
  transform: translateX(calc(100% + var(--float-gap)));
  opacity: 0;
}

/* Too narrow for that, the buttons keep the corner and the controls take the top beside them. */
@media (max-width: 48rem) {
  .float.heat-tools {
    left: auto;
  }

  .float.heat-side {
    right: calc(2 * var(--float-gap) + 34px);
    width: auto;
    bottom: auto;
    max-height: 60%;
  }
}

.heat-status {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
}

.heat-tools button {
  display: block;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 9px 9px 0 0;
  background: none;
  color: var(--ink);
}

.heat-tools button + button {
  border-top: 1px solid var(--line);
  border-radius: 0 0 9px 9px;
}

.heat-tools button:where(:enabled:hover) {
  background: var(--tint);
  color: var(--ink);
}

.heat-tools svg {
  width: 16px;
  height: 16px;
  vertical-align: middle;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
}

.heat-controls {
  display: grid;
  gap: 0.5rem;
}

.heat-controls .sub {
  font-size: 0.9rem;
  margin-top: 0.25rem;
}

/* The page's input box, padding and border, on a slider shortens the track the thumb can
   travel inside the box that is drawn: its ends looked out of reach. */
.heat-controls input[type='range'] {
  margin: 0.35rem 0;
}

/* The timeframes down a time axis: years, then the lanes of bars, then the leaders, then the
   labels, each placed by heat.js in rem. A label is two lines of a set height, its name above the
   days it has data on, and cut short rather than wrapped, which would throw the layout out. */
.heat-timeline {
  position: relative;
  margin: 0.6rem 0 0.2rem;
}

.tl-year {
  position: absolute;
  left: 0;
  height: 0;
  border-top: 1px dashed var(--line);
}

.tl-year span {
  position: absolute;
  top: -0.55rem;
  left: 0;
  padding-right: 0.25rem;
  background: var(--raised);
  font-size: 0.75rem;
  line-height: 1.1rem;
  color: var(--muted);
}

/* In front of the bars, which a leader crosses on its way to the bar it is for; clicks go
   through to them. */
.tl-leaders {
  position: absolute;
  top: 0;
  z-index: 1;
  overflow: visible;
  pointer-events: none;
}

.tl-leaders path {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
}

/* --tf is the timeframe's own colour, set on its bar, its label and its lit leader, which are
   drawn in it as they would be in the accent. */
.tl-leaders path.lit {
  stroke: var(--tf, var(--accent));
}

button.tl-bar {
  position: absolute;
  box-sizing: border-box;
  width: 0.7rem;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: var(--surface);
}

button.tl-bar.on {
  border-color: var(--tf, var(--accent));
  background: var(--tf, var(--accent));
}

button.tl-bar:where(:enabled:hover),
button.tl-bar.lit {
  border-color: var(--tf, var(--accent));
  background: color-mix(in oklab, var(--tf, var(--accent)) 22%, var(--ground));
}

button.tl-bar.on:where(:enabled:hover),
button.tl-bar.on.lit {
  background: color-mix(in oklab, var(--tf, var(--accent)), white 14%);
}

label.check.tl-label {
  position: absolute;
  right: 0;
  height: 2.4rem;
  align-items: flex-start;
  overflow: hidden;
  cursor: pointer;
}

.tl-label input {
  margin-top: 0.2rem;
  accent-color: var(--tf, var(--accent));
}

.tl-label > span {
  display: grid;
  min-width: 0;
}

.tl-label > span > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tl-label .tl-name {
  font-size: 0.9rem;
  line-height: 1.3rem;
}

/* The name, and the kinds of its memory at the right of its line. */
.tl-label .tl-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.tl-head .tl-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tl-kinds {
  display: flex;
  flex: none;
  gap: 0.1rem;
}

/* --kind is the kind's colour (geo.css's --memory-*): drawn in it while shown, grey while left
   out, and struck through while the timeframe is not ticked, so what ticking it brings back
   still shows. */
button.tl-kind {
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--kind);
  cursor: pointer;
}

button.tl-kind:hover {
  background: var(--tint-strong);
}

button.tl-kind.off {
  color: var(--muted);
}

button.tl-kind svg {
  width: 0.95rem;
  height: 0.95rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

button.tl-kind .strike {
  display: none;
}

button.tl-kind.struck .strike {
  display: inline;
  stroke: var(--muted);
}

button.tl-kind.struck {
  opacity: 0.65;
}

.tl-label.lit .tl-name {
  color: var(--tf, var(--accent));
}

.tl-label .muted {
  font-size: 0.78rem;
  line-height: 1.1rem;
}

/* Near home that is hundreds of days; the list scrolls rather than pushing the page down. */
.heat-days {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.heat-days .table.scroll {
  max-height: 14rem;
}

/* The legend sits on the map, above the scale, as narrow as it can be read. */
.heat-legend {
  width: 16rem;
  padding: 0.4rem 0.6rem;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
}

.heat-popup .leaflet-popup-content {
  font-size: 0.9rem;
}

.heat-popup .leaflet-popup-content p {
  margin: 0 0 0.5rem;
}

.heat-popup table {
  font-size: 0.85rem;
  background: none;
}

.heat-popup th {
  background: var(--raised);
}

.heat-note {
  font-size: 0.85rem;
}

.stops {
  display: grid;
  gap: 0.35rem;
}

.stop {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.stop input[type='color'] {
  width: 2.5rem;
  height: 1.8rem;
}

.ramp {
  height: 1rem;
  border-radius: 4px;
  border: 1px solid var(--line);
}

.ramp-ends {
  display: flex;
  justify-content: space-between;
  font-size: 0.8rem;
  color: var(--muted);
}

.wheel-row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

.wheel-row p {
  margin: 0;
  font-size: 0.85rem;
}

.wheel {
  flex: none;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  border: 1px solid var(--line);
}

.copy-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.copy-row input {
  flex: 1;
  min-width: 0;
  font-family: var(--mono);
  font-size: 0.8rem;
}

.layer-name {
  flex: none;
  width: 5.5rem;
  font-size: 0.85rem;
}

/* Over the tiles, light or dark: dark writing with a light halo reads on either. */
.tile-grid {
  box-sizing: border-box;
  border: 1px solid rgb(31 111 235 / 45%);
  color: #0a3069;
  font:
    11px ui-monospace,
    SFMono-Regular,
    Menlo,
    monospace;
  padding: 2px 4px;
  text-shadow: 0 0 2px #fff;
}

/* ── calendar ───────────────────────────────────────────────────────────── */

/* The key stays at the top of the window, so a calendar several screens long still says what
   its colours mean wherever the reader is in it. */
.cal-key {
  position: sticky;
  top: var(--header-height);
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  align-items: center;
  margin: 0.75rem 0;
  padding: 0.5rem 0;
  background: var(--ground);
  border-bottom: 1px solid var(--line);
  font-size: 0.85rem;
  color: var(--muted);
}

@media (max-width: 48rem) {
  .cal-key {
    top: 0;
  }
}

.cal-ramp {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.cal-ramp .ramp {
  display: inline-block;
  width: 10rem;
  height: 0.8rem;
}

.cal-swatch {
  display: inline-block;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 3px;
  vertical-align: middle;
}

.cal-section + .cal-section > .cal-year {
  margin-top: 1rem;
}

.cal-year h3 {
  display: flex;
  align-items: center;
  margin: 0 0 0.4rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
}

/* One grid per line of months, all --sheet-columns wide: the count is measured in
   calendar.js, because only a whole line can be left out. The gap is SHEET_GAP_REM. */
.cal-sheets {
  display: grid;
  grid-template-columns: repeat(var(--sheet-columns, 3), minmax(0, 1fr));
  column-gap: 0.75rem;
  /* Twenty years are thousands of cells: a line out of sight is left undrawn, at the height
     calendar.js measured off one that is drawn. */
  content-visibility: auto;
  contain-intrinsic-block-size: auto var(--line-height, 15rem);
}

.cal-sheets + .cal-sheets {
  margin-top: 0.75rem;
}

.cal-sheet h4 {
  display: flex;
  gap: 0.35rem;
  align-items: center;
  margin: 0 0 0.25rem;
  font-size: 0.9rem;
  font-weight: 600;
}

.cal-sheet h4 .loading-spinner {
  width: 0.7rem;
  height: 0.7rem;
  border-width: 1.5px;
}

.cal-weekdays,
.cal-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.1875rem;
}

.cal-weekdays {
  margin-bottom: 0.1875rem;
  color: var(--muted);
  font-size: 0.7rem;
  text-align: center;
}

.cal-days .day {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  min-width: 0;
  border-radius: 0.1875rem;
  background: var(--cell-fill);
  color: var(--cell-ink);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  overflow: hidden;
  /* The reading is sized off the cell as laid out, which is what a size container is for. */
  container-type: size;
  /* The marks sit in its corners. */
  position: relative;
}

.cal-days a.day[href] {
  cursor: pointer;
}

.cal-days a.day[href]:hover {
  outline: 2px solid var(--ink);
  outline-offset: -1px;
}

/* As large as its cell can carry: capped by the type size, by the height, and by the width
   shared between the characters of a tabular figure (about 0.62 em each). */
.cal-days .reading {
  font-size: min(
    0.75rem,
    calc(100cqb * 0.8),
    calc(100cqi / (max(1, var(--reading-length, 1)) * 0.62))
  );
  line-height: 1;
  white-space: nowrap;
}

/* How far a day is labelled, drawn in the cell's own ink: dashed for part of it, solid for all. */
.cal-check {
  width: 68%;
  height: 68%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Butt ends, or the round caps close the gaps: each cap reaches half the stroke width into one.
   The pattern repeats every 4.5 units and is shifted to put a dash on the corner, 4.5 units
   along the path, so the tick keeps its shape. */
.cal-check.some {
  stroke-linecap: butt;
  stroke-dasharray: 2.5 2;
  stroke-dashoffset: 1.25;
}

.cal-check.key {
  width: 1rem;
  height: 1rem;
  vertical-align: -0.2em;
  color: var(--ink);
}

/* What the heatmap and the statistics hold of a day, bottom left, and the cloud's copy of it,
   bottom right, in the cell's own ink: filled for the day as it is now, outlined for an older
   build of it. */
.cal-heat,
.cal-cloud {
  position: absolute;
  bottom: 4%;
  width: 36%;
  height: 36%;
  max-width: 0.75rem;
  max-height: 0.75rem;
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linejoin: round;
  pointer-events: none;
}

.cal-heat {
  left: 4%;
}

.cal-cloud {
  right: 4%;
}

.cal-heat .fold {
  fill: none;
  stroke: var(--cell-fill);
}

.cal-heat.stale,
.cal-cloud.stale {
  fill: none;
}

.cal-heat.stale .fold {
  stroke: currentColor;
}

.cal-heat.key,
.cal-cloud.key {
  position: static;
  width: 1rem;
  height: 1rem;
  max-width: none;
  max-height: none;
  vertical-align: -0.2em;
  color: var(--ink);
  --cell-fill: var(--ground);
}

/* A day the map importer is fetching a map for, which builds it again: a dashed line round it,
   where nothing else of the cell is. */
.cal-days .day.download,
.cal-swatch.download {
  outline: 1.5px dashed var(--ink);
  outline-offset: -2px;
}

.cal-swatch.download {
  background: var(--raised);
}

/* A day put first: its top left corner in another colour. Drawn square and clipped by the cell,
   so the corner is rounded exactly as the cell is. A line of the page's colour along the cut
   keeps the corner apart from a fill as light as itself. */
.cal-first {
  position: absolute;
  top: 0;
  left: 0;
  width: 45%;
  height: 45%;
  max-width: 0.75rem;
  max-height: 0.75rem;
  fill: var(--ink);
  pointer-events: none;
}

.cal-first .edge {
  fill: none;
  stroke: var(--ground);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.cal-swatch.first {
  position: relative;
  overflow: hidden;
  background: var(--raised);
}

.cal-swatch.first .cal-first {
  width: 70%;
  height: 70%;
}

/* Putting a year or a month first: quiet until it is looked at, since there are twelve of them
   to every year and most are never pressed. */
.cal-first-button {
  display: inline-flex;
  margin-left: auto;
  padding: 0.125rem;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  color: var(--muted);
}

.cal-first-button svg {
  width: 0.85rem;
  height: 0.85rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.cal-first-button:enabled:hover {
  border-color: var(--line);
  background: var(--tint);
  color: var(--accent);
}

.cal-first-button:enabled:active {
  border-color: var(--accent);
  background: var(--tint-strong);
  color: var(--accent-pressed);
  box-shadow: none;
}

.cal-first-button:disabled {
  opacity: 0.35;
}

/* A day with nothing recorded, which is not the bottom of the ramp: on a colour scale a zero is
   a colour. A cross in it, so the cell reads as an answer rather than as one that failed to
   draw. */
.cal-days .day.blank,
.cal-swatch.blank {
  position: relative;
  background: var(--surface);
}

.cal-days .day.blank::before,
.cal-days .day.blank::after,
.cal-swatch.blank::before,
.cal-swatch.blank::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  height: 62%;
  width: min(0.16rem, 7%);
  min-width: 1px;
  border-radius: 999px;
  background: var(--line-strong);
}

.cal-days .day.blank::before,
.cal-swatch.blank::before {
  transform: rotate(45deg);
}

.cal-days .day.blank::after,
.cal-swatch.blank::after {
  transform: rotate(-45deg);
}

/* There is data, but not yet the number: the day has not been processed. */
.cal-days .day.unknown,
.cal-swatch.unknown {
  background: repeating-linear-gradient(45deg, var(--raised) 0 2px, var(--line-strong) 2px 3px);
}

/* A day of a year still being read: the month stands, but says nothing yet. */
.cal-days .day.waiting {
  background: var(--surface);
}

/* Before the first day with data, or after today: a place held, so the days keep their
   weekday columns. */
.cal-days .day.pad {
  background: none;
}

/* A stretch with nothing in it, said in a line instead of drawn as a hundred empty cells. */
.cal-gap {
  margin: 0.75rem 0;
  padding: 0.375rem 0;
  border-top: 1px dashed var(--line-strong);
  border-bottom: 1px dashed var(--line-strong);
  color: var(--muted);
  font-size: 0.85rem;
}

/* Follows the pointer, over everything but a dialog. */
.cal-tip {
  position: fixed;
  z-index: 1150;
  max-width: 20rem;
  padding: 0.375rem 0.5rem;
  border: 1px solid var(--line-strong);
  border-radius: 0.375rem;
  background: var(--raised);
  color: var(--ink);
  box-shadow: var(--shadow);
  font-size: 0.8rem;
  pointer-events: none;
}

.cal-tip strong {
  display: block;
  margin-bottom: 0.15rem;
}

/* The body is several lines, and a newline is how it says so. */
.cal-tip span {
  white-space: pre-line;
}

/* Memory on a day (docs/Memory.md): stripes over its colour where the memory is shown, cut to
   the cell's shape with it, and a red corner where it meets a recording. */
.cal-days .day.memory,
.cal-swatch.memory {
  background-image: repeating-linear-gradient(
    135deg,
    rgb(15 19 22 / 45%) 0 1.5px,
    transparent 1.5px 5px
  );
}

/* Remembered only: a grey of its own, apart from a day with no data, with light stripes. */
.cal-days .day.remembered,
.cal-swatch.remembered {
  background-color: var(--line-strong);
}

.cal-days .day.remembered.memory,
.cal-swatch.remembered.memory {
  background-image: repeating-linear-gradient(
    135deg,
    rgb(255 255 255 / 28%) 0 1.5px,
    transparent 1.5px 5px
  );
}

/* Memory that meets a recording: the top right corner in red, as the top left is for a day put
   first. */
.cal-conflict {
  position: absolute;
  top: 0;
  right: 0;
  width: 45%;
  height: 45%;
  max-width: 0.75rem;
  max-height: 0.75rem;
  fill: var(--bad);
  pointer-events: none;
}

.cal-conflict .edge {
  fill: none;
  stroke: var(--ground);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.cal-swatch.conflict {
  position: relative;
  overflow: hidden;
  background: var(--raised);
}

.cal-swatch.conflict .cal-conflict {
  width: 70%;
  height: 70%;
}

/* ── statistics ─────────────────────────────────────────────────────────── */

/* The statistics fit the window: the filters stay put, and only the table scrolls, under its
   header and above its total. A window too short for that scrolls as the other pages do. */
@media (height > 30rem) {
  body.fills {
    height: 100vh;
    height: 100dvh;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  body.fills main {
    flex: 1;
    min-height: 0;
    width: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    padding-bottom: 1rem;
  }

  body.fills .fills-rest {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  body.fills .table.scroll {
    flex: 0 1 auto;
    min-height: 8rem;
    max-height: none;
  }
}

/* Fixed widths, so the columns hold still while rows come and go as the table scrolls. */
table.statistics {
  table-layout: fixed;
}

table.statistics th,
table.statistics td {
  vertical-align: middle;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* One height for every row: the rows out of view are stood in for by their number times it. */
table.statistics tbody tr.line td {
  height: 2rem;
  padding-top: 0;
  padding-bottom: 0;
  box-sizing: border-box;
}

table.statistics tr.pad td {
  padding: 0;
  border: 0;
}

table.statistics .bar {
  min-width: 0;
}

table.statistics th {
  cursor: grab;
}

table.statistics th .head {
  display: inline-flex;
  gap: 0.3rem;
}

.table.scroll.loading table {
  opacity: 0.5;
  transition: opacity 0.2s 0.2s;
}

table.statistics th.dragging {
  opacity: 0.4;
}

/* Where a dragged column goes. The header keeps its bottom line, which is a shadow too. */
table.statistics th.drop-before {
  box-shadow:
    inset 3px 0 0 var(--accent),
    inset 0 -1px 0 var(--line);
}

table.statistics th.drop-after {
  box-shadow:
    inset -3px 0 0 var(--accent),
    inset 0 -1px 0 var(--line);
}

/* The total stays at the bottom of the scroller, as the header stays at its top. Its line is a
   shadow for the same reason as the header's. */
table.statistics tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 1;
  background: var(--surface);
  font-weight: 600;
  border-bottom: 0;
  box-shadow: inset 0 2px 0 var(--line);
}

table.statistics .period {
  display: inline-flex;
  gap: 0.4rem;
}

/* Which layers a view adds up: recorded data, and the timeframes of memory ticked. */
details.filter {
  position: relative;
}

details.filter > summary {
  cursor: pointer;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--surface);
  white-space: nowrap;
}

details.filter .filter-list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.25rem);
  right: 0;
  min-width: 18rem;
  display: grid;
  gap: 0.35rem;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--raised);
  box-shadow: var(--shadow);
}

details.filter .filter-list .hint {
  margin: 0.25rem 0 0;
}

/* ── the days ───────────────────────────────────────────────────────────── */

.revisits {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin-bottom: 1rem;
}
