body {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

/*
 * The pre-boot loader: a plate that cycles through the company's apps inside a
 * tracing ring. Drawn before the bundle exists, so it may only use the two
 * custom properties `index.html` seeds from localStorage — no Vuetify tokens,
 * no bundled fonts.
 */
.loading-container {
    background-color: var(--initial-loader-bg, #ffffff);
    height: 100dvh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
}

.appCycler {
    position: relative;
    width: 120px;
    height: 120px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Ring ------------------------------------------------------------------- */

.appRing {
    position: absolute;
    inset: 0;
    width: 120px;
    height: 120px;
    fill: none;
    /* The arc chases clockwise from the top rather than from 3 o'clock. */
    transform: rotate(-90deg);
}

.appRing__track {
    stroke: var(--initial-loader-color, #5d87ff);
    stroke-width: 2;
    opacity: 0.12;
}

/*
 * `pathLength="100"` on the rect normalises its perimeter, so these dash
 * values are literally "a 28% segment travelling around the shape".
 */
.appRing__arc {
    stroke: var(--initial-loader-color, #5d87ff);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-dasharray: 28 72;
    /* Exactly two app holds per lap, so the two rhythms stay in step. */
    animation: ringTravel 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}

@keyframes ringTravel {
    to {
        stroke-dashoffset: -100;
    }
}

/* Plate ------------------------------------------------------------------ */

.appPlate {
    position: relative;
    width: 76px;
    height: 76px;
    border-radius: 20px;
    /* Neutral first, so a browser without color-mix still gets a plate. */
    background-color: rgba(128, 128, 128, 0.1);
    background-color: color-mix(in srgb, var(--initial-loader-color, #5d87ff) 8%, transparent);
    display: flex;
    align-items: center;
    justify-content: center;
}

/*
 * Every frame is stacked in the same place; only `.is-active` is visible. The
 * script owns which one that is, so the sequence works for any number of apps
 * without keyframe percentages that depend on the count.
 */
.appFrame {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transform: scale(0.88) translateY(4px);
    /* Kept well under the 750ms hold, or each mark spends its turn arriving. */
    transition: opacity 0.26s ease, transform 0.26s cubic-bezier(0.34, 1.3, 0.64, 1);
    pointer-events: none;
}

.appFrame.is-active {
    opacity: 1;
    transform: scale(1) translateY(0);
}

/*
 * Contained, never cropped — these are logos, and the app tiles letterbox them
 * for the same reason (see `pages/index.vue`).
 */
.appMark {
    width: 44px;
    height: 38px;
    object-fit: contain;
}

/* The bundled fallbacks are strokes, so they take the theme's primary colour. */
.appMark--icon {
    color: var(--initial-loader-color, #5d87ff);
}

/* Name ------------------------------------------------------------------- */

.appName {
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1;
    color: var(--initial-loader-color, #5d87ff);
    opacity: 0.75;
}

/* No platform name cached yet — take up no space, so the plate stays centred. */
.appName:empty {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .appRing__arc {
        animation-duration: 4s;
        animation-timing-function: linear;
    }

    .appFrame {
        transition-duration: 0.15s;
        transform: none;
    }

    .appFrame.is-active {
        transform: none;
    }
}
