/* =============================================================================
   Eyvi.Design - application shell
   -----------------------------------------------------------------------------
   The chrome around every page, and the two pieces of UI Blazor's bootstrap
   requires (the WebAssembly loading indicator and the unhandled-error bar).
   Token-only. The application supplies the navigation, the brand and any scope
   switcher; this supplies where they go.

   Expected markup: .app-shell > (.skip-link, .app-sidebar, .app-overlay,
   .app-main > (.app-topbar > .app-nav-toggle, .app-topbar__spacer), .app-content).

   RESPONSIVE MODEL - one breakpoint, 64rem (lg):

     >= 64rem   the sidebar is a permanent column; content is centred and capped
     <  64rem   the sidebar is a modal drawer opened from the top bar: it sits
                above the top bar, dims what it covers, and closes on Escape, on
                the overlay and on navigation (the application's markup does that)
   ============================================================================= */

/* Anchors the skip link and lets the drawer overlay cover the whole viewport. */
.app-shell {
    display: flex;
    /* dvh accounts for a phone browser's collapsing address bar; vh does not, and
       leaves a strip of background below the fold. vh first as the fallback. */
    min-height: 100vh;
    min-height: 100dvh;
}

/* -----------------------------------------------------------------------------
   Skip link - the first thing a keyboard or screen-reader user meets
   ----------------------------------------------------------------------------- */

.skip-link {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    z-index: var(--z-top);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    background-color: var(--color-surface-container);
    color: var(--color-text);
    box-shadow: var(--shadow-lg);

    /* Off-screen rather than display:none, so it is still focusable. */
    transform: translateY(calc(-100% - var(--space-4)));
    transition: transform var(--motion-duration-fast) var(--motion-easing-standard);
}

.skip-link:focus-visible {
    transform: translateY(0);
}

/* -----------------------------------------------------------------------------
   Sidebar
   ----------------------------------------------------------------------------- */

.app-sidebar {
    /* A drawer scrolled to its end must not start scrolling the page behind it -
       "scroll chaining" is the effect where closing a nav by flicking it also moves
       the content underneath, which reads as the screen having come apart. */
    overscroll-behavior: contain;
    position: sticky;
    top: 0;
    z-index: var(--z-sidebar);
    flex: 0 0 var(--layout-sidebar-width);
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
    background-color: var(--color-sidebar-background);
    color: var(--color-sidebar-text);
    border-right: var(--border-width) solid var(--color-sidebar-border);

    /* Clears a notch when the device is held in landscape. */
    padding-left: var(--layout-safe-left);
    padding-top: var(--layout-safe-top);
    padding-bottom: var(--layout-safe-bottom);
}

/* -----------------------------------------------------------------------------
   Main column
   ----------------------------------------------------------------------------- */

.app-main {
    flex: 1 1 auto;
    /* Without this, a wide table inside a flex child stretches the whole shell
       instead of scrolling within its own container. */
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.app-topbar {
    position: sticky;
    top: 0;
    z-index: var(--z-topbar);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--layout-topbar-height);
    padding: 0 var(--space-5);
    padding-top: var(--layout-safe-top);
    padding-right: calc(var(--space-5) + var(--layout-safe-right));
    background-color: var(--color-topbar-background);
    border-bottom: var(--border-width) solid var(--color-border);
}

/* Pushes the account controls to the trailing edge, leaving the leading edge for
   the drawer toggle. A spacer rather than justify-content, so items on either
   side keep their natural order for a screen reader. */
.app-topbar__spacer {
    flex: 1 1 auto;
}

.app-content {
    flex: 1 1 auto;
    width: 100%;
    max-width: var(--layout-content-max-width);
    /* Centred once the window is wider than the cap, rather than clinging to the
       sidebar with an ocean of empty space on the right. */
    margin-inline: auto;
    padding: var(--space-6) var(--space-5);
    padding-bottom: calc(var(--space-6) + var(--layout-safe-bottom));
}

/* -----------------------------------------------------------------------------
   Drawer toggle and overlay - only meaningful below the breakpoint
   ----------------------------------------------------------------------------- */

.app-nav-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    width: var(--layout-touch-target-min);
    min-width: var(--layout-touch-target-min);
    padding: 0;
    border-radius: var(--radius-md);
}

.app-overlay {
    display: none;
    position: fixed;
    inset: 0;
    /* A button, not a div: dismissing the drawer has to be reachable without a
       pointer, and Escape alone is not discoverable. */
    border: 0;
    padding: 0;
    background-color: var(--color-overlay);
    cursor: pointer;
}

/* -----------------------------------------------------------------------------
   Mobile and tablet-portrait layout
   ----------------------------------------------------------------------------- */

@media (max-width: 64rem) {
    .app-nav-toggle {
        display: inline-flex;
    }

    .app-sidebar {
        position: fixed;
        inset: 0 auto 0 0;
        /* Never the full width: leaving the page edge visible is what makes it
           read as a layer over the page rather than a new screen. */
        width: min(20rem, 84vw);
        flex-basis: auto;
        /* Above the top bar and the overlay: the drawer is modal here. */
        z-index: calc(var(--z-modal) + 1);
        transform: translateX(-100%);
        transition: transform var(--motion-duration-base) var(--motion-easing-standard);
        box-shadow: var(--shadow-lg);
    }

    .app-shell--nav-open .app-sidebar {
        transform: translateX(0);
    }

    .app-overlay {
        display: block;
        z-index: var(--z-modal);
        opacity: 0;
        visibility: hidden;
        transition:
            opacity var(--motion-duration-base) var(--motion-easing-standard),
            visibility var(--motion-duration-base) step-end;
    }

    .app-shell--nav-open .app-overlay {
        opacity: 1;
        visibility: visible;
        transition:
            opacity var(--motion-duration-base) var(--motion-easing-standard),
            visibility 0s;
    }

    .app-content {
        padding: var(--space-5) var(--space-4);
        padding-bottom: calc(var(--space-5) + var(--layout-safe-bottom));
    }

    .app-topbar {
        padding-inline: var(--space-4);
        padding-right: calc(var(--space-4) + var(--layout-safe-right));
    }
}

/* Stops the page behind the drawer from scrolling. :has() is unsupported only in
   browsers the applications do not target; where it is missing the background
   simply scrolls, which is untidy rather than broken. */
body:has(.app-shell--nav-open) {
    overflow: hidden;
}

/* A drawer that slides is orientation; a drawer that slides for someone who asked
   for less motion is just slower. Snap instead. */
@media (prefers-reduced-motion: reduce) {
    .app-sidebar,
    .app-overlay,
    .skip-link {
        transition: none;
    }
}

/* -----------------------------------------------------------------------------
   Blazor WebAssembly bootstrap UI
   Shown by index.html before the .NET runtime has finished downloading, which on
   a poor connection takes a while - so it is worth looking deliberate.
   ----------------------------------------------------------------------------- */

.loading-progress {
    position: relative;
    display: block;
    width: 8rem;
    height: 8rem;
    margin: 20vh auto 1rem;
}

.loading-progress circle {
    fill: none;
    stroke: var(--color-surface-inset);
    stroke-width: 0.6rem;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}

.loading-progress circle:last-child {
    stroke: var(--color-primary);
    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
    transition: stroke-dasharray var(--motion-duration-base) var(--motion-easing-standard);
}

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: var(--font-weight-medium);
    inset: calc(20vh + 3.25rem) 0 auto 0;
    color: var(--color-text-muted);
}

.loading-progress-text::after {
    content: var(--blazor-load-percentage-text, "Loading");
}

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: var(--z-top);
    padding: var(--space-3) var(--space-5);
    padding-bottom: calc(var(--space-3) + var(--layout-safe-bottom));
    background-color: var(--color-warning-subtle);
    color: var(--color-text);
    box-shadow: var(--shadow-lg);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: var(--space-3);
    top: var(--space-2);
}
