/* Sidebar */
:root {
  --sidebar-inline-space: max(calc(var(--inline-space) * 1.5), 1vw);
  --sidebar-switch-height: calc(var(--btn-size) + var(--block-space) * 2 + 1px);
  /* Room below the bottom bars' controls, so they sit level with the composer's instead of 8px from a
     phone's bottom edge, where the display's rounded corners clip them. Both bars already carry the
     same --block-space-half — the composer's own padding, ours from centring a button in the band — so
     the difference is exactly one --block-space, which the composer gets from the .margin-block on its
     form in composer_form_tag, app/helpers/rooms_helper.rb. Keep the two in step.

     Zero beyond this breakpoint, where the band is --footer-height and the two already line up. */
  --sidebar-bottom-clearance: var(--block-space);

  @media (min-width: 100ch) {
    --sidebar-bottom-clearance: 0px;
  }
}

/* On body, not :root: the wide value reads --footer-height, which layout.css declares on body, and a
   var() inside a custom property resolves against the element the declaration matched — on :root it
   would be invalid at computed-value time. */
body {
  --sidebar-filter-height: calc(var(--btn-size) + var(--block-space));

  @media (min-width: 100ch) {
    --sidebar-filter-height: var(--footer-height);
  }
}

.sidebar__container {
  block-size: 100dvh;
  max-block-size: 100dvh;
  padding-block-end: calc(var(--sidebar-filter-height) + var(--sidebar-bottom-clearance));
  /* The switch is sticky, so scrolling an item into view would otherwise park it underneath. */
  scroll-padding-block-start: var(--sidebar-switch-height);

  /* Two stacked rows below 21em — see .sidebar__filter. */
  @media (max-width: 21em) {
    padding-block-end: calc(var(--sidebar-filter-height) * 2 + var(--sidebar-bottom-clearance));
  }
}

/* Rendered at body level, a sibling of #sidebar rather than a child: #sidebar carries
   backdrop-filter, which makes it the containing block for every fixed descendant, so
   inset-inline-start: 0 would resolve to the sidebar's left edge rather than the viewport's. */
.sidebar__tools {
  align-items: center;
  /* Taller by the clearance, with it as padding: the fill still reaches the screen edge while the
     buttons sit above it. */
  block-size: calc(var(--sidebar-filter-height) + var(--sidebar-bottom-clearance));
  inset: auto 0 0 0;
  padding-block-end: var(--sidebar-bottom-clearance);
  padding-inline: var(--sidebar-inline-space);
  /* Full-bleed over .sidebar__filter, the way #nav is over #main-content: only the buttons take
     events, via .btn's pointer-events: auto.

     Sitting at the band's leading edge puts these two ahead of the field visually but after it in the
     DOM, so focus order within this one row does not match reading order. The four controls live in
     two different flex containers, so `order` cannot reach across them; the alternative was moving
     these to the trailing edge, which reads worse. */
  pointer-events: none;
  position: fixed;
  transform: translate(100%);
  transition: transform 300ms ease;
  z-index: 4;

  /* Slides with the drawer. Sitting outside #sidebar, it no longer inherits that transform. */
  #sidebar.open ~ & {
    transform: translate(0);
  }

  /* Its own row below 21em rather than an overlay, so it needs the fill it was borrowing from
     .sidebar__filter — without one the list scrolls visibly through it — and it should swallow taps
     instead of passing them to a list nobody can see. */
  @media (max-width: 21em) {
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    background-color: oklch(var(--lch-white) / 0.92);
    pointer-events: auto;
  }

  /* No padding-inline: the buttons are a fixed 2.65em in a 5vw column, so insetting leaves a content
     box narrower than they are below ~1400px, and they overflow it off-centre. */
  @media (min-width: 100ch) {
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    block-size: auto;
    flex-direction: column;
    inline-size: 5vw;
    inset: auto auto var(--footer-height) 0;
    padding-block: var(--block-space);
    padding-inline: 0;
    position: absolute;
    transform: none;
  }
}

.sidebar__toggle {
  inset-block-start: var(--block-space-half);
  inset-inline-start: calc((var(--btn-size) + max(var(--inline-space), 1vw)) * -1);
  position: absolute;
  transition:
    inset-inline-start 300ms ease,
    border-color 300ms ease,
    background-color 300ms ease;
  z-index: 5;

  #sidebar:where(:not(.open):has(.unread)) & {
    &::after {
      --size: 1em;

      aspect-ratio: 1;
      background-color: var(--color-negative);
      block-size: var(--size);
      border-radius: calc(var(--size) * 2);
      content: "";
      flex-shrink: 0;
      inline-size: var(--size);
      inset-block-start: calc(var(--size) / -4);
      inset-inline-end: calc(var(--size) / -4);
      position: absolute;
    }
  }

  @media (min-width: 100ch) {
    display: none;
    inset-block-start: var(--block-space);
  }

  .open & {
    inset-block-start: var(--block-space);

    @media (max-width: 100ch) {
      inset-inline-start: var(--sidebar-inline-space);

      & img {
        filter: invert(0);
      }

      @media (prefers-color-scheme: dark) {
        & img {
          filter: invert(100%);
        }
      }
    }
  }
}

/* Direct messsages */
.directs {
  --btn-border-color: var(--color-border-darker);
  --column-gap: calc(var(--inline-space) / 1.5);

  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  display: grid;
  grid-auto-columns: minmax(auto, max-content);
  grid-auto-flow: column;
  inset: 0 0 auto 0;
  justify-content: start;
  overscroll-behavior: auto;
  margin-block-end: var(--block-space-half);
  padding-block: var(--block-space) var(--block-space-half);
  padding-inline: var(--sidebar-inline-space) calc(var(--sidebar-inline-space) + var(--column-gap));
  position: sticky;
  z-index: 3;

  .open & {
    @media (max-width: 100ch) {
      -webkit-mask-image: linear-gradient(to left, oklch(0% 0 0 / 1) 85%, oklch(0% 0 0 / 0) 99%);
      mask-image: linear-gradient(to left, oklch(0% 0 0 / 1) 85%, oklch(0% 0 0 / 0) 99%);
      padding-inline-start: calc(var(--btn-size) + var(--sidebar-inline-space) + (var(--column-gap) * 2.5));
    }
  }

  &.directs--new {
    --column-gap: var(--inline-space);
    --row-gap: 2px;

    -webkit-mask-image: none;
    mask-image: none;
    padding-inline-end: var(--sidebar-inline-space);
  }
}

.directs--edit  {
  display: grid;
  gap: var(--inline-space);
  grid-template-columns: repeat(auto-fit, minmax(33%, 1fr));
  grid-template-rows: min-content;
  place-items: center;

  .member {
    aspect-ratio: 1;
    block-size: auto;
    inline-size: 100%;
    margin-inline: auto;
    place-content: center;
  }
}

.direct {
  border-radius: 0.3em;
  box-shadow: none;
  color: var(--color-text);
  display: grid;
  justify-items: center;
  position: relative;
  text-decoration: none;

  .avatar,
  .avatar__group {
    margin-inline: calc(var(--inline-space) / 1.5);

    .avatar {
      margin-inline: 0;
    }
  }

  @media (any-hover: hover) {
    &:where(:not(:active):hover) .avatar {
      filter: brightness(0.7);
    }
  }

  &:focus-within,
  &:where(:not(:active)):focus-visible {
    outline: 0;
  }
}

.direct__author {
  --column-gap: 0.3ch;

  margin-block-start: 0.1em;

  .unread & {
    font-weight: 800;
    position: relative;

    &::after {
      --size: 0.6em;

      aspect-ratio: 1;
      background-color: var(--color-text);
      block-size: var(--size);
      border-radius: calc(var(--size) * 2);
      content: "";
      flex-shrink: 0;
      inline-size: var(--size);
      position: absolute;
      inset: 1.3em auto auto 50%;
      transform: translate(-50%);
    }
  }
}

/* Rooms */
.rooms {
  --column-gap: 0.5em;
  --row-gap: 0.5em;

  padding-inline: var(--sidebar-inline-space);
}

/* One list shows at a time, but both stay in the DOM. Hiding rather than not rendering is
   deliberate: badge-dot counts unread Stimulus targets to set the app badge, and rooms-list only
   marks a room unread when it can find its element, so a list that is absent undercounts the badge
   and drops incoming unread events. Both look up nodes rather than checking visibility, so
   display: none costs nothing. */
.sidebar__views {
  padding-block: var(--block-space);

  &[data-sidebar-view="people"] .conversations { display: none; }
  &:not([data-sidebar-view="people"]) .people { display: none; }

  &[data-sidebar-view="rooms"] #direct_rooms { display: none; }

  &[data-sidebar-view="recent"]:not(.sidebar__filtering) {
    /* Unread is exempt at any age, so rooms-list adding .unread reveals a quiet room without a
       reload. Scoped to .conversations: the directory's cells carry .direct with no marker of their
       own, and an unscoped rule hides every person. */
    .conversations .room:not([data-recently-active]):not(.unread),
    .conversations .direct:not([data-recently-active]):not(.unread) {
      display: none;
    }

    /* Shown only with something on both sides. Testing for a child rather than :not(:empty): the
       pings container is always rendered, and everything in it may be hidden by the window. */
    .conversations:has(.direct[data-recently-active], .direct.unread):has(.room[data-recently-active], .room.unread) .separator {
      display: block;
    }
  }

  .separator {
    display: none;
  }
}

/* Searching shows matches from every list, whichever view you are on. Pings step aside: their people
   are all in the directory, so leaving them in shows every match twice. */
.sidebar__views.sidebar__filtering {
  .conversations,
  .people {
    display: flex;
  }

  #direct_rooms {
    display: none;
  }

  .direct:not(.sidebar__match),
  .room:not(.sidebar__match) {
    display: none;
  }

  /* button_to wraps each directory cell in a form, so the grid item is the form, not the .direct
     inside it — hiding only the button leaves the form holding an empty cell. */
  .directs--edit > *:not(:has(.sidebar__match)) {
    display: none;
  }

  &:has(.people .sidebar__match):has(.conversations .room.sidebar__match) .separator {
    display: block;
  }
}

/* One gutter for the three lists, so the views do not shift sideways as you switch. .rooms stays
   because the search page reuses it. */
.rooms,
.conversations,
.people {
  --column-gap: 0.5em;
  --row-gap: 0.5em;

  padding-inline: var(--sidebar-inline-space);
}

/* .directs--edit is shared with the direct-room editor, where cells are large and three 33% tracks
   plus gaps collapse to two columns. Scoped here so that screen keeps its own density. The row gap
   has to clear the unread dot, which hangs below a person's name.

   Fluid avatars with their inline margin dropped, the way .member is fluid in the same component: a
   fixed avatar plus that margin gave every cell a 63px floor, which overflowed a narrow sidebar and
   clipped the last person in each row. */
.people .directs--edit,
.conversations .directs--edit {
  gap: calc(var(--block-space) * 1.15) var(--inline-space);
  /* Sized tracks rather than a fixed count, because a fixed count cannot hold this gap: the sidebar is
     26vw, so four stretched tracks put 11px between faces at 1024 and 106px at 2560 — the gap the owner
     reported was diverging, not mistuned. Letting the count follow the width holds it at the column gap
     instead, at the cost of a face that breathes between 46 and 58px and a row that reflows as the
     window is dragged.

     auto-fill, not the auto-fit the base component uses: hidden cells are not placed, so auto-fit
     collapses the empty tracks and a single match in the filter would stretch to 300px. */
  grid-template-columns: repeat(auto-fill, minmax(2.9rem, 1fr));

  /* Stretched, so an avatar fills its track rather than sitting centred inside it. */
  justify-items: stretch;

  /* A larger floor below the drawer's own breakpoint, where a face is a thumb target rather than
     something you click. Fewer per row as a result, which is the trade worth making on the axis with
     the least room. */
  @media (max-width: 100ch) {
    grid-template-columns: repeat(auto-fill, minmax(3.75rem, 1fr));
  }

  /* Both kinds of face, or they stop matching. A group ping's box is a fixed 5ch, so once single
     avatars went fluid the two diverged — by 13px on a phone — and broke the row's rhythm. The cluster
     inside it stays as upstream sizes it, in ch. */
  .direct .avatar,
  .direct .avatar__group {
    inline-size: 100%;
    margin-inline: 0;

    img {
      aspect-ratio: 1;
      block-size: auto;
      inline-size: 100%;
    }
  }

  .direct .avatar__group {
    aspect-ratio: 1;
    block-size: auto;
  }
}

/* Switch */
.sidebar__switch {
  /* A fill on top of the blur: blur alone leaves pills scrolling underneath legible. */
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  background-color: oklch(var(--lch-white) / 0.92);
  border-block-end: 1px solid var(--color-border-darker);
  inset-block-start: 0;
  padding-block: var(--block-space);
  padding-inline: var(--sidebar-inline-space);
  position: sticky;
  z-index: 3;

  /* The drawer's toggle sits over the scroller when open, so the band clears it the way .directs
     does — otherwise it covers the left third of the first pill and eats its clicks. */
  .open & {
    @media (max-width: 100ch) {
      padding-inline-start: calc(var(--btn-size) + var(--sidebar-inline-space) + var(--inline-space-half));
    }
  }
}

.sidebar__view {
  /* Three labels across a 26vw column: .btn's 1.1em inline padding pushes the row past the sidebar's
     edge at the low end of this breakpoint and clips the last one. */
  --btn-padding: 0.5em 0.6em;

  flex: 1;
  min-inline-size: 0;

  /* Filled dark is the app's current-thing treatment, as on .room--current and the search chip. Both
     of those are inert labels, so neither says what hover should do; this is a link, so it follows
     .room.unread and keeps the ring, pointing --hover-color at the state colour. btn--reversed's
     hover is a brightness dim, which reads as "about to fire" on something already selected. */
  &[aria-current] {
    --btn-background: var(--color-text);
    --btn-border-color: var(--color-text);
    --btn-color: var(--color-text-reversed);
    --hover-color: var(--color-text);
  }
}

/* Its own modifier rather than reusing .unread: rooms-list, badge-dot and #sidebar:has(.unread) all
   operate on that class, so a switch pill carrying it would raise a phantom indicator. */
.sidebar__view--unread {
  --btn-border-color: var(--color-text);
  --hover-color: var(--color-text);

  &:not(:hover) {
    box-shadow: 0 0 0 1px var(--color-text);
  }
}

/* Fixed at the foot of the drawer rather than sticky inside the scroller, which leaves a strip beneath
   the bar where the list shows through. On a phone .sidebar__tools overlays the leading end of this
   band, so reserve its width — the gutter, the two buttons, and the gaps either side of them. On
   desktop the tools move to the viewport's left gutter and the band becomes --footer-height, lining
   the field up with the composer's across the two columns. */
.sidebar__filter {
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  background-color: oklch(var(--lch-white) / 0.92);
  block-size: calc(var(--sidebar-filter-height) + var(--sidebar-bottom-clearance));
  inset: auto 0 0 0;
  padding-block: 0 var(--sidebar-bottom-clearance);
  padding-inline: var(--sidebar-inline-space);
  /* Reserve the overlaid tools, the way .directs and .sidebar__switch reserve the drawer's toggle:
     their gutter, the two buttons, the gap between them and one gap after. --column-gap, not
     --inline-space, because that is what .gap resolves the tools' own gap through. */
  padding-inline-start: calc(var(--btn-size) * 2 + var(--sidebar-inline-space) + var(--column-gap, var(--inline-space)) * 2);
  position: fixed;
  z-index: 4;

  /* Every term of that reserve is font-relative while the row is viewport-width, so below roughly a
     phone's width — or at any width once text is scaled up — it eats the field entirely. There the
     tools take their own row again: vertical space is the cheaper thing to spend. */
  @media (max-width: 21em) {
    block-size: var(--sidebar-filter-height);
    inset-block-end: calc(var(--sidebar-filter-height) + var(--sidebar-bottom-clearance));
    padding-block-end: 0;
    padding-inline-start: var(--sidebar-inline-space);
  }

  @media (min-width: 100ch) {
    padding-inline-start: var(--sidebar-inline-space);
  }
}

.room {
  background-color: var(--color-text-reversed);
  color: var(--color-text);
  font-weight: normal;
  justify-content: start;
  margin-inline-end: auto;
  max-inline-size: 100%;

  .searches-list & {
    border-radius: 0.5em;
  }

  &.unread {
    --btn-border-color: var(--color-text);
    --hover-color: var(--color-text);

    font-weight: 600;

    &:not(:hover) {
      box-shadow: 0 0 0 1px var(--color-text);
    }
  }
}

.room--current {
  --btn-border-radius: 0.5em;
  --hover-filter: none;
  --num-buttons: 1;

  min-block-size: var(--btn-size);

  .room__contents {
    .account-has-logo & {
      --num-buttons: 2;
    }

    max-inline-size: calc(100dvw - (var(--btn-size) * var(--num-buttons)) - (var(--btn-size) + max(var(--inline-space), 1vw)) - ((var(--inline-space) + 1.8em) * 2) - var(--sidebar-width) - var(--inline-space) * var(--num-buttons));

    @media (max-width: 100ch) {
      --num-buttons: 2;

      .account-has-logo & {
        --num-buttons: 3;
      }
    }
  }
}

/* Searches */
.searches {
  #nav {
    align-items: start;
    column-gap: 0;
    padding-inline-end: 0;
  }

  .rooms {
    padding-block-start: var(--block-space);
  }

  .message--formatted .message__room {
    display: block;
    font-size: 0.8rem;
    font-weight: 400;
    overflow: hidden;
    text-overflow: ellipsis;
    visibility: visible;
    white-space: nowrap;

    &::before {
      content: "→";
    }

    .message--me & {
      &::after {
        content: "←";
      }

      &::before {
        content: "";
      }
    }
  }

  .message__actions {
    display: none !important;
    visibility: hidden !important;
  }
}

.searches__recents {
  --mask: linear-gradient(to left, oklch(0% 0 0 / 1) 97%, oklch(0% 0 0 / 0) 99%);

  -webkit-mask-image: var(--mask);
  display: none;
  mask-image: var(--mask);
  position: relative;

  @media (max-width: 100ch) {
    display: flex;
  }

  .room {
    max-inline-size: 20ch;

    &:first-child {
      margin-inline-start: var(--inline-space);
    }
  }

  .searches__btn {
    margin-inline-end: var(--inline-space-double);
  }
}

.searches__query {
  --btn-border-radius: 0.5em;

  min-block-size: var(--btn-size);
}

.searches__results {
  padding-block-start: var(--navbar-height);
}

.searches__input:required:invalid {
  ~ .searches__reset {
    display: none;
    visibility: hidden;
  }
}
