body fuse-splash-screen {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #05050b;
    color: #F9FAFB;
    overflow: hidden;
    z-index: 999999;
    pointer-events: none;
    opacity: 1;
    visibility: visible;
    transition: opacity 400ms cubic-bezier(0.4, 0, 0.2, 1);
}

body fuse-splash-screen img {
    width: 120px;
    max-width: 120px;
}

body fuse-splash-screen .spinner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 40px;
    width: 56px;
}

body fuse-splash-screen .spinner > div {
    width: 12px;
    height: 12px;
    background-color: #1E96F7;
    border-radius: 100%;
    display: inline-block;
    -webkit-animation: fuse-bouncedelay 1s infinite ease-in-out both;
    animation: fuse-bouncedelay 1s infinite ease-in-out both;
}

body fuse-splash-screen .spinner .bounce1 {
    -webkit-animation-delay: -0.32s;
    animation-delay: -0.32s;
}

body fuse-splash-screen .spinner .bounce2 {
    -webkit-animation-delay: -0.16s;
    animation-delay: -0.16s;
}

/* Cold-start message. The splash stays up until the first NavigationEnd, which on a scale-to-zero
   backend waits on the auth round-trip — so the user needs to know the wait is expected, not broken.
   It fades in AFTER 1.2s (opacity 0 until then, via animation-delay) so a warm load never flashes it. */
body fuse-splash-screen .hold {
    margin-top: 28px;
    max-width: 22rem;
    padding: 0 24px;
    text-align: center;
    opacity: 0;
    animation: fuse-hold-in 600ms cubic-bezier(0.4, 0, 0.2, 1) 1200ms forwards;
}

body fuse-splash-screen .hold .hold-title {
    margin: 0;
    font-size: 15px;
    line-height: 22px;
    font-weight: 500;
    letter-spacing: 0.01em;
    color: #E5E7EB;
}

body fuse-splash-screen .hold .hold-sub {
    margin: 6px 0 0;
    font-size: 13px;
    line-height: 20px;
    color: #9CA3AF;
}

@keyframes fuse-hold-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    body fuse-splash-screen .hold {
        animation-delay: 1200ms;
        animation-duration: 1ms;
        transform: none;
    }
}

@-webkit-keyframes fuse-bouncedelay {
    0%, 80%, 100% {
        -webkit-transform: scale(0)
    }
    40% {
        -webkit-transform: scale(1.0)
    }
}

@keyframes fuse-bouncedelay {
    0%, 80%, 100% {
        -webkit-transform: scale(0);
        transform: scale(0);
    }
    40% {
        -webkit-transform: scale(1.0);
        transform: scale(1.0);
    }
}

body:not(.fuse-splash-screen-hidden) {
    overflow: hidden;
}

body.fuse-splash-screen-hidden fuse-splash-screen {
    visibility: hidden;
    opacity: 0;
}

/* ============================================================
   BOOT BACKDROP — ctoteachings.com's purple perspective mesh, in pure CSS.

   The marketing site draws this mesh with assets/wave-bg.jpg (528 KB). That file cannot come
   anywhere near the splash: this screen paints BEFORE the bundle loads, on the cold mobile start
   where every byte is in front of the first frame. So the mesh is two repeating-linear-gradients
   on a perspective-tilted plane, and the bloom is the same radial-gradient trick the marketing
   site already uses at zero bytes (public/style.css `.glow-purple` / `.glow-blue`).

   Cost: no new network request (this stylesheet is already linked from index.html), no image, no
   markup change, no animation. Gradients are composited on the first paint like a flat colour.

   Everything is masked away from the centre on purpose — the middle stays BLACK so the dove mark
   and both lines of hold copy keep full contrast.

   FOUR DECISIONS #598 ASKED FOR, RECORDED HERE BECAUSE THIS IS THE FILE THAT HOLDS THEM:

   1. LANG ONLY. helper-portal has its own splash-screen.css and is a different product with
      different branding — the purple mesh is ctoteachings.com's language-learning identity, not a
      house style. Copying it there would be a branding decision nobody has made. The two files
      already diverge; they are not a shared kit.
   2. NOTHING NEW ANIMATES, so prefers-reduced-motion needs no new rule. The mesh and both blooms
      are static paint. The only motion on this screen is what already shipped — the bouncing dots
      and the 1.2s fade-in of the hold copy — and the reduce query above already flattens the
      latter. A still backdrop is the right answer anyway: the splash is meant to be brief.
   3. ALWAYS DARK, prefers-color-scheme is NOT consulted. This markup paints before any theme class
      exists, index.html already declares <meta name="color-scheme" content="dark">, and both apps
      default to the dark Ink theme — so a light splash would be a white flash handing over to a
      dark app for most users. A mesh this dark has no light-mode equivalent that keeps the copy
      legible; it would be a second design, not a variable swap.
   4. THE COPY IS UNTOUCHED. index.html is not modified by this change at all — the dove, the
      spinner and both hold lines are exactly what they were, and the spec beside this file pins
      that the markup still names one asset and still says both sentences.
   ============================================================ */

/* The mesh: one flat grid, tipped away from the viewer so its lines converge. The plane's near
   edge is the bottom-right corner and it recedes up and to the left, which is what makes it read
   as sweeping IN from that corner rather than sitting flat behind the copy. */
body fuse-splash-screen::before {
    content: '';
    position: absolute;
    top: -55%;
    right: -45%;
    width: 165%;
    height: 150%;
    background-image:
        repeating-linear-gradient(to right, rgba(167, 139, 255, 1) 0 1px, transparent 1px 38px),
        repeating-linear-gradient(to bottom, rgba(167, 139, 255, 1) 0 1px, transparent 1px 38px);
    transform: perspective(900px) rotateX(64deg) rotate(-24deg);
    transform-origin: 100% 100%;
    /* Fade the grid out toward the middle of the plane; the far edge is where the lines crowd into
       mush, and the near edge is where the copy sits. */
    -webkit-mask-image: radial-gradient(78% 78% at 90% 98%, #000 0%, rgba(0, 0, 0, 0.62) 46%, transparent 82%);
    mask-image: radial-gradient(78% 78% at 90% 98%, #000 0%, rgba(0, 0, 0, 0.62) 46%, transparent 82%);
    opacity: 0.85;
    z-index: 0;
}

/* The blooms. Deliberately NO filter: blur() — a radial-gradient is already a soft bloom, and a
   full-screen blur pass is a GPU cost paid in front of the first frame for no visible gain. */
body fuse-splash-screen::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
        radial-gradient(58% 52% at 100% 100%, rgba(122, 100, 255, 0.55), transparent 70%),
        radial-gradient(52% 46% at 2% 4%, rgba(60, 40, 200, 0.42), transparent 70%);
    z-index: 0;
}

/* Content above both layers. The splash children are the logo, the spinner and the hold copy. */
body fuse-splash-screen > * {
    position: relative;
    z-index: 1;
}
