/* Base layout and element styling for the application shell.
 * Mobile-first: the unprefixed rules describe the phone, and the min-width
 * queries below are the only places a larger screen is considered. */

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

html {
  -webkit-text-size-adjust: 100%;
  /* The header and the bottom navigation are sticky, so anything the browser
   * scrolls into view can land underneath them. Without this reserve, tabbing
   * to a control near either edge puts the focus ring somewhere off-screen and
   * the keyboard user loses their place. */
  scroll-padding-block: var(--nav-height) var(--nav-height);
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  background-color: var(--color-bg);
  /* Real 500 and 600 weights come out of the variable file, so nothing here
   * needs the browser to fake a bold -- which is what makes small type look
   * muddy. */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3 {
  margin: 0 0 var(--space-3);
  line-height: var(--line-height-tight);
  /* Negative tracking on headings only. At display sizes the default spacing
   * reads as gaps between letters instead of one word; at body size the same
   * value would cost legibility. */
  letter-spacing: var(--tracking-tight);
  font-weight: var(--font-weight-bold);
  /* Stops a two-line title from leaving one orphan word on the second line. */
  text-wrap: balance;
}

h1 { font-size: var(--font-size-2xl); }
h2 { font-size: var(--font-size-xl); }
h3 { font-size: var(--font-size-lg); }

@media (min-width: 48rem) {
  h1 { font-size: var(--font-size-3xl); }
}

/* --- Icons ---
 * One sprite, defined once in components/icons.html. Size, colour and stroke
 * live here so an icon always matches the text beside it and follows the
 * theme without a second copy for dark mode. */

.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.icon {
  width: 1.125rem;
  height: 1.125rem;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

p {
  margin: 0 0 var(--space-3);
}

a {
  color: var(--color-accent);
  /* An underline that sits below the descenders rather than through them, and
   * thins out at this size instead of doubling the weight of the word. */
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}

/* One visible focus style for every interactive element. Removing outlines
 * without replacing them makes the app unusable by keyboard. */
:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Every animated property in the project switches off here, in one place.
 * The custom properties are what make that possible: a component that
 * animates with `var(--transition-base)` stops animating when this block
 * redefines it, without the component knowing this block exists. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  :root {
    --transition-fast: 0.01ms;
    --transition-base: 0.01ms;
    --transition-slow: 0.01ms;
  }

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

/* Lets a keyboard user jump past the navigation. Visually hidden until focused. */
.skip-link {
  position: absolute;
  left: var(--space-2);
  top: calc(var(--space-2) * -10);
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  background-color: var(--color-accent);
  color: var(--color-text-inverted);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: top var(--transition-fast) var(--ease-out);
}

.skip-link:focus {
  top: var(--space-2);
}

/* --- Application frame ---
 *
 * One navigation, two shapes. On a phone `.app-sidebar` is `display: contents`
 * so the header and the bar behave exactly as they always did: title stuck to
 * the top, bar stuck to the bottom within thumb reach. On a wide screen the
 * same element becomes a real box and the two stack into a left column.
 *
 * The alternative -- a second block of links for desktop -- is a second place
 * to forget a section when one is added. */

.app-shell {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.app-sidebar {
  display: contents;
}

.app-body {
  order: 2;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.app-header {
  position: sticky;
  top: 0;
  /* Higher than .app-nav, and that is load-bearing rather than tidy.
   *
   * In the sidebar both of these are flex items of .app-sidebar, and the
   * flexbox spec says a flex item with a z-index other than `auto` creates a
   * stacking context *even when it is position: static* -- which neither of
   * them looks like it is doing, because both were written as sticky bars for
   * the phone and only stop being sticky at 64rem.
   *
   * The consequence: .app-nav sat in its own stacking context at the same
   * level and later in order, so it painted over the account panel no matter
   * what z-index the panel itself carried. The panel opened, was fully
   * opaque, and had the navigation showing through it.
   */
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  height: var(--nav-height);
  padding: 0 var(--space-4);
  background-color: color-mix(in srgb, var(--color-surface-raised) 82%, transparent);
  /* The header sits over content that scrolls under it. A flat fill reads as
   * a lid; the blur reads as glass and keeps the page continuous. Browsers
   * without support fall back to the 82% fill, which is still legible. */
  backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--color-border);
}

/* --- The account menu ---
 *
 * A <details> element. Every bit of its open/closed behaviour, its keyboard
 * handling and its focus order comes from the element rather than from a
 * script -- see templates/components/account_menu.html.
 *
 * The trigger replaced the "easy-facu" wordmark that used to sit here. */

.account {
  position: relative;
  min-width: 0;
}

.account__trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  min-height: 2rem;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
  cursor: pointer;
  /* The default marker is a triangle in the browser's own colour, on the
   * wrong side, at the wrong size. The chevron in the markup replaces it. */
  list-style: none;
  transition: background-color var(--transition-fast) var(--ease-out);
}

.account__trigger::-webkit-details-marker {
  display: none;
}

.account__trigger:hover {
  background-color: var(--color-surface);
}

.account__name {
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-tight);
  /* An address is longer than the column. Truncating is what keeps the
   * chevron beside the name instead of pushed off the edge. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account__chevron {
  width: 1rem;
  height: 1rem;
  color: var(--color-text-muted);
  transition: transform var(--transition-fast) var(--ease-out);
}

.account[open] .account__chevron {
  transform: rotate(180deg);
}

.account__panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--space-1));
  left: 0;
  min-width: 13rem;
  padding: var(--space-1);
  background-color: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  animation: account-open var(--transition-base) var(--ease-out);
}

@keyframes account-open {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.98);
  }
}

.account__identity {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  /* An address does not wrap onto a second line inside a menu. */
  overflow: hidden;
}

.account__identity strong {
  font-size: var(--font-size-sm);
  color: var(--color-text);
}

.account__identity span,
.account__identity strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 2rem;
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--font-size-sm);
  color: var(--color-text);
  text-align: left;
  text-decoration: none;
  background: none;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color var(--transition-fast) var(--ease-out);
}

.account__item:hover {
  background-color: var(--color-surface);
}

.account__item .icon {
  color: var(--color-text-muted);
}

/* --- Avatar --- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0;
  line-height: 1;
  color: #ffffff;
  /* The one place in the project that paints text on the gradient rather than
   * on a token, so the contrast tests cannot check it. White on the darkest
   * stop of --gradient-brand measures 7.1:1 in the light theme and 8.0:1 in
   * the dark one; the pink end is lighter still, so white is the safe choice
   * at both ends and in both themes. */
  background: var(--gradient-brand);
  border-radius: var(--radius-full);
  user-select: none;
}

.avatar--sm {
  width: 1.25rem;
  height: 1.25rem;
}

.avatar--lg {
  width: 2.5rem;
  height: 2.5rem;
  font-size: var(--font-size-md);
}

.avatar--empty {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}

/* --- Collapsing the sidebar ---
 *
 * Defaults to open, and the attribute that closes it is written by the inline
 * snippet in base.html before first paint. A page whose script never runs is
 * a page with its navigation visible, which is the only acceptable direction
 * for this to fail in. */

.sidebar-toggle {
  /* A desktop action: on a phone the navigation is the bar along the bottom
   * and there is no column to fold. The button stays in the markup and the
   * media query below is what reveals it, rather than shipping two headers. */
  display: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  color: var(--color-text-muted);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition:
    background-color var(--transition-fast) var(--ease-out),
    color var(--transition-fast) var(--ease-out);
}

.sidebar-toggle:hover {
  background-color: var(--color-surface);
  color: var(--color-text);
}

.app-main {
  flex: 1;
  width: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: var(--space-4) var(--space-4) var(--space-12);
}

@media (min-width: 48rem) {
  .app-main {
    padding: var(--space-6) var(--space-6) var(--space-12);
  }
}

.app-footer {
  padding: var(--space-4);
  border-top: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  text-align: center;
}

/* --- Primary navigation ---
 * On a phone it is a bottom bar, reachable with a thumb: this app is opened
 * one-handed in a hallway. On a wider screen it moves into a left column. */

.app-nav {
  order: 3;
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  justify-content: space-around;
  background-color: color-mix(in srgb, var(--color-surface-raised) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  border-top: 1px solid var(--color-border);
  /* The home-indicator strip on a modern phone sits over the bottom of the
   * viewport. Without this the last row of icons is under the student's own
   * gesture bar. */
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.app-nav__link {
  flex: 1;
  /* Icon above label on a phone, beside it in the sidebar. Same markup. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-0);
  padding: var(--space-2) var(--space-0);
  text-align: center;
  font-size: var(--font-size-2xs);
  color: var(--color-text-muted);
  text-decoration: none;
  /* 44px is the smallest reliably tappable target; the icon needs the rest. */
  min-height: 48px;
  transition: color var(--transition-fast) var(--ease-out);
}

.app-nav__link[aria-current="page"] {
  color: var(--color-accent);
  font-weight: var(--font-weight-semibold);
}

/* The icon leads the state change and the label follows, which is what reads
 * as the bar responding rather than repainting. */
.app-nav__link .icon {
  transition: transform var(--transition-base) var(--ease-spring);
}

.app-nav__link[aria-current="page"] .icon {
  transform: translateY(-1px) scale(1.08);
}

.app-nav__link:active .icon {
  transform: scale(0.92);
}

/* --- The sidebar ---
 * 64rem rather than the old 48rem: a tablet in portrait has the width for a
 * horizontal bar but not for a column plus a readable measure beside it. */

@media (min-width: 64rem) {
  .app-shell {
    display: grid;
    grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  }

  .app-sidebar {
    grid-column: 1;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100dvh;
    padding: var(--space-3) var(--space-2);
    background-color: var(--color-surface);
    border-right: 1px solid var(--color-border);
  }

  .app-body {
    grid-column: 2;
  }

  .app-header {
    position: static;
    height: auto;
    padding: var(--space-2) var(--space-2) var(--space-4);
    background: none;
    backdrop-filter: none;
    border-bottom: none;
  }

  .app-nav {
    position: static;
    /* Back to auto: nothing here is sticky, and any value but auto makes
     * this flex item a stacking context. See .app-header above. */
    z-index: auto;
    /* Fills whatever the header leaves, which is what gives the sign-out form
     * below room to push itself to the bottom with `margin-top: auto`. */
    flex: 1;
    flex-direction: column;
    justify-content: flex-start;
    gap: 1px;
    background: none;
    backdrop-filter: none;
    border-top: none;
    padding-bottom: 0;
  }

  .app-nav__link {
    position: relative;
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--space-2);
    min-height: 2rem;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    text-align: left;
    transition:
      background-color var(--transition-fast) var(--ease-out),
      color var(--transition-fast) var(--ease-out);
  }

  .app-nav__link:hover {
    background-color: var(--color-surface-raised);
    color: var(--color-text);
  }

  /* The active item reads as a filled row rather than as coloured text --
   * at this size the fill is what the eye finds without reading. */
  .app-nav__link[aria-current="page"] {
    background-color: var(--color-accent-subtle);
  }

  /* A short bar in the logo's own violet at the leading edge of the active
   * row. It is what tells the eye which row is current from across the
   * screen, before any of the text resolves. */
  .app-nav__link[aria-current="page"]::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 3px;
    height: 1rem;
    border-radius: var(--radius-full);
    background: var(--gradient-brand);
    transform: translateY(-50%);
  }

  .app-nav__link[aria-current="page"] .icon {
    transform: none;
  }
}

/* Six destinations across a phone leaves each about 60px. The label has to
 * stay on one line inside that, or the icons above them stop lining up. */
.app-nav__label {
  white-space: nowrap;
}

/* The sign-out control is a form (Django rejects a GET logout), so the button
 * inside it has to look exactly like the sibling nav links. */
.app-nav__form {
  flex: 1;
  display: flex;
  margin: 0;
}

.app-nav__link--button {
  flex: 1;
  font: inherit;
  font-size: var(--font-size-2xs);
  background: none;
  border: none;
  cursor: pointer;
}

/* Everything the sidebar changes about these two, kept here rather than in
 * the frame block above so it lands after the base rules it overrides. A
 * media query that sits earlier in the file only appears to work: the later
 * unconditional rule wins every property they share. */
@media (min-width: 64rem) {
  .app-nav__form {
    flex: 0 0 auto;
    /* Pushed to the foot of the column. Sign-out is not a destination -- it is
     * the way out -- and sitting it directly under "Perfil" makes it the sixth
     * thing you might have meant to click. The margin does the work rather
     * than absolute positioning, so the column keeps laying itself out and a
     * seventh nav item would still land above it. */
    margin-top: auto;
  }

  .app-nav__link--button {
    flex: 0 0 auto;
    width: 100%;
    font-size: var(--font-size-sm);
  }
}

/* --- The mark ---
 * Sized by its own width/height attributes so it can be dropped in at any
 * size; this only fixes the properties every instance shares. */

.logo {
  display: block;
  flex-shrink: 0;
  overflow: visible;
}

/* --- Motion ---
 *
 * Two rules govern everything below.
 *
 * **Nothing is hidden unless a script is there to reveal it.** The reveal
 * animation starts from opacity 0, and a page whose content starts invisible
 * is a blank page if the script fails -- a stale service-worker copy, a
 * blocked file, an old browser. `@media (scripting: enabled)` is what makes
 * that impossible: with no scripting the hiding rule never applies and the
 * page renders exactly as it did before any of this existed.
 *
 * **Everything stops under prefers-reduced-motion.** The durations are
 * custom properties, and the reduced-motion block near the top of this file
 * flattens all of them at once, so a component that animates with
 * `var(--transition-base)` needs no rule of its own to respect the setting.
 */

@media (scripting: enabled) {
  [data-reveal] > * {
    opacity: 0;
    transform: translateY(6px);
  }

  [data-reveal] > .is-in {
    opacity: 1;
    transform: none;
    transition:
      opacity var(--transition-slow) var(--ease-out) var(--reveal-delay, 0ms),
      transform var(--transition-slow) var(--ease-out) var(--reveal-delay, 0ms);
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] > * {
    opacity: 1;
    transform: none;
  }
}

/* Cross-document view transitions.
 *
 * This is the one piece of "premium" that a server-rendered app gets for
 * free and a single-page app has to build: two lines, no JavaScript, and
 * every navigation in the site cross-fades instead of blinking white.
 *
 * The header and the sidebar are given names, so the browser holds them
 * still while the content under them changes -- which is what separates a
 * navigation inside an app from a page load. Browsers without support
 * navigate exactly as they do today.
 */
@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--transition-base);
  animation-timing-function: var(--ease-out);
}

@media (min-width: 64rem) {
  .app-sidebar {
    view-transition-name: app-sidebar;
  }
}

.app-header {
  view-transition-name: app-header;
}

/* A slide of a few pixels under the cross-fade. Any more and the page reads
 * as sliding in from somewhere, which is a claim about where the student
 * came from that the browser cannot actually make. */
@keyframes page-enter {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

::view-transition-new(root) {
  animation-name: page-enter;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

/* --- The sidebar, folded ---
 *
 * Only at the width where a sidebar exists at all. Below 64rem the navigation
 * is the bottom bar and there is nothing to fold, so none of this applies and
 * a stored "collapsed" from a desktop session does nothing on a phone.
 *
 * The column narrows to just the icons rather than disappearing. A sidebar
 * that vanishes leaves nothing to click to bring it back except a button
 * somewhere else, and it also throws away the one thing the column is for --
 * knowing where you are without reading. At 3.25rem the icons still say that. */

@media (min-width: 64rem) {
  .sidebar-toggle {
    display: inline-flex;
  }

  .app-shell {
    transition: grid-template-columns var(--transition-base) var(--ease-out);
  }

  :root[data-sidebar="collapsed"] .app-shell {
    grid-template-columns: 3.25rem minmax(0, 1fr);
  }

  :root[data-sidebar="collapsed"] .app-sidebar {
    padding-inline: var(--space-1);
    align-items: center;
  }

  /* The labels go, the icons stay, and the row becomes a square. `display:
   * none` on the label rather than a width transition: animating text out
   * reflows every row for the whole duration, and the column is already
   * moving. */
  :root[data-sidebar="collapsed"] .app-nav__label,
  :root[data-sidebar="collapsed"] .account__name,
  :root[data-sidebar="collapsed"] .account__chevron {
    display: none;
  }

  :root[data-sidebar="collapsed"] .app-nav__link,
  :root[data-sidebar="collapsed"] .app-nav__link--button {
    width: 2.25rem;
    justify-content: center;
    padding-inline: 0;
  }

  :root[data-sidebar="collapsed"] .app-nav__form {
    justify-content: center;
  }

  /* The bar that marks the current row would sit under the icon at this
   * width, so the fill has to carry it alone. */
  :root[data-sidebar="collapsed"] .app-nav__link[aria-current="page"]::before {
    display: none;
  }

  :root[data-sidebar="collapsed"] .app-header {
    flex-direction: column;
    gap: var(--space-1);
    padding-inline: 0;
  }

  :root[data-sidebar="collapsed"] .account__trigger {
    padding: var(--space-1);
  }

  /* The panel cannot open leftward out of a 52px column. */
  :root[data-sidebar="collapsed"] .account__panel {
    left: 0;
    min-width: 12rem;
  }
}

/* --- The folded sidebar opens under the pointer ---
 *
 * Hovering the rail shows the full column, and moving away puts it back.
 * Three decisions in here, and each of them is the difference between this
 * being pleasant and being infuriating.
 *
 * **Only where a pointer really hovers.** `hover: hover` and `pointer: fine`
 * together mean a mouse or a trackpad. On a touch screen the first tap counts
 * as a hover, so without this guard the column would fling itself open every
 * time a thumb landed near the left edge -- and then stay open, because there
 * is no "moving away" on touch.
 *
 * **It floats over the content instead of pushing it.** Widening the grid
 * column would reflow the entire page every time the pointer crossed the rail,
 * which is the most distracting thing a layout can do. Absolute positioning
 * over the top costs a shadow and moves nothing else.
 *
 * **The stored preference is untouched.** Hovering shows the column; it does
 * not unfold it. Somebody who folded the sidebar wants it folded, and having
 * it silently un-fold itself because they reached for the scrollbar would mean
 * the button they pressed did not stick.
 */

@media (min-width: 64rem) and (hover: hover) and (pointer: fine) {
  :root[data-sidebar="collapsed"] .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 30;
    width: 3.25rem;
    transition:
      width var(--transition-base) var(--ease-out),
      box-shadow var(--transition-base) var(--ease-out);
  }

  :root[data-sidebar="collapsed"] .app-sidebar:hover,
  /* Tabbing into the column has to open it too. A keyboard user landing on an
   * invisible label is the same failure as never opening it at all. */
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within {
    width: var(--sidebar-width);
    padding-inline: var(--space-2);
    align-items: stretch;
    box-shadow: var(--shadow-lg);
  }

  :root[data-sidebar="collapsed"] .app-sidebar:hover .app-nav__label,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .app-nav__label,
  :root[data-sidebar="collapsed"] .app-sidebar:hover .account__name,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .account__name,
  :root[data-sidebar="collapsed"] .app-sidebar:hover .account__chevron,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .account__chevron {
    display: block;
  }

  :root[data-sidebar="collapsed"] .app-sidebar:hover .app-nav__link,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .app-nav__link,
  :root[data-sidebar="collapsed"] .app-sidebar:hover .app-nav__link--button,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .app-nav__link--button {
    width: 100%;
    justify-content: flex-start;
    padding-inline: var(--space-2);
  }

  :root[data-sidebar="collapsed"] .app-sidebar:hover .app-header,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .app-header {
    flex-direction: row;
    padding-inline: var(--space-2);
  }

  /* The label is `display: none` while folded, so it needs putting back as the
   * flex row it is rather than as a block. */
  :root[data-sidebar="collapsed"] .app-sidebar:hover .account__name,
  :root[data-sidebar="collapsed"] .app-sidebar:focus-within .account__name {
    display: -webkit-box;
    display: block;
  }
}
