.app-sidebar {
  width: 17rem;
  flex-shrink: 0;
  padding: 0 1.25rem;
  background: var(--color-warm-surface);
  border-right: 1px solid var(--color-warm-border);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  position: sticky;
  top: var(--impersonation-strip-height);
  z-index: 30;
  align-self: flex-start;
  height: calc(100vh - var(--impersonation-strip-height));
  transition:
    width 0.15s ease,
    padding 0.15s ease;

  .app-shell--collapsed & {
    width: 4.25rem;
    padding-left: 0.6rem;
    padding-right: 0.6rem;
  }

  .full-bleed {
    text-wrap: nowrap;

    margin-left: -1.25rem;
    margin-right: -1.25rem;
    padding: 0.6rem 1.25rem;

    .app-shell--collapsed & {
      margin-left: -0.6rem;
      margin-right: -0.6rem;
      padding: 0.6rem;
    }
  }

  .tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--control-brand-size);
    height: var(--control-brand-size);
    flex-shrink: 0;
    margin: 0 auto;
    border-radius: var(--radius);
    background: var(--color-warm-accent-bg);
    color: var(--color-warm-accent);
    font-size: 1.1rem;
    font-weight: 700;
    transition: background 0.15s ease;

    &:hover {
      background: var(--color-warm-accent);
      color: var(--color-on-accent);
    }
  }

  .header {
    height: var(--app-header-height);
    /* The header's own rule, so the two read as one line across the
       window (header.css). */
    border-bottom: 1px solid var(--color-warm-border-strong);
    transition:
      background 0.15s ease,
      width 0.15s ease,
      padding 0.15s ease;

    &:hover {
      background: radial-gradient(
        ellipse 60% 1.75rem at 50% 100%,
        rgba(181, 101, 43, 0.1),
        rgba(181, 101, 43, 0) 100%
      );
    }

    .brand {
      display: block;
      height: var(--app-header-height);
      line-height: calc(var(--app-header-height) - 1.25rem);
      text-decoration: none;

      .app-shell--collapsed & {
        font-size: 0;
        height: revert;
        line-height: revert;

        &::after {
          content: attr(data-collapsed);

          display: flex;
          align-items: center;
          justify-content: center;
          width: var(--control-brand-size);
          height: var(--control-brand-size);
          flex-shrink: 0;
          margin: 0 auto;
          border-radius: var(--radius);
          background: var(--color-warm-accent-bg);
          color: var(--color-warm-accent);
          font-size: 1.1rem;
        }

        &:hover {
          &::after {
            background: var(--color-warm-accent);
            color: var(--color-on-accent);
          }
        }
      }
    }
  }

  > .spacer {
    flex: 1;
  }

  .nav {
    display: flex;
    flex-direction: column;

    .link {
      display: flex;
      align-items: center;
      border-radius: var(--radius);
      text-decoration: none;
      transition:
        background 0.12s ease,
        color 0.12s ease,
        var(--press);
    }
  }

  /* The rail is exactly 100vh and its children all size to their content, so
   on a viewport shorter than the rail's content (~556px expanded -- a
   window dragged short, or devtools docked along the bottom) nothing could
   give: .app-sidebar .spacer is already at zero, min-height's auto default
   stopped every other child from shrinking, and .nav-secondary spilled
   out under the bottom edge of the screen. The rail is sticky, so scrolling
   could not reach it either -- the account and its links were simply gone.

   This is the one child that should absorb that: the primary nav is what
   grows as destinations are added, and losing a few pixels off a scrollable
   list of links costs less than losing the account group entirely. min-height
   is what actually unlocks it -- a flex item's auto minimum is its content,
   which is why the rail overflowed instead of shrinking -- and overflow-y
   then gives the shortfall somewhere to go. Above ~556px the spacer still
   takes up all the slack and none of this comes into play.

   Deliberately not overflow on .app-sidebar itself: .app-sidebar-edge and
   .app-sidebar-float-toggle are positioned to overhang its right edge, and
   any non-visible overflow value would clip both of them off. */
  .nav-primary {
    gap: 0.4rem;
    min-height: 0;
    overflow-y: auto;

    .link {
      gap: 0.8rem;
      padding: 0.4rem 0.5rem;
      border: 1px solid transparent;
      background: transparent;
      color: var(--color-warm-heading);
      font-size: 1rem;
      font-weight: 600;

      &:hover {
        background: var(--color-warm-accent-bg);
        border-color: transparent;
      }

      &.active {
        border-color: transparent;
        background: var(--color-warm-accent-bg);
      }

      &:hover,
      &.active {
        .icon {
          background: var(--color-warm-accent);
          color: var(--color-on-accent);
        }
      }

      /* Collapsed, the row *is* its icon chip: the chip keeps the place tier's
         size and fill and the row shrinks to it, so the rail reads as a column
         of the same tiles as the brand and the avatar. It also gives hover and
         active a real box to paint -- previously the row kept its full width
         but lost its padding and fill, so hovering drew a border onto what was
         effectively an empty strip. */
      .app-shell--collapsed & {
        justify-content: center;
        width: var(--control-place-size);
        height: var(--control-place-size);
        margin: 0 auto;
        padding: 0;
        border-color: transparent;
        background: none;
      }

      .icon {
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--control-place-size);
        height: var(--control-place-size);
        flex-shrink: 0;
        border-radius: var(--radius);
        background: var(--color-warm-accent-bg);
        color: var(--color-warm-accent);
        transition:
          background 0.12s ease,
          color 0.12s ease;

        .app-shell--collapsed & {
          width: 100%;
          height: 100%;
        }
      }

      .label {
        .app-shell--collapsed & {
          display: none;
        }
      }
    }
  }

  .nav-secondary {
    gap: 0.1rem;
    border-top: 1px solid var(--color-warm-border-strong);
    transition: background 0.15s ease;
    padding-bottom: 0.6rem;
    /* The containing block for the inline group's exit animation, which
       leaves the flow so the popover avatar can take its place immediately
       -- see .inline--sink-out. */
    position: relative;

    &:hover {
      /* A soft glow rather than a linear band, so it tapers off on all sides
         instead of meeting the element's left/right edges as a hard line.
         Centered at the top border, reaching down about 1.75rem -- roughly
         the identity row's first line -- before fading out completely. */
      background: radial-gradient(
        ellipse 60% 1.75rem at 50% 0,
        rgba(181, 101, 43, 0.1),
        rgba(181, 101, 43, 0) 100%
      );
    }

    .app-shell--collapsed & {
      padding-bottom: 0;
    }

    .inner {
      display: flex;
      flex-direction: column;
      gap: 0.1rem;

      .app-shell--collapsed & {
        display: none;
      }
    }

    /* Expanding the rail: identity + Account/Profile replace the collapsed
       popover avatar. They read as rising up into place rather than unfolding
       downward from the top, since they're pinned to the sidebar's bottom
       edge via .app-sidebar-spacer. Triggered by sidebar_controller.js only
       on the actual expand interaction, not on page load. */
    .inline--rise-in {
      animation: app-nav-secondary-rise-in 0.2s ease;
    }

    .identity {
      display: flex;
      align-items: center;
      gap: 0.8rem;
      text-decoration: none;
      color: inherit;

      .app-shell--collapsed & {
        justify-content: center;
      }

      .avatar {
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--control-place-size);
        height: var(--control-place-size);
        /* Square-with-a-radius, not a circle: the avatar is a place you can go
           (it links to the Account), so it takes the place tier's shape along
           with the brand and the nav icons instead of being the one round
           thing in the rail. */
        border-radius: var(--radius);
        background: var(--color-warm-accent-bg);
        color: var(--color-warm-accent);
        font-weight: 700;
        flex-shrink: 0;
        transition:
          background 0.12s ease,
          color 0.12s ease;
      }

      .identity-text {
        min-width: 0;

        .app-shell--collapsed & {
          display: none;
        }
      }

      /* Single line + ellipsis rather than wrapping -- wrapping let a long name
         or email reflow to extra lines while the sidebar's width was still
         mid-transition (collapsed <-> expanded), which shifted the whole
         identity row up/down as the reflow settled. */
      .account {
        margin: 0;
        font-weight: 600;
        color: var(--color-warm-heading);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .user {
        margin: 0;
        font-size: 0.8rem;
        color: var(--color-warm-muted);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
    }

    .link {
      align-items: baseline;
      gap: 0.7rem;
      min-height: var(--control-tool-size);
      padding: 0.5rem 0.8rem;
      color: var(--color-warm-muted);
      font-size: 0.85rem;

      /* The icon stands on the label's baseline. An svg's own bottom edge
         would: this is the rest of the way, from there to where the stroke
         stops, 2 of the viewBox's 24 units at nav_icon's 18px. */
      .icon svg {
        vertical-align: -1.5px;
      }

      &:hover {
        background: var(--color-warm-accent-bg);
        border-color: transparent;
        color: var(--color-warm-accent);
      }

      &.active {
        color: var(--color-warm-accent);
        font-weight: 600;
      }

      &.centered {
        justify-content: center;
      }

      &.logout {
        display: none;
      }
    }

    .avatar-trigger {
      display: flex;
      justify-content: center;
      cursor: pointer;
      list-style: none;

      &::-webkit-details-marker {
        display: none;
      }
    }
  }

  /* The collapse toggle has two hit targets that highlight each other on
     hover so they read as one control: a floating button anchored near the
     sidebar's top, and the sidebar's entire border with the content area. */

  /* Anchored to .app-sidebar, not .app-shell: the sidebar is sticky, so it's
     both the element the strip must track (one viewport tall, pinned) and a
     stacking context -- from outside it the strip would paint over
     .app-sidebar-float-toggle, which sits inside. left: 100% also means the
     strip follows the rail's collapse for free, riding its width transition. */
  .app-sidebar-edge {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 100%;
    width: 14px;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.1s ease;

    &:hover,
    &:focus-visible {
      background: var(--color-warm-border);
      outline: none;

      .icon {
        opacity: 1;
      }
    }

    .icon {
      color: var(--color-warm-muted);
      opacity: 0;
      transition:
        opacity 0.1s ease,
        transform 0.15s ease;

      .app-shell--collapsed & {
        transform: rotate(180deg);
      }
    }
  }

  .app-sidebar-float-toggle {
    position: absolute;
    top: 1.25rem;
    right: -0.9rem;
    width: var(--control-tool-size);
    height: var(--control-tool-size);
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* The one control that keeps a rest border and surface, and the one
       exempt from its tier's radius: it straddles the rail's edge, so it
       needs to hold its own against two backgrounds, and a squared tile
       sitting on that edge reads as a clipped panel rather than a button.
       It still takes the tool tier's size and its hover. */
    border: 1px solid var(--color-warm-border);
    border-radius: 50%;
    background: var(--color-surface);
    color: var(--color-warm-muted);
    box-shadow: var(--shadow);
    cursor: pointer;
    /* Over .app-sidebar-edge, which follows this in the markup and would
       otherwise paint and hit-test above it -- see the note on that rule. The
       strip is transparent at rest, so the cost shows on hover: its
       --color-warm-border band would run across this button's face. */
    z-index: 1;
    transform: rotate(180deg);
    transition:
      background 0.12s ease,
      color 0.12s ease,
      border-color 0.12s ease,
      var(--press);

    &:hover {
      background: var(--color-warm-accent-bg);
      border-color: transparent;
      color: var(--color-warm-accent);
    }

    .icon {
      transform: rotate(0deg);
      transition: transform 0.15s ease;
    }

    .app-shell--collapsed & {
      transform: none;
    }
  }

  @media (min-width: 40.0625rem) and (max-width: 64rem) {
    .app-sidebar-float-toggle,
    .app-sidebar-edge {
      display: none;
    }
  }

  @media (max-width: 40.0625rem) {
    position: fixed;
    top: var(--impersonation-strip-height);
    left: 0;
    width: 100vw;
    z-index: 100;
    gap: 0.6rem;

    .app-shell--collapsed & {
      visibility: hidden;
    }

    .header {
      &:hover {
        background: none;
      }

      .brand {
        display: none;
      }
    }

    .nav-primary {
      margin-left: -0.5rem;
      margin-right: -0.5rem;
      .link {
        font-size: unset;
      }
    }

    .nav-secondary {
      .link {
        &.logout {
          display: unset;
          text-align: center;
        }
      }
    }

    .app-sidebar-float-toggle {
      visibility: visible;
      position: fixed;
      top: calc(0.625rem + var(--impersonation-strip-height));
      left: 1.25rem;
      z-index: 101;

      width: var(--control-brand-size);
      height: var(--control-brand-size);
      border-radius: var(--radius);
      background: var(--color-warm-accent-bg);
      color: var(--color-warm-accent);
      font-size: 1rem;
      font-weight: 700;
      transition: background 0.15s ease, var(--press);
      border: 0;
      box-shadow: none;

      &:hover {
        background: var(--color-warm-accent);
        color: var(--color-on-accent);
      }
    }

    .app-sidebar-edge {
      display: none;
    }
  }
}

/* :has() rather than a sibling selector -- the edge strip follows the
   button in .app-sidebar, so highlighting the button from the strip would
   need a previous-sibling combinator, which doesn't exist. */
.app-shell:has(.app-sidebar-edge:hover) .app-sidebar-float-toggle,
.app-shell:has(.app-sidebar-edge:focus-visible) .app-sidebar-float-toggle {
  background: var(--color-warm-accent-bg);
  border-color: transparent;
  color: var(--color-warm-accent);
}

.app-shell:has(.app-sidebar-float-toggle:hover) .app-sidebar-edge,
.app-shell:has(.app-sidebar-float-toggle:focus-visible) .app-sidebar-edge {
  background: var(--color-warm-border);
}

.app-shell:has(.app-sidebar-float-toggle:hover) .app-sidebar-edge .icon,
.app-shell:has(.app-sidebar-float-toggle:focus-visible)
  .app-sidebar-edge
  .icon {
  opacity: 1;
}

.app-sidebar-menu {
  display: none;
  position: relative;

  .app-shell--collapsed & {
    display: block;
  }

  /* Beside the avatar, up from its bottom: the menu travels up and to the
     right (popover_menu.css's menu-pop). */
  & .popover-menu-items {
    top: unset;
    bottom: 0.625rem;
    left: calc(100% + 1rem);
    right: auto;
    min-width: 10rem;
    --menu-travel-x: 1;
    --menu-travel-y: -1;
    transform-origin: bottom left;
  }
}

/* Collapsing the rail: the mirror of --rise-in. The group sinks and fades
   away instead of snapping out, so the eye follows the secondary nav
   downwards as it gives way to the popover avatar. It stays painted over
   the collapsed state's display:none for the animation's duration (this
   rule and that one have equal specificity, so source order decides), and
   leaves the flow -- pinned to the group's own bottom edge, which doesn't
   move on collapse -- so the avatar settles into place on the rail's width
   transition rather than waiting for the ghost to clear. 0.15s matches
   that transition. sidebar_controller.js adds the class on the collapse
   interaction and drops it on animationend. */
.app-shell--collapsed .inline--sink-out {
  display: flex;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  animation: app-nav-secondary-sink-out 0.15s ease forwards;
}

/* The sinking group is bottom-pinned, so it starts out sitting right on
   top of the popover avatar taking its place -- two identity avatars at
   once. Hold the incoming one back until the outgoing one has nearly
   faded, so the two read as a swap rather than a duplicate. */
.app-shell--collapsed .inline--sink-out ~ .app-sidebar-menu {
  animation: app-nav-secondary-avatar-fade-in 0.15s ease;
}

/** Animations **/

@keyframes app-nav-secondary-rise-in {
  from {
    opacity: 0;
    transform: translateY(0.6rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes app-nav-secondary-sink-out {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(0.6rem);
  }
}

@keyframes app-nav-secondary-avatar-fade-in {
  from {
    opacity: 0;
  }
  60% {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
