@layer components {
  /* The closed dock slides out via negative end margin; unlike the sidebar's
     start-side overhang, end-side overflow is reachable by horizontal scroll.
     hidden, not clip: Chrome ignores clip on the root for viewport scrolling. */
  html:has(body.app) {
    overflow-x: hidden;
  }

  body.app {
    /* The root's hidden viewport is still programmatically scrollable, so any
       focus or scrollIntoView reaching content in the end-side overhang shifts
       the whole page sideways and sticks (no scrollbar to drag it back). Clip
       the overflow at the body instead — clip forbids scrolling and is not a
       scroll container, so the sticky panels keep the viewport scrollport and
       fixed chrome (header, slideovers) escapes untouched. */
    overflow-x: clip;
    --sidebar-width: 25vw;
    --dock-width: 25vw;
    --header-height: 2.75rem;
    --footer-height: 2.75rem;
    /* Width left for the center track once open panels are subtracted. #main's
       viewport-based widths must min() against this: a definite inline-size is
       a floor for the fr track, so an unaware 85vw page pushes the open dock
       off-screen (clipped since the root hides overflow-x). */
    --main-available: 100vw;

    /* The fixed header and footer are out of flow; this keeps the grid between them. */
    padding-block-start: var(--header-height);
    padding-block-end: var(--footer-height);

    display: grid;
    grid-template-rows: auto auto auto auto 1fr auto;
    grid-template-columns: 1fr;
    grid-template-areas:
      "impersonate"
      "header"
      "vendor-preview"
      "toolbar"
      "main"
      "footer";

    @media only screen and (min-width: 70ch) {
      grid-template-columns: auto 1fr auto;
      grid-template-areas:
        "impersonate impersonate impersonate"
        "sidebar header dock"
        "sidebar vendor-preview dock"
        "sidebar toolbar dock"
        "sidebar main dock"
        "sidebar footer dock";

      &:has(#sidebar-toggle:checked) {
        --main-available: calc(100vw - var(--sidebar-width));
      }

      &:has(#dock-toggle:checked) {
        --main-available: calc(100vw - var(--dock-width));
      }

      &:has(#sidebar-toggle:checked):has(#dock-toggle:checked) {
        --main-available: calc(100vw - var(--sidebar-width) - var(--dock-width));
      }
    }

    /* Turbo-injected body children must not steal grid cells. */
    > turbo-frame,
    > turbo-cable-stream-source {
      display: contents;
    }
  }

  .impersonate-banner {
    grid-area: impersonate;
  }

  .vendor-preview-banner {
    grid-area: vendor-preview;
  }

  :where(#main-website) {
    container-type: inline-size;
    font-size: var(--font-medium-responsive);
  }

  :where(#main) {
    container-type: inline-size;
    font-size: var(--font-small-medium-responsive);
    grid-area: main;
    inline-size: min(95ch, 75vw, var(--main-available, 100vw));
    margin-top: 1rem;
    margin-inline: auto;
    max-inline-size: 100%;
    padding-block-end: clamp(
      var(--block-space),
      5%,
      calc(var(--block-space) * 3)
    );
    padding-inline: clamp(var(--inline-space), 5%, calc(var(--inline-space) * 3));
    /* text-align: center; */

    @media (max-width: 70ch) {
      inline-size: min(70ch, 100vw);
      padding-inline: var(--inline-space);
    }
  }

  #main.medium-width {
    inline-size: min(110ch, 85vw, var(--main-available, 100vw));

    @media (max-width: 70ch) {
      inline-size: min(70ch, 100vw);
    }
  }

  #main.wide-width {
    /* Wide layout - uses most of the available space in the main area */
    inline-size: min(150ch, 95%, var(--main-available, 100vw));

    @media (max-width: 70ch) {
      inline-size: min(70ch, 100vw);
    }
  }

  #main.full-width {
    /* Full-bleed layout - claims nearly the whole viewport (e.g. timeline) */
    inline-size: 97%;

    @media (max-width: 70ch) {
      inline-size: min(70ch, 100vw);
    }
  }

  /* Scoped to the app body: layout.css is shared with the website bundle, whose
     footer stays in flow at the page end. */
  :where(body.app #footer) {
    grid-area: footer;
    max-inline-size: 100vw;
    view-transition-name: footer;
    /* Fixed like the header: as a grid item the footer's containing block is
       its own single-row area, so sticky has no room to travel. */
    position: fixed;
    inset-block-end: 0;
    inset-inline: 0;
    block-size: var(--footer-height);
    pointer-events: none;
    z-index: 1;
    transition:
      inset-inline-start 0.2s ease-out,
      inset-inline-end 0.2s ease-out;

    :has(#sidebar-toggle:checked) & {
      @media (min-width: 70ch) {
        inset-inline-start: var(--sidebar-width);
      }
    }

    :has(#dock-toggle:checked) & {
      @media (min-width: 70ch) {
        inset-inline-end: var(--dock-width);
      }
    }

    > nav {
      /* Solid like the header, with the same hairline; the strip itself stays
         click-through — only the controls catch the pointer. The old fade is
         parked here:
      background: linear-gradient(
        to top,
        oklch(var(--lch-white) / 0.85) 40%,
        transparent
      ); */
      background-color: var(--color-bg);
      block-size: 100%;
      border-block-start: var(--border);
      padding-inline: var(--inline-space);
      view-transition-name: footer-nav;

      a,
      button,
      dialog,
      form {
        pointer-events: auto;
      }
    }
  }

  :where(#header) {
    /* Fixed, not sticky: as a grid item the header's containing block is its
       own single-row grid area, so sticky has no room to travel. Its box was
       already full-viewport-width in every panel state (the old negative
       margins only compensated the shrinking center column), so fixing it
       changes no geometry — the toggles just stay reachable while scrolled. */
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: var(--header-height);
    /* Trying out a solid header — the frosted treatment is parked here:
    background-color: oklch(var(--lch-white) / 0.6);
    backdrop-filter: blur(5px); */
    background-color: var(--color-bg);
    /* border-block-end: var(--border); */
    view-transition-name: header;
    z-index: 1;
    transition:
      inset-inline-start 0.2s ease-out,
      inset-inline-end 0.2s ease-out;

    /* Open panels own their full column up to the viewport top; the header
       spans only the center strip so their color and toggles stay on top. */
    :has(#sidebar-toggle:checked) & {
      @media (min-width: 70ch) {
        inset-inline-start: var(--sidebar-width);
      }
    }

    :has(#dock-toggle:checked) & {
      @media (min-width: 70ch) {
        inset-inline-end: var(--dock-width);
      }
    }

    > nav {
      view-transition-name: nav;
    }

    > * {
      align-items: center;
      /* Fill the fixed header and let flex centre the row vertically, so the
         toggles sit on the bar's middle at any --header-height. The crumbs are
         pinned to the absolute centre (breadcrumbs.css); left toggles pack at
         the start, the actions cluster carries its own end margin. */
      block-size: 100%;
      display: flex;
      gap: 1ch;
      padding-inline: var(--inline-space);
    }

    .btn {
      flex-shrink: 0;
    }
  }

  :where(#sidebar) {
    background-color: var(--color-gray-light);
    /* Mirrors #dock: viewport-sticky and pulled over the body's header
       padding, so the panel colors its column to the top and its nav stays
       visible however far the page is scrolled. */
    block-size: 100dvh;
    font-size: var(--font-small-medium-responsive);
    grid-area: sidebar;
    inline-size: var(--sidebar-width);
    inset-block-start: 0;
    /* Overhang the body's header/footer padding both ways (as #dock does), so the
       column colours edge-to-edge with no white strip above the fixed footer. */
    margin-block-start: calc(-1 * var(--header-height));
    margin-block-end: calc(-1 * var(--footer-height));
    max-block-size: 100dvh;
    overflow: auto;
    position: sticky;
    transition: margin-inline-start 0.2s ease-out;
    padding-top: 2.5rem;

    :has(#sidebar-toggle:checked) & {
      margin-inline-start: 0;
    }

    @media (max-width: 70ch) {
      display: none;
    }

    @media (min-width: 70ch) {
      margin-inline-start: calc(-1 * var(--sidebar-width));
    }
  }

  :where(#dock) {
    background-color: var(--color-gray-light);
    /* Viewport-sticky, not page-tall: the grid area spans the whole page, so a
       100% box pins the dock's content to the page top — opened while scrolled
       down it looks empty (and the lazy frame never intersects, so it never
       loads). */
    block-size: 100dvh;
    container-type: inline-size;
    font-size: var(--font-small-medium-responsive);
    grid-area: dock;
    inline-size: var(--dock-width);
    inset-block-start: 0;
    /* The body reserves header/footer padding for the centre strip; the panel
       colours its column edge-to-edge, so it overhangs both by a negative margin
       — up over the header (its own headroom padding below re-seats the content)
       and down over the footer, so no white strip is left above the fixed
       footer, which itself retracts from the dock column when open. */
    margin-block-start: calc(-1 * var(--header-height));
    margin-block-end: calc(-1 * var(--footer-height));
    max-block-size: 100dvh;
    overflow: auto;
    position: sticky;
    scroll-behavior: smooth;
    transition:
      margin-inline-end 0.2s ease-out,
      inline-size 0.2s ease-out;
    padding-block-start: calc(var(--header-height) + var(--block-space-half));
    padding-inline: clamp(var(--inline-space), 6%, calc(var(--inline-space) * 3));
    padding-block-end: var(--block-space);

    :has(#dock-toggle:checked) & {
      margin-inline-end: 0;
    }

    @media (max-width: 70ch) {
      display: none;
    }

    @media (min-width: 70ch) {
      margin-inline-end: calc(-1 * var(--dock-width));
    }
  }

  :where(#toolbar) {
    display: flex;
    grid-area: toolbar;
    inset: 0 0 auto;
    justify-content: center;
    min-width: 0;
    overflow: hidden;
    padding-inline: var(--inline-space);
    position: sticky;
    view-transition-name: toolbar;
    z-index: 1;
  }

  /* While a slideover is open, refresh broadcasts (morph + view transition)
     would promote the named chrome into the top layer, painting it above the
     slideover's z-index for the duration of the cross-fade. Dropping the names
     keeps the chrome inside the root snapshot, behind the overlay. */
  html:has(.slide-over-container) :is(#header, #header > nav, #footer, #footer > nav, #toolbar) {
    view-transition-name: none;
  }

  :is(#header, #footer, #sidebar, #dock, #toolbar) {
    @media print {
      display: none;
    }
  }

  @media print {
    body.app {
      padding-block-start: 0;
    }
  }
}
