@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;
  }

  .dev-stamp {
    animation: slide-up-fade-in 400ms ease-out both;
    overflow: hidden;
    padding-inline: 1.5em 1em;
    position: relative;

    &::before {
      content: "DEV";
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 0 0.5em;
      background: var(--color-gray);
      font-size: var(--font-x-small-responsive);
      font-weight: 700;
      line-height: 1;
      text-align: center;
      writing-mode: vertical-rl;
      transform: rotate(180deg);
    }
  }

  :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);
    }
  }

  /* The header and footer bars. Fixed, not sticky: as a grid item each bar's
     containing block is its own single-row area, so sticky has no room to
     travel. Open panels own their full column, so the bars span only the
     centre strip and the panels' colour and toggles stay on top. The footer
     is scoped to the app body: layout.css is shared with the website bundle,
     whose footer stays in flow at the page end. */
  :where(#header, body.app #footer) {
    position: fixed;
    inset-inline: 0;
    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);
      }
    }
  }

  :where(body.app #footer) {
    grid-area: footer;
    max-inline-size: 100vw;
    view-transition-name: footer;
    inset-block-end: 0;
    block-size: var(--footer-height);
    pointer-events: none;

    > 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) {
    inset-block-start: 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;

    > 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;
    }
  }

  /* The side panels. Viewport-sticky, not page-tall: the grid area spans the
     whole page, so a 100% box pins the content to the page top — a dock
     opened while scrolled down looked empty, and its lazy frame never
     intersected, so it never loaded. Both panels overhang the body's
     header/footer padding by a negative margin, so each colours its column
     edge-to-edge with no white strip above the fixed footer. */
  :where(#sidebar, #dock) {
    background-color: var(--color-gray-light);
    block-size: 100dvh;
    font-size: var(--font-small-medium-responsive);
    inset-block-start: 0;
    margin-block-start: calc(-1 * var(--header-height));
    margin-block-end: calc(-1 * var(--footer-height));
    max-block-size: 100dvh;
    overflow: auto;
    position: sticky;

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

  :where(#sidebar) {
    grid-area: sidebar;
    inline-size: var(--sidebar-width);
    transition: margin-inline-start 0.2s ease-out;
    padding-top: 2.5rem;

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

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

  :where(#dock) {
    container-type: inline-size;
    grid-area: dock;
    inline-size: var(--dock-width);
    scroll-behavior: smooth;
    transition:
      margin-inline-end 0.2s ease-out,
      inline-size 0.2s ease-out;
    /* Headroom: re-seats the content below the header overhang. */
    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 (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;
    }
  }
}
