/* Reset */

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

/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role="list"],
ol[role="list"] {
  list-style: none;
}

/* Set core root defaults */
html:focus-within {
  scroll-behavior: smooth;
}
html {
  scroll-padding-top: 64px;
}

/* Set core body defaults */
body {
  min-height: 100vh;
  text-rendering: optimizeSpeed;
  line-height: 1.5;
}

/* A elements that don't have a class get default styles */
a:not([class]) {
  text-decoration-skip-ink: auto;
}

/* Make images easier to work with */
img,
picture {
  max-width: 100%;
  display: block;
}

/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
  font: inherit;
}

/* Remove all animations, transitions and smooth scroll for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
  html:focus-within {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Colour
   --------------------------------------------------------------------------
   Every colour in this file is a token, and the dark scheme redefines the
   tokens rather than adding a second set of rules. A half-tokenised sheet is
   how a page ends up with dark text on a dark background: one rule that kept
   its literal, in a file where everything else moved.
   ========================================================================== */

:root {
  color-scheme: light dark;

  --sidebar: #39113e;
  --sidebar-hover: #301034;
  --sidebar-ink: #cdc3ce;
  --sidebar-ink-strong: #ffffff;
  --sidebar-ink-quiet: rgba(255, 255, 255, 0.55);
  --sidebar-raised: rgba(255, 255, 255, 0.08);
  --sidebar-raised-hover: rgba(255, 255, 255, 0.16);

  --page: #ffffff;
  --surface: #ffffff;
  --surface-sunken: #faf9fa;
  --surface-raised: #fafafa;
  --ink: rgb(29, 28, 29);
  --ink-strong: #1d1c1d;
  --ink-muted: #616061;
  --line: #e2e2e2;
  --line-strong: #d5d0d6;
  --link: rgb(18, 100, 163);
  --accent: #39113e;

  --on-accent: #ffffff;
  --target: #fff8d4;
  --warn-edge: #d97706;
  --warn-surface: #fff7ed;
  --warn-ink: #7c2d12;

  --shadow: rgba(0, 0, 0, 0.25);
  --scrim: rgba(0, 0, 0, 0.45);
  --lightbox-backdrop: rgba(0, 0, 0, 0.85);

  --highlight-bg: #ffe066;
  --highlight-ink: #1a1a1a;
  --badge-bg: rgba(0, 0, 0, 0.05);
  --badge-ink: var(--ink-muted);
  --badge-reply-bg: rgba(57, 17, 62, 0.08);
  --badge-reply-ink: var(--accent);
}

/* The same archive at night. The sidebar stays the workspace's colour - it is
   how you know where you are - and everything else inverts around it. */
@media (prefers-color-scheme: dark) {
  :root {
    --sidebar: #2b0d2f;
    --sidebar-hover: #3a1440;
    --sidebar-ink: #cdc3ce;

    --page: #16151a;
    --surface: #1d1b22;
    --surface-sunken: #232029;
    --surface-raised: #262330;
    --ink: #e8e6ea;
    --ink-strong: #f5f3f7;
    --ink-muted: #a8a2ad;
    --line: #322e3a;
    --line-strong: #423c4c;
    --link: #9ec5ff;
    --accent: #c9a6d4;

    --on-accent: #16151a;
    --target: #3b3410;
    --warn-edge: #b45309;
    --warn-surface: #2a1c0d;
    --warn-ink: #f5c99b;

    --shadow: rgba(0, 0, 0, 0.6);
    --scrim: rgba(0, 0, 0, 0.6);
    --lightbox-backdrop: rgba(0, 0, 0, 0.92);

    --highlight-bg: #e6b800;
    --highlight-ink: #16151a;
    --badge-bg: rgba(255, 255, 255, 0.08);
    --badge-ink: var(--ink-muted);
    --badge-reply-bg: rgba(201, 166, 212, 0.15);
    --badge-reply-ink: var(--accent);
  }
}

@font-face {
  font-family: "Lato";
  src: url("fonts/Lato-Regular.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: "Lato";
  src: url("fonts/Lato-Bold.ttf") format("truetype");
  font-weight: bold;
  font-style: normal;
}

body,
html {
  font-family: "Lato", sans-serif;
  font-size: 14px;
  color: var(--ink);
  background: var(--page);
}

a {
  color: var(--link);
}

audio,
video {
  max-width: 400px;
}

.messages-list {
  padding-bottom: 20px;
}

.messages-list .avatar {
  height: 36px;
  width: 36px;
  border-radius: 7px;
  margin-right: 10px;
  background: var(--line-strong);
}

.message-gutter {
  display: flex;
  margin: 10px;
  scroll-margin-top: 64px;
}

.message-gutter:target {
  background-color: var(--surface-raised);
  border: 2px solid var(--sidebar);
  padding: 10px;
  border-radius: 5px;
}

.message-gutter div:first-of-type {
  flex-shrink: 0;
}

.message-gutter > .message-gutter {
  /** i.e. replies in thread. Just here to be easily findable */
}

/* ==========================================================================
   The channel entry page (channelId.html)
   --------------------------------------------------------------------------
   One page holding the whole channel, one chunk of pre-rendered messages at a
   time. The static channelId-N.html pages are the same conversation without
   JavaScript; this is the chrome around the list they share.
   ========================================================================== */

/* The page scrolls, not a box, so anchors and the back button behave. The
   browser also tries to keep the reader put when content appears above the
   viewport - channel.js makes that correction itself, so the browser must not
   double it. */
#channel-messages {
  overflow-anchor: none;
}

/* The two edges of the loaded range. When one comes into view the next chunk
   is fetched; while it is being fetched, the edge says so. */
.chunk-sentinel {
  height: 1px;
}

#channel-messages.chunk-loading .chunk-sentinel::before {
  content: "Loading…";
  display: block;
  padding: 0.5rem 0;
  color: var(--ink-muted);
  font-size: 0.85rem;
  text-align: center;
}

.chunk-broken,
.chunk-fallback {
  margin: 1rem 10px;
  padding: 0.75rem 1rem;
  border-left: 4px solid var(--warn-edge);
  background: var(--warn-surface);
  color: var(--warn-ink);
  line-height: 1.5;
}

.sender {
  font-weight: 800;
  margin-right: 10px;
}

.timestamp {
  font-weight: 200;
  font-size: 13px;
  color: var(--ink-muted);
}

.header {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 5;
  background: var(--surface);
  color: var(--ink-muted);
  border-bottom: 1px solid var(--line);
  box-sizing: border-box;
}

.header a {
  color: var(--ink-muted);
}

.header a:active,
.header a.current {
  color: var(--ink-strong);
}

.text {
  overflow-wrap: break-word;
}

.file {
  max-height: 270px;
  margin-right: 10px;
  margin-top: 10px;
  border-radius: 4px;
  border: 1px solid var(--line);
  outline: none;
}

.reaction {
  background-color: var(--line);
  display: inline-block;
  border-radius: 10px;
  font-size: 0.7em;
  padding-left: 6px;
  padding-right: 6px;
  padding-bottom: 4px;
  margin-right: 5px;
  padding-top: 4px;
}

.reaction img {
  height: 16px;
  width: 16px;
  margin-right: 3px;
  vertical-align: middle;
  display: inline-block;
}

.reaction span {
  position: relative;
  top: 1px;
}

#index {
  display: flex;
  align-items: flex-start;
}

#channels {
  background: var(--sidebar);
  width: 250px;
  color: var(--sidebar-ink);
  padding-top: 10px;
  overflow: scroll;
  padding-bottom: 20px;
}

#channels ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

#channels p {
  padding-left: 20px;
}

#channels .section {
  font-weight: 800;
  color: var(--sidebar-ink-strong);
  margin-top: 10px;
}

#channels .section:first-of-type {
  margin-top: 0;
}

#channels a {
  padding: 5px;
  display: block;
  color: var(--sidebar-ink);
  text-decoration: none;
  padding-left: 20px;
  display: flex;
  max-height: 28px;
  white-space: pre;
  text-overflow: ellipsis;
  overflow: hidden;
}

#channels a .avatar {
  height: 20px;
  width: 20px;
  border-radius: 3px;
  margin-right: 10px;
  object-fit: contain;
}

#channels a:hover {
  background: var(--sidebar-hover);
  color: var(--sidebar-ink-strong);
}

#messages {
  flex-grow: 1;
  min-width: 0;
}

#search {
  margin: 10px;
  text-align: center;
}

#search ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0;
}

#search li {
  padding: 10px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-sunken);
  border-radius: 5px;
  width: 100%;
  max-width: 800px;
  text-align: left;
  margin-bottom: 10px;
}

#search a {
  text-decoration: none;
  color: unset;
}

.SearchBox {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}

.Search {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 600px;
  position: relative;
}

.Search input {
  width: 100%;
  padding: 10px;
  padding-right: 40px;
  border: 1px solid var(--line);
  border-radius: 4px;
}

.Search .clear {
  position: absolute;
  right: 5px;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 20px;
  color: var(--ink-muted);
}

.Filters {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

.Filters select {
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: white;
  min-width: 150px;
}
/* Names over the years: one block per person, one row per name they used. */
#names {
  padding: 20px 30px;
  overflow-y: auto;
  height: 100vh;
}

#names .person {
  margin-bottom: 28px;
}

#names .person h2 {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  margin-bottom: 6px;
}

#names .person h2 .avatar {
  width: 24px;
  height: 24px;
  border-radius: 4px;
}

#names table {
  border-collapse: collapse;
  width: 100%;
}

#names td {
  padding: 2px 12px 2px 0;
  vertical-align: top;
}

#names td.timestamp {
  white-space: nowrap;
  width: 1%;
}

/* ---------------------------------------------------------------------------
   Stats and profiles.

   One hue for every chart (--viz-series), because every chart on these pages
   measures the same thing - how many messages - against a different axis. A
   second colour would claim a second measure. The value is validated for >= 3:1
   against both surfaces; the dark block is stepped for the dark surface rather
   than being the light value inverted.
   --------------------------------------------------------------------------- */
:root {
  --viz-surface: var(--surface);
  --viz-series: #4a3aa7;
  --viz-series-soft: #4a3aa71f;
  --viz-axis: var(--line);
  --viz-ink: var(--ink-strong);
  --viz-ink-muted: var(--ink-muted);
}

/* No dark block on purpose. The rest of this archive is a light page with a
   white background and no theme switch, so flipping only the chart tokens on
   prefers-color-scheme paints white text onto a white page - which is exactly
   what it did the first time this was rendered. The charts follow the page. */

#stats,
#profile,
#names {
  padding: 24px 30px 60px;
  overflow-y: auto;
  height: 100vh;
  max-width: 900px;
}

.profile-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 4px;
}

.profile-head .avatar {
  width: 56px;
  height: 56px;
  border-radius: 8px;
}

.profile-head h1 {
  margin: 0;
}

/* Above the fold: the shape of the whole thing in one row. */
.viz-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 10px;
  margin: 18px 0 26px;
}

.viz-tile {
  border: 1px solid var(--viz-axis);
  border-radius: 8px;
  padding: 10px 12px;
}

.viz-tile-value {
  font-size: 22px;
  font-weight: 700;
  color: var(--viz-ink);
  line-height: 1.2;
}

.viz-tile-label {
  font-size: 12px;
  color: var(--viz-ink-muted);
}

.viz-tile-hint {
  font-size: 11px;
  color: var(--viz-ink-muted);
  margin-top: 2px;
}

.viz-figure {
  margin: 0 0 26px;
}

.viz-figure figcaption,
.viz-figure-caption {
  font-weight: 700;
  margin-bottom: 8px;
}

.viz-note {
  font-weight: 400;
  color: var(--viz-ink-muted);
}

/* preserveAspectRatio="none" stretches the marks; the labels are drawn in the
   same space, so they stretch too. A fixed height keeps that distortion small
   and predictable rather than dependent on the window. */
/* Height follows the viewBox rather than being fixed: with a fixed height the
   uniform scaling letterboxes the drawing inside it and leaves dead space above
   and below. Width drives it, the aspect ratio does the rest. */
svg.viz {
  width: 100%;
  height: auto;
  display: block;
}

.viz-mark {
  fill: var(--viz-series);
}

.viz-axis {
  stroke: var(--viz-axis);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.viz-line {
  fill: none;
  stroke: var(--viz-series);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}

.viz-area {
  fill: var(--viz-series-soft);
}

.viz-label {
  fill: var(--viz-ink-muted);
  font-size: 13px;
  font-family: "Lato", sans-serif;
}

/* Rankings: name, bar, number. Direct labels on every row, so the bar is the
   comparison and the number is the fact - neither has to carry both. */
.viz-bars {
  list-style: none;
  margin: 0;
  padding: 0;
}

.viz-bars li {
  display: grid;
  grid-template-columns: minmax(90px, 210px) 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
}

.viz-bars-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.viz-bars-track {
  background: var(--viz-series-soft);
  border-radius: 4px;
  height: 12px;
}

.viz-bars-fill {
  display: block;
  background: var(--viz-series);
  border-radius: 4px;
  height: 100%;
}

.viz-bars-value {
  font-variant-numeric: tabular-nums;
  color: var(--viz-ink-muted);
  white-space: nowrap;
}

.viz-table summary,
.drill summary {
  cursor: pointer;
  color: var(--viz-ink-muted);
  font-size: 12px;
  padding: 4px 0;
}

.drill {
  border-top: 1px solid var(--viz-axis);
  padding: 6px 0;
}

.drill > summary {
  font-size: 15px;
  font-weight: 700;
  color: var(--viz-ink);
}

.drill .count {
  color: var(--viz-ink-muted);
  font-weight: 400;
  font-size: 12px;
  margin-left: 6px;
}

table.timeline {
  border-collapse: collapse;
  width: 100%;
  margin: 6px 0 14px;
}

table.timeline td {
  padding: 3px 12px 3px 0;
  vertical-align: top;
}

table.timeline td.kind,
table.timeline td.timestamp {
  color: var(--viz-ink-muted);
  white-space: nowrap;
  width: 1%;
}

/* Drill-down: year -> month -> day -> hour. Bars are buttons, so they focus
   and respond to the keyboard; the disabled ones are the empty buckets, which
   are shown rather than skipped so a gap looks like a gap. */
.dd-crumbs {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.dd-back {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--viz-series);
  cursor: pointer;
  text-decoration: underline;
}

.dd-sep,
.dd-total {
  color: var(--viz-ink-muted);
}

.dd-here {
  font-weight: 700;
}

.dd-total {
  margin-left: auto;
  font-size: 12px;
}

.dd-chart {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 150px;
  border-bottom: 1px solid var(--viz-axis);
  padding-bottom: 2px;
}

.dd-cell {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: stretch;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  position: relative;
}

.dd-cell:disabled {
  cursor: default;
}

.dd-bar {
  display: block;
  background: var(--viz-series);
  border-radius: 4px 4px 0 0;
  min-height: 1px;
}

.dd-cell:hover:not(:disabled) .dd-bar,
.dd-cell:focus-visible .dd-bar {
  background: var(--viz-ink);
}

.dd-cell-label {
  font-size: 10px;
  color: var(--viz-ink-muted);
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  padding-top: 3px;
}

/* A month of days or a day of hours fits; ten years of months does not, so the
   labels thin out rather than overlapping. */
.dd-chart:has(.dd-cell:nth-child(20)) .dd-cell-label {
  font-size: 9px;
}

.dd-chart:has(.dd-cell:nth-child(29)) .dd-cell:nth-child(even) .dd-cell-label {
  visibility: hidden;
}

/* Reaction rows: the emoji itself, then its name, so a custom one is
   recognisable before it is read. */
.emoji-bars .viz-bars-label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.emoji-mark img {
  width: 18px;
  height: 18px;
  vertical-align: middle;
}

.emoji-mark {
  font-size: 16px;
  line-height: 1;
  min-width: 18px;
}

/* Faces over the years: one picture per change, oldest first. */
.faces {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.faces li {
  width: 64px;
  text-align: center;
}

.faces img {
  width: 64px;
  height: 64px;
  border-radius: 8px;
  object-fit: cover;
  background: var(--viz-series-soft);
  display: block;
}

.faces .timestamp {
  font-size: 10px;
  color: var(--viz-ink-muted);
}

/* The author of a message links to their own page. It should not look like a
   link in the middle of a conversation - the name is the affordance, and the
   underline appears when you are already pointing at it. */
.author-link {
  color: inherit;
  text-decoration: none;
}

.author-link:hover .sender,
.author-link:focus-visible .sender {
  text-decoration: underline;
}

.author-link:focus-visible {
  outline: 2px solid var(--viz-series);
  outline-offset: 2px;
}

/* A custom emoji typed into a message, rather than used as a reaction. Sized
   in em so it grows with the text it sits in - a 128px source at its natural
   size would be a picture with a sentence around it. The small lift is what
   puts it on the same optical line as the letters beside it.

   The search results are the same emoji in the same sentences, so they are
   sized here rather than left to find their own size a thousand lines down. */
.text img.emoji,
#search li img.emoji {
  height: 1.35em;
  width: auto;
  max-width: 4em;
  vertical-align: -0.25em;
  display: inline-block;
}

/* Custom emoji the archive never managed to download. The shortcode is what
   the person typed, and it beats an empty box. */
.emoji-missing {
  color: var(--ink-muted);
  font-size: 0.9em;
}

/* Every message is its own anchor; the timestamp is how you get the link. */
a.timestamp {
  color: inherit;
  text-decoration: none;
}

a.timestamp:hover,
a.timestamp:focus-visible {
  text-decoration: underline;
}

.message-gutter:target {
  background: var(--target);
  scroll-margin-top: 1rem;
}

/* What the archive does not have. Every count on the page is a count of what
   was fetched, and where nothing was fetched that is not the same thing. */
.gap-notice {
  margin: 0 0 1.5rem;
  padding: 0.75rem 1rem;
  border-left: 4px solid var(--warn-edge);
  background: var(--warn-surface);
  color: var(--warn-ink);
  line-height: 1.5;
  max-width: 60rem;
}

.gap-notice .gap-range {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.gap-divider {
  margin: 1.5rem 0;
  padding: 0.5rem 0.75rem;
  border-top: 1px dashed var(--warn-edge);
  border-bottom: 1px dashed var(--warn-edge);
  background: var(--warn-surface);
  color: var(--warn-ink);
  font-size: 0.85rem;
  text-align: center;
}

/* When these pages were made. A static archive looks identical whether it was
   written last night or abandoned in March. */
.generated {
  padding: 0.75rem 1rem;
  color: var(--ink-muted);
  font-size: 0.8rem;
}

#index .generated {
  color: var(--sidebar-ink-quiet);
}

/* A file Slack itself no longer has. Better than a link that cannot work. */
.file-gone {
  display: inline-block;
  padding: 0.25rem 0.5rem;
  border: 1px dashed var(--ink-muted);
  border-radius: 4px;
  color: var(--ink-muted);
  font-size: 0.85rem;
}

/* ==========================================================================
   Layout, on a screen of any width
   --------------------------------------------------------------------------
   The archive was built as a desktop frameset: a 250px sidebar and an iframe
   sized `calc(100vw - 250px)`, which on a phone is a sidebar taking most of
   the screen and a conversation squeezed into what is left. People read Slack
   on phones; they will read this on phones.
   ========================================================================== */

/* The sidebar keeps its own place while the page scrolls past it; the page
   scrolls, not a box inside it, so anchors and the back button behave. */
#channels {
  position: sticky;
  top: 0;
  height: 100dvh;
}

#channels {
  flex-shrink: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

#messages {
  min-width: 0;
}

/* The drawer control. A checkbox rather than a script: the sidebar has to work
   in a page that may be opened from a file:// copy years from now. */
.nav-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.nav-open,
.nav-backdrop {
  display: none;
}

@media (max-width: 860px) {
  .nav-open {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    position: fixed;
    top: 10px;
    left: 10px;
    /* Above the drawer, so the same control that opens it closes it. */
    z-index: 60;
    padding: 8px 14px;
    border-radius: 999px;
    background: var(--sidebar);
    color: var(--sidebar-ink-strong);
    font-weight: 800;
    cursor: pointer;
    box-shadow: 0 2px 10px var(--shadow);
  }

  #index > #channels {
    position: fixed;
    height: auto;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 50;
    width: min(82vw, 300px);
    padding-top: 56px;
    transform: translateX(-100%);
    transition: transform 0.18s ease-out;
    box-shadow: 0 0 40px var(--shadow);
  }

  #index > .nav-toggle:checked ~ #channels {
    transform: none;
  }

  #index > .nav-toggle:checked ~ .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 45;
    background: var(--scrim);
  }

  .nav-toggle:focus-visible ~ .nav-open {
    outline: 2px solid var(--sidebar-ink-strong);
    outline-offset: 2px;
  }

  /* Room for the button, which floats over whatever is in the frame. The
     search page has no sidebar and no button, so it is not in this list. */
  .page,
  #stats,
  #profile,
  #names {
    padding-top: 52px;
  }
}

/* The conversation itself, narrow. */
@media (max-width: 600px) {
  body,
  html {
    font-size: 15px;
  }

  .message-gutter {
    margin: 10px 8px;
  }

  .messages-list .avatar {
    height: 30px;
    width: 30px;
    margin-right: 8px;
  }

  audio,
  video,
  .files img {
    max-width: 100%;
  }

  .viz-tiles {
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  }
}

/* Wide content inside a narrow screen scrolls itself rather than the page. */
table.timeline,
#names table,
#stats table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
}

/* ==========================================================================
   The search page
   --------------------------------------------------------------------------
   It centred everything: the heading, the filters, and every result as its own
   shrink-to-fit box on the middle line. A list of a hundred results became a
   hundred boxes of a hundred different widths, none of whose first words line
   up - so the eye has to find the start of every line before it can read it.
   A list of results is a list. It starts at the left.
   ========================================================================== */

#search {
  margin: 0;
  padding: 0 16px 48px;
  text-align: left;
}

.Header {
  position: sticky;
  top: 0;
  z-index: 10;
  margin: 0 -16px 20px;
  padding: 18px 16px 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.Header h1 {
  max-width: 900px;
  margin: 0 auto 12px;
  font-size: 20px;
  letter-spacing: -0.01em;
}

.SearchBox {
  max-width: 900px;
  margin: 0 auto;
  align-items: stretch;
  gap: 8px;
}

.Search {
  max-width: none;
}

.Search input {
  padding: 12px 40px 12px 14px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
}

.Search input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.Filters {
  justify-content: flex-start;
}

.Filters select,
.Filters input[type="date"] {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  max-width: 100%;
  font: inherit;
}

.ResetFilters {
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--link);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  transition: background 0.15s ease;
}

.ResetFilters:hover {
  background: var(--surface-sunken);
}

.ResultsMeta {
  max-width: 900px;
  margin: 0 auto 12px;
  color: var(--ink-muted);
  font-size: 13px;
  text-align: left;
}

.SearchStatus {
  max-width: 900px;
  margin: 20px auto;
  color: var(--ink-muted);
}

mark.search-highlight {
  background: var(--highlight-bg);
  color: var(--highlight-ink);
  padding: 1px 3px;
  border-radius: 3px;
  font-weight: 500;
}

.ThreadBadge {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  margin: 0 2px;
  vertical-align: middle;
}

.ThreadBadge.ReplyBadge {
  background: var(--badge-reply-bg);
  color: var(--badge-reply-ink);
}

.ThreadBadge.RootBadge {
  background: var(--badge-bg);
  color: var(--badge-ink);
}

/* "From" and "To" beside their pickers. A bare date box says nothing about
   which end of the range it is, and two of them side by side say less. */
.DateFilter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-muted);
  font-size: 13px;
}

/* Safari paints the picker's own indicator dark on dark; this is the one
   place the archive has to say which scheme its inputs are drawn in. */
.Filters input[type="date"] {
  color-scheme: light dark;
}

#search ul {
  display: block;
  max-width: 900px;
  margin: 0 auto;
  padding: 0;
}

#search li {
  width: auto;
  max-width: none;
  margin: 0 0 8px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-radius: 10px;
  text-align: left;
}

#search a {
  display: block;
}

#search a:hover li,
#search a:focus-visible li {
  background: var(--surface-sunken);
  border-color: var(--line-strong);
}

.SearchShortcut {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 4px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  color: var(--ink-muted);
  background: var(--surface-sunken);
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  pointer-events: none;
}

.SearchSummary {
  max-width: 900px;
  margin: 12px auto;
  color: var(--ink-muted);
  font-size: 13px;
}

.SearchSummary.empty {
  text-align: center;
  padding: 36px 0;
  font-size: 15px;
}

.SearchActionLink {
  background: none;
  border: none;
  color: var(--link);
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
  font-size: inherit;
  font-family: inherit;
}

.SearchActionLink:hover {
  color: var(--ink-strong);
}

.MessageMeta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  color: var(--ink-muted);
  font-size: 12px;
  margin-bottom: 4px;
}

#search li .Channel {
  font-weight: 800;
  color: var(--accent);
}

#search li .MetaSeparator {
  color: var(--ink-muted);
  opacity: 0.5;
}

#search li p:last-child,
#search li .MessageBody {
  overflow-wrap: anywhere;
}

@media (max-width: 600px) {
  .Filters {
    flex-direction: column;
  }

  .Filters select,
  .Filters .DateFilter {
    width: 100%;
  }

  /* The label keeps its width and the picker takes the rest, so the two rows
     line up rather than each finding its own edge. */
  .DateFilter span {
    min-width: 3em;
  }

  .DateFilter input[type="date"] {
    flex: 1;
  }
}

/* Messages / Media - the search page is really two browsers over the same
   database, and this is the whole difference between them. */
.ModeTabs {
  display: flex;
  gap: 4px;
  max-width: 900px;
  margin: 0 auto 4px;
  padding: 0 16px;
}

.ModeTab {
  padding: 6px 14px;
  border: none;
  border-radius: 8px 8px 0 0;
  background: none;
  color: var(--ink-muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.ModeTab.active {
  background: var(--surface);
  color: var(--ink-strong);
  box-shadow: inset 0 -2px 0 var(--accent);
}

.ModeTab:hover:not(.active) {
  color: var(--ink);
}

.RandomButton {
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
}

.RandomButton:hover {
  background: var(--surface-sunken);
}

.MediaGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.MediaItem {
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.MediaItem:hover {
  border-color: var(--line-strong);
  box-shadow: 0 4px 12px var(--shadow);
}

.MediaThumb {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  width: 100%;
  padding: 0;
  margin: 0;
  border: none;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  background: var(--surface-sunken);
  cursor: zoom-in;
  overflow: hidden;
}

.MediaThumb img,
.MediaThumb video {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  transition: transform 0.15s ease;
}

.MediaThumb:hover img,
.MediaThumb:hover video {
  transform: scale(1.02);
}

.MediaZoomHint {
  position: absolute;
  right: 6px;
  bottom: 6px;
  background: var(--scrim);
  color: var(--on-accent);
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 4px;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}

.MediaThumb:hover .MediaZoomHint {
  opacity: 1;
}

.MediaFileBadge {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

.MediaDetails {
  display: flex;
  flex-direction: column;
  padding: 10px;
  gap: 6px;
  flex: 1;
}

.MediaMessage {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

.MediaMessage .emoji {
  width: 16px;
  height: 16px;
  vertical-align: -3px;
}

.MediaMeta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}

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

.MediaSender .Channel {
  font-weight: 600;
}

.MediaActions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: auto;
  padding-top: 6px;
  border-top: 1px solid var(--line);
}

.MediaArchiveLink {
  font-size: 11px;
  font-weight: 600;
  color: var(--link);
  text-decoration: none;
}

.MediaArchiveLink:hover {
  text-decoration: underline;
}

.MediaCopyButton {
  font-size: 11px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface);
  color: var(--ink-muted);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.MediaCopyButton:hover {
  background: var(--surface-sunken);
  color: var(--ink);
}

.MediaCopyButton.copied {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

/* Lightbox Modal for zooming into media */
.MediaModalBackdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--lightbox-backdrop);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  box-sizing: border-box;
}

.MediaModalContent {
  position: relative;
  display: flex;
  flex-direction: column;
  max-width: 94vw;
  max-height: 94vh;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: 0 10px 30px var(--shadow);
  overflow: hidden;
}

.MediaModalHeader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-raised);
  font-size: 13px;
  color: var(--ink-muted);
  gap: 12px;
}

.MediaModalMeta {
  display: flex;
  align-items: center;
  gap: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.MediaModalCounter {
  font-size: 11px;
  color: var(--ink-muted);
}

.MediaModalClose {
  background: transparent;
  border: none;
  font-size: 18px;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
}

.MediaModalClose:hover {
  background: var(--surface-sunken);
  color: var(--ink-strong);
}

.MediaModalBody {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  min-height: 200px;
  max-height: calc(85vh - 120px);
  background: var(--page);
  overflow: hidden;
}

.MediaModalStage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  overflow: auto;
  padding: 12px;
  box-sizing: border-box;
  position: relative;
}

.MediaModalImg {
  max-width: 88vw;
  max-height: calc(85vh - 140px);
  object-fit: contain;
  cursor: zoom-in;
  border-radius: 4px;
}

.MediaModalImg.zoomed {
  max-width: none;
  max-height: none;
  cursor: zoom-out;
}

.MediaModalVideo {
  max-width: 88vw;
  max-height: calc(85vh - 140px);
  border-radius: 4px;
}

.MediaModalFileCard {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 32px;
  color: var(--ink);
}

.MediaFileBadge.large {
  font-size: 24px;
  padding: 8px 16px;
  border-radius: 6px;
  background: var(--surface-sunken);
}

.MediaZoomPill {
  position: absolute;
  bottom: 20px;
  right: 20px;
  background: var(--scrim);
  color: var(--on-accent);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 20px;
  font-size: 12px;
  padding: 6px 12px;
  cursor: pointer;
  box-shadow: 0 2px 6px var(--shadow);
  transition: background 0.15s ease;
}

.MediaZoomPill:hover {
  background: var(--ink-strong);
}

.MediaModalNav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: var(--scrim);
  color: var(--on-accent);
  border: none;
  font-size: 32px;
  line-height: 1;
  width: 44px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 10;
  border-radius: 6px;
  opacity: 0.7;
  transition: opacity 0.15s ease, background 0.15s ease;
}

.MediaModalNav:hover {
  opacity: 1;
  background: var(--ink-strong);
}

.MediaModalNav.prev {
  left: 12px;
}

.MediaModalNav.next {
  right: 12px;
}

.MediaModalFooter {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface-raised);
}

.MediaModalMessage {
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink-strong);
  max-height: 90px;
  overflow-y: auto;
  padding: 2px 0;
  word-break: break-word;
}

.MediaModalMessage .emoji {
  width: 18px;
  height: 18px;
  vertical-align: -3px;
}

.MediaModalActions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.MediaModalActionBtn {
  font-size: 12px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 6px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.MediaModalActionBtn:hover {
  background: var(--surface-sunken);
  border-color: var(--ink-muted);
}

.MediaModalActionBtn.primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

.MediaModalActionBtn.primary:hover {
  opacity: 0.9;
}

.MediaModalActionBtn.copied {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

@media (max-width: 600px) {
  .MediaGrid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
  }

  .MediaModalNav {
    width: 36px;
    height: 48px;
    font-size: 24px;
  }
}

/* Somebody quoting something. Slack shows it as a bar down the left, and this
   archive showed it as a sentence with a stray angle bracket in front. */
.text blockquote {
  margin: 4px 0;
  padding: 2px 0 2px 12px;
  border-left: 4px solid var(--line-strong);
  color: var(--ink-muted);
}

.text blockquote:first-child {
  margin-top: 0;
}

/* The search box in the sidebar. Collapsed it is a line; focused it lifts and
   fills, because a search field that looks like a search field is worth more
   than the 40 pixels it costs. */
.channel-search {
  display: flex;
  gap: 6px;
  align-items: center;
  margin: 0 12px 12px;
  padding: 4px;
  border-radius: 8px;
  background: var(--sidebar-raised);
  transition:
    background 0.15s ease-out,
    box-shadow 0.15s ease-out;
}

.channel-search:focus-within {
  background: var(--sidebar-raised-hover);
  box-shadow: 0 0 0 2px var(--sidebar-raised-hover);
}

.channel-search input {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--sidebar-ink-strong);
  font-size: 13px;
}

.channel-search input::placeholder {
  color: var(--sidebar-ink-quiet);
}

.channel-search input:focus {
  outline: none;
}

.channel-search button {
  border: none;
  background: transparent;
  color: var(--sidebar-ink);
  font-size: 18px;
  line-height: 1;
  padding: 4px 8px;
  cursor: pointer;
  border-radius: 6px;
}

.channel-search button:hover,
.channel-search button:focus-visible {
  color: var(--sidebar-ink-strong);
  background: var(--sidebar-raised);
}

/* The front page: what this is, before you pick a conversation. */
#front {
  max-width: 900px;
  padding: 32px 24px 48px;
}

#front h1 {
  font-size: 28px;
  letter-spacing: -0.02em;
}

#front .front-links {
  margin-top: 24px;
}

/* The sidebar is on every page now, so its own footer sits at the end of it
   rather than at the end of whatever page it is attached to. */
#channels .generated {
  padding: 16px 20px 24px;
  color: var(--sidebar-ink-quiet);
}

/* ==========================================================================
   The channel header, now that a page is a page
   --------------------------------------------------------------------------
   It floated two pieces of metadata to the right of an inline heading, which
   worked in a wide frame and collapses into a paragraph of run-together
   sentences anywhere else: "offtopic Created by Professor Plum on Wednesday
   ... a workspace that has genuinely used Analprint scan Ten years of this
   channel display name 10v PIKKIKSET...".
   ========================================================================== */

.header {
  display: grid;
  grid-template-columns: minmax(auto, max-content) auto 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
  row-gap: 2px;
  column-gap: 8px;
  padding: 6px 16px;
  min-height: 0;
  z-index: 5;
}

.header h1 {
  grid-row: 1;
  grid-column: 1;
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--ink-strong);
  line-height: 1.3;
  white-space: nowrap;
}

.header .created:has(a),
.header .channel-stats-link {
  grid-row: 1;
  grid-column: 2;
  font-size: 11px;
  line-height: 1.3;
  white-space: nowrap;
  align-self: center;
}

.header .created:has(a) a,
.header .channel-stats-link a {
  display: inline-flex;
  align-items: center;
  padding: 1px 7px;
  border-radius: 10px;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  color: var(--ink-muted);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.header .created:has(a) a:hover,
.header .channel-stats-link a:hover {
  color: var(--ink-strong);
  background: var(--surface-raised);
  border-color: var(--ink-muted);
}

.header .topic {
  grid-row: 2;
  grid-column: 1 / 4;
  margin: 0;
  font-size: 12px;
  color: var(--ink-muted);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.header .topic:empty {
  display: none;
}

.header .topic:hover {
  white-space: normal;
  overflow-wrap: anywhere;
}

.header .created:not(:has(a)),
.header .channel-creator {
  grid-row: 2;
  grid-column: 4;
  justify-self: end;
  font-size: 11px;
  color: var(--ink-muted);
  opacity: 0.75;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40vw;
}

.header .created:not(:has(a)):hover,
.header .channel-creator:hover {
  opacity: 1;
}

.header:not(:has(.topic:not(:empty))) .created:not(:has(a)),
.header:not(:has(.topic:not(:empty))) .channel-creator {
  grid-column: 1 / -1;
  justify-self: start;
}

/* Nothing in a message may push the page sideways: not a long URL, not a
   pasted table, not a word in German. */
.text,
.text p,
.text blockquote {
  overflow-wrap: anywhere;
}

.text pre {
  max-width: 100%;
  overflow-x: auto;
}

.page {
  padding-left: 10px;
}

.messages-list {
  max-width: 100%;
}

@media (max-width: 860px) {
  html {
    scroll-padding-top: 96px;
  }

  .header {
    padding-top: 56px;
  }

  .message-gutter {
    scroll-margin-top: 96px;
  }

  .page,
  #stats,
  #profile,
  #names,
  #front {
    padding-top: 0;
  }

  #stats,
  #profile,
  #names,
  #front {
    padding-top: 56px;
  }
}

/* The channel list, the way Slack sets it: the # in its own column so the
   names line up with each other rather than each starting wherever its own
   hash ended, and the rows indented under the section they belong to. */
#channels .section {
  padding-left: 16px;
}

#channels a {
  padding-left: 16px;
  gap: 8px;
  align-items: center;
}

#channels a .lead {
  flex: 0 0 12px;
  text-align: center;
  opacity: 0.6;
}

#channels a > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==========================================================================
   Getting around a channel with ten years in it
   ========================================================================== */

.pagination {
  grid-row: 1;
  grid-column: 4;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  line-height: 1.3;
  margin: 0;
}

.pagination .pages {
  display: flex;
  align-items: center;
  gap: 8px;
}

.pagination .where {
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.pagination .spent {
  color: var(--ink-muted);
  opacity: 0.5;
}

.calendar {
  position: relative;
  flex: 0 0 auto;
}

.calendar summary {
  cursor: pointer;
  color: var(--ink-muted);
  user-select: none;
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--surface-sunken);
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.calendar summary::-webkit-details-marker {
  display: none;
}

.calendar summary::after {
  content: "▾";
  font-size: 9px;
  opacity: 0.7;
}

.calendar summary:hover,
.calendar[open] summary {
  color: var(--ink-strong);
  border-color: var(--ink-muted);
  background: var(--surface-raised);
}

.calendar-years {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  max-height: 50vh;
  overflow-y: auto;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 6px 24px var(--shadow);
  z-index: 100;
  width: max-content;
  max-width: min(90vw, 420px);
}

.calendar-year {
  display: flex;
  gap: 10px;
  align-items: baseline;
  padding: 2px 0;
}

.calendar-label {
  flex: 0 0 3.2em;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.calendar-months {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 4px;
}

.calendar-months a {
  padding: 2px 6px;
  border-radius: 4px;
  text-decoration: none;
  color: var(--link);
}

.calendar-months a:hover,
.calendar-months a:focus-visible {
  background: var(--surface-sunken);
}

.calendar-months a.current {
  background: var(--accent);
  color: var(--on-accent);
}

/* The header on a phone: the channel and where you are, and everything else
   folded away. It was taking a third of the screen before a single message. */
@media (max-width: 600px) {
  .header {
    grid-template-columns: 1fr auto;
    row-gap: 4px;
    padding: 56px 12px 6px;
  }

  .header .created:not(:has(a)),
  .header .channel-creator,
  .header .topic {
    display: none;
  }

  .header .created:has(a),
  .header .channel-stats-link {
    grid-row: 2;
    grid-column: 1 / -1;
    justify-self: start;
  }

  .header h1 {
    grid-row: 1;
    grid-column: 1;
    font-size: 15px;
  }

  .header .pagination {
    grid-row: 1;
    grid-column: 2;
    font-size: 12px;
  }

  .messages-list .timestamp {
    font-size: 11px;
  }
}

/* A month with nothing in it, drawn rather than omitted. Hovering says which
   kind of nothing: a quiet channel, or a stretch the archive never got. */
.calendar-months .empty {
  padding: 2px 6px;
  color: var(--ink-muted);
  opacity: 0.45;
  cursor: help;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

/* What was probably said while nobody was archiving. Dotted, because it is an
   estimate; banded, because the band is how much the surrounding years
   disagree; and in no total anywhere on the page. */
.viz-estimate {
  fill: none;
  stroke: var(--viz-series);
  stroke-width: 2;
  stroke-dasharray: 5 4;
  opacity: 0.75;
}

.viz-estimate-band {
  fill: var(--viz-series);
  opacity: 0.12;
}

/* The estimate on a bar: hollow, because it was not counted, with a whisker
   for the range the surrounding years disagree over. */
.viz-estimate-cap {
  fill: var(--viz-series);
  fill-opacity: 0.14;
  stroke: var(--viz-series);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.viz-estimate-whisker {
  stroke: var(--viz-series);
  stroke-width: 1.5;
  opacity: 0.5;
}

/* The control that turns speculation on. */
.speculate {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 12px;
  margin: 0 0 20px;
  font-size: 13px;
}

.speculate label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  color: var(--ink);
}

.speculate-note {
  color: var(--ink-muted);
}

/* While it is on, the numbers that changed say so rather than looking like
   counts. */
body.speculating .viz-tile[data-speculative] {
  border-style: dashed;
}

body.speculating .viz-tile[data-speculative] .viz-tile-value {
  color: var(--viz-series);
}

/* The estimate column in a Numbers table, quieter than the counts beside it. */
.viz-estimated {
  color: var(--ink-muted);
  font-style: italic;
}

/* ==========================================================================
   "What we could have had"
   --------------------------------------------------------------------------
   The toggle is meant to feel like a different reading of the same archive,
   not like a filter. So: a different typeface, numbers that count up to their
   speculative selves, and a page that shifts under you - loudly enough that
   nobody screenshots one of these numbers thinking it is a count.
   ========================================================================== */

.speculate {
  border: 1px dashed var(--line-strong);
  border-radius: 10px;
  padding: 10px 14px;
}

.speculate-label {
  font-weight: 800;
  letter-spacing: 0.02em;
}

body.speculating .speculate {
  border-color: var(--viz-series);
  background: color-mix(in srgb, var(--viz-series) 8%, transparent);
}

body.speculating .speculate-label {
  color: var(--viz-series);
  text-shadow: 0 0 18px color-mix(in srgb, var(--viz-series) 60%, transparent);
}

/* Every speculative number, while the toggle is on. Georgia against Lato is a
   deliberate mismatch: these are not the same kind of number as the ones the
   page shows by default, and they should not look like them. */
body.speculating [data-speculative] .viz-tile-value,
body.speculating .viz-bars-value[data-speculative] {
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  color: var(--viz-series);
  text-shadow: 0 0 16px color-mix(in srgb, var(--viz-series) 45%, transparent);
  animation: speculate-in 420ms cubic-bezier(0.2, 0.9, 0.2, 1.1);
}

body.speculating .viz-tile[data-speculative] {
  border-style: dashed;
  border-color: var(--viz-series);
  background: color-mix(in srgb, var(--viz-series) 6%, var(--surface));
}

body.speculating .viz-tile[data-speculative] .viz-tile-hint {
  color: var(--viz-series);
  font-style: italic;
}

/* The bars grow into the space the estimate would have filled. */
body.speculating .viz-bars-fill {
  background: repeating-linear-gradient(
    135deg,
    var(--viz-series) 0 8px,
    color-mix(in srgb, var(--viz-series) 55%, transparent) 8px 16px
  );
}

body.speculating .viz-figure,
body.speculating .viz-tiles {
  transition: transform 300ms ease-out;
}

@keyframes speculate-in {
  0% {
    opacity: 0.15;
    transform: translateY(8px) scale(0.94);
    filter: blur(3px);
  }
  60% {
    transform: translateY(-2px) scale(1.03);
    filter: blur(0);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

/* Somebody who asked not to be moved around is not asking for less
   information, only for less movement. */
@media (prefers-reduced-motion: reduce) {
  body.speculating [data-speculative] .viz-tile-value,
  body.speculating .viz-bars-value[data-speculative] {
    animation: none;
  }
}

/* A number that is being redrawn from its own interval should not jump the
   layout around while it does it. */
body.speculating [data-speculative] .viz-tile-value,
body.speculating .viz-bars-value[data-speculative] {
  font-variant-numeric: tabular-nums;
}

/* The dotted line and the hollow caps move between draws rather than snapping,
   so a redraw reads as uncertainty rather than as a glitch. */
body.speculating .viz-estimate,
body.speculating .viz-estimate-cap {
  transition:
    d 900ms ease-in-out,
    y 900ms ease-in-out,
    height 900ms ease-in-out,
    points 900ms ease-in-out;
}

/* A number that is being redrawn must not resize its own box. The tile is
   fixed width and the speculative figure is longer than the count it replaces,
   so without this it wraps to two lines and the layout jumps every second. */
.viz-tile-value {
  white-space: nowrap;
}

body.speculating .viz-tile[data-speculative] .viz-tile-value {
  font-size: clamp(1.1rem, 4.2cqw, 1.6rem);
}

.viz-tile {
  container-type: inline-size;
  overflow: hidden;
}

.viz-bars-value[data-speculative] {
  white-space: nowrap;
}
