<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="color-scheme" content="dark" />
    <meta name="theme-color" content="#202020" />
    <title>
        {{#IF isInReportingMode}}
        ❌
        {{/IF}}
        Jellyfin
    </title>
    <style>
        /* Noto Sans (latin), matching the Jellyfin web client font. Embedded so the page renders
           identically before the web client is available. */
        @font-face {
            font-family: "Noto Sans";
            font-style: normal;
            font-display: swap;
            font-weight: 400;
            src: url(data:font/woff2;base64,[encoded data, 17496 characters]) format("woff2");
        }

        @font-face {
            font-family: "Noto Sans";
            font-style: normal;
            font-display: swap;
            font-weight: 700;
            src: url(data:font/woff2;base64,[encoded data, 17952 characters]) format("woff2");
        }

        :root {
            --jf-background: #101010;
            --jf-paper: #202020;
            --jf-text: #fff;
            --jf-text-secondary: rgba(255, 255, 255, 0.7);
            --jf-primary: #00a4dc;
            --jf-primary-dark: #00729a;
            --jf-divider: rgba(255, 255, 255, 0.12);
            --jf-error: #d15353;
            --jf-warn: #f2b01e;
            --jf-success: #5cbd5c;
            --jf-scrollbar-thumb: #3b3b3b;
        }

        * {
            box-sizing: border-box;
            scrollbar-width: thin;
            scrollbar-color: var(--jf-scrollbar-thumb) var(--jf-paper);
        }

        html {
            height: 100%;
            font-size: 93%;
            line-height: 1.35;
        }

        body {
            margin: 0;
            padding: 0 0 50px;
            min-height: 100%;
            background-color: var(--jf-background);
            color: var(--jf-text);
            font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }

        ::-webkit-scrollbar {
            width: 0.4em;
            height: 0.4em;
        }

        ::-webkit-scrollbar-track {
            background: var(--jf-paper);
        }

        ::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 2px;
        }

        a {
            color: var(--jf-primary);
            text-decoration: none;
        }

        a:hover {
            text-decoration: underline;
        }

        .page {
            max-width: 54em;
            margin: 0 auto;
            padding: 4.5em 3.3% 0;
        }

        .logo {
            display: block;
            width: 15em;
            max-width: 70%;
            height: auto;
            margin: 0 auto 2.25em;
        }

        .status {
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
            gap: 0.75em;
            text-align: center;
            margin-bottom: 1.5em;
        }

        .status .status-text {
            margin: 0;
            font-weight: 400;
            font-size: 1.25em;
            color: var(--jf-text);
        }

        .status.is-error .status-text {
            color: var(--jf-error);
        }

        /* Buttons — matching the web client's emby-button styles. */
        .jf-button {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            box-sizing: border-box;
            margin: 0;
            padding: 0.9em 1em;
            border: 0;
            border-radius: 0.2em;
            font-family: inherit;
            font-size: inherit;
            font-weight: 600;
            line-height: 1.35;
            cursor: pointer;
            outline: none;
            text-decoration: none;
            transition: 0.2s;
            -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        }

        .jf-button-primary {
            background: var(--jf-primary);
            color: rgba(0, 0, 0, 0.87);
        }

        .jf-button-primary:hover,
        .jf-button-primary:focus {
            background: var(--jf-primary-dark);
        }

        .jf-button-secondary {
            background: #424242;
            color: var(--jf-text-secondary);
        }

        .jf-button-secondary:hover,
        .jf-button-secondary:focus {
            background: #616161;
        }

        /* Redirect countdown shown once the server is ready. */
        .redirect-bar {
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
            flex-wrap: wrap;
            gap: 1em;
            margin-bottom: 1.5em;
            text-align: center;
        }

        .redirect-countdown {
            color: var(--jf-text-secondary);
        }

        /* Material (MDL) spinner — the same one the web client uses while loading. */
        .mdl-spinner {
            position: relative;
            display: inline-block;
            flex: 0 0 auto;
            width: 1.95em;
            height: 1.95em;
            font-size: 0.8em;
            animation: mdl-spinner__container-rotate 1568.23529412ms linear infinite;
        }

        @keyframes mdl-spinner__container-rotate {
            to {
                transform: rotate(360deg);
            }
        }

        .mdl-spinner__layer {
            position: absolute;
            width: 100%;
            height: 100%;
            opacity: 0;
            border-color: var(--jf-primary);
        }

        .mdl-spinner__layer-1 {
            animation: mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
        }

        .mdl-spinner__layer-2 {
            animation: mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
        }

        .mdl-spinner__layer-3 {
            animation: mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
        }

        .mdl-spinner__layer-4 {
            animation: mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
        }

        @keyframes mdl-spinner__fill-unfill-rotate {
            12.5% { transform: rotate(135deg); }
            25% { transform: rotate(270deg); }
            37.5% { transform: rotate(405deg); }
            50% { transform: rotate(540deg); }
            62.5% { transform: rotate(675deg); }
            75% { transform: rotate(810deg); }
            87.5% { transform: rotate(945deg); }
            to { transform: rotate(1080deg); }
        }

        @keyframes mdl-spinner__layer-1-fade-in-out {
            from { opacity: 0.99; }
            25% { opacity: 0.99; }
            26% { opacity: 0; }
            89% { opacity: 0; }
            90% { opacity: 0.99; }
            100% { opacity: 0.99; }
        }

        @keyframes mdl-spinner__layer-2-fade-in-out {
            from { opacity: 0; }
            15% { opacity: 0; }
            25% { opacity: 0.99; }
            50% { opacity: 0.99; }
            51% { opacity: 0; }
        }

        @keyframes mdl-spinner__layer-3-fade-in-out {
            from { opacity: 0; }
            40% { opacity: 0; }
            50% { opacity: 0.99; }
            75% { opacity: 0.99; }
            76% { opacity: 0; }
        }

        @keyframes mdl-spinner__layer-4-fade-in-out {
            from { opacity: 0; }
            65% { opacity: 0; }
            75% { opacity: 0.99; }
            90% { opacity: 0.99; }
            100% { opacity: 0; }
        }

        .mdl-spinner__circle {
            box-sizing: border-box;
            height: 100%;
            border-width: 0.21em;
            border-style: solid;
            border-color: inherit;
            border-bottom-color: transparent !important;
            border-radius: 50%;
            animation: none;
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
        }

        .mdl-spinner__circle-clipper {
            display: inline-block;
            position: relative;
            width: 50%;
            height: 100%;
            overflow: hidden;
            border-color: inherit;
        }

        .mdl-spinner__circle-clipper .mdl-spinner__circle {
            width: 200%;
        }

        .mdl-spinner__circleLeft {
            border-right-color: transparent !important;
            transform: rotate(129deg);
            animation: mdl-spinner__left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
        }

        .mdl-spinner__circleRight {
            left: -100%;
            border-left-color: transparent !important;
            transform: rotate(-129deg);
            animation: mdl-spinner__right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
        }

        @keyframes mdl-spinner__left-spin {
            from { transform: rotate(130deg); }
            50% { transform: rotate(-5deg); }
            to { transform: rotate(130deg); }
        }

        @keyframes mdl-spinner__right-spin {
            from { transform: rotate(-130deg); }
            50% { transform: rotate(5deg); }
            to { transform: rotate(-130deg); }
        }

        /* Startup log — a paperList-style surface. */
        .logs-panel {
            background-color: var(--jf-paper);
            border-radius: 0.2em;
            box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
            padding: calc(0.8em - 4px) 1.2em 1em;
        }

        .logs-header {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            gap: 1em;
            margin-bottom: 0.5em;
            padding-bottom: 0.5em;
            border-bottom: 1px solid var(--jf-divider);
        }

        .logs-header h2 {
            margin: 0;
            font-weight: 400;
            font-size: 1.17em;
        }

        .download-logs {
            flex: 0 0 auto;
            font-size: 0.9em;
            font-weight: 600;
        }

        .logs-scroll {
            overflow-y: auto;
        }

        .action-list,
        .action-list ul {
            list-style: none;
            margin: 0;
            padding: 0;
        }

        .action-list {
            font-size: 0.92em;
        }

        .action-list li {
            position: relative;
            padding: 0.18em 0;
        }

        .action-list summary,
        .action-list li > span {
            display: block;
            color: var(--jf-text);
        }

        .action-list summary {
            cursor: pointer;
            list-style: none;
        }

        .action-list summary::-webkit-details-marker {
            display: none;
        }

        /* Severity dot, colored from the theme palette. */
        .action-list summary::before,
        .action-list li > span::before {
            content: "";
            display: inline-block;
            flex: 0 0 auto;
            width: 0.5em;
            height: 0.5em;
            margin-right: 0.7em;
            border-radius: 50%;
            background: var(--dot, var(--jf-text-secondary));
        }

        .action-list li.success-item { --dot: var(--jf-success); }
        .action-list li.info-item { --dot: var(--jf-primary); }
        .action-list li.warn-item { --dot: var(--jf-warn); }
        .action-list li.danger-item,
        .action-list li.danger-strong-item { --dot: var(--jf-error); }

        .action-list li.warn-item > span,
        .action-list li.warn-item > details > summary { color: var(--jf-warn); }

        .action-list li.danger-item > span,
        .action-list li.danger-item > details > summary,
        .action-list li.danger-strong-item > span,
        .action-list li.danger-strong-item > details > summary { color: var(--jf-error); }

        .action-list li.danger-strong-item > span { font-weight: 700; }

        /* Nested groups: indent with a divider guide line. */
        .action-list ul {
            margin-left: 0.25em;
            padding-left: 1.1em;
            border-left: 1px solid var(--jf-divider);
        }
    </style>
</head>

<body>
    <div class="page">
                    <svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 251 72" fill="none" role="img" aria-label="Jellyfin">
                <g clip-path="url(#a)">
                    <path fill="url(#b)" d="M24.212 49.158C22.66 46.042 32.838 27.588 36 27.588c3.167.002 13.323 18.488 11.788 21.57-1.534 3.082-22.025 3.116-23.576 0" />
                    <path fill="url(#c)" fill-rule="evenodd" d="M.482 64.995C-4.195 55.605 26.477 0 36 0c9.533 0 40.153 55.713 35.527 64.995s-66.368 9.39-71.045 0m12.254-8.148c3.064 6.152 43.518 6.084 46.548 0 3.03-6.086-17.032-42.586-23.275-42.586S9.671 50.694 12.736 56.847" clip-rule="evenodd" />
                    <path fill="#fff" d="M225.22 56c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.219-.218c-.054-.107-.054-.247-.054-.527V26.8c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.183c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527v2.895a7.9 7.9 0 0 1 3.419-3.254q2.261-1.103 5.074-1.103 3.308 0 5.845 1.434a10.1 10.1 0 0 1 4.026 4.026q1.434 2.536 1.434 5.9V55.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.218c-.107.055-.247.055-.527.055h-5.625c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527V38.408q0-2.978-1.709-4.688-1.654-1.764-4.357-1.764-2.702 0-4.412 1.764-1.654 1.766-1.654 4.688V55.2c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.218c-.107.055-.247.055-.527.055zm-11.54-33.363c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527v-6.121c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .219.219c.054.107.054.247.054.527v6.12c0 .28 0 .42-.054.528a.5.5 0 0 1-.219.218c-.107.055-.247.055-.527.055zm0 33.363c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.218-.219c-.055-.107-.055-.247-.055-.527V26.8c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h5.624c.28 0 .42 0 .527.055a.5.5 0 0 1 .219.218c.054.107.054.247.054.527v28.4c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.219c-.107.054-.247.054-.527.054zm-16.712-.054c.107.054.247.054.527.054h5.625c.28 0 .42 0 .526-.054a.5.5 0 0 0 .219-.219c.055-.107.055-.247.055-.527V32.452h5.872c.28 0 .42 0 .527-.054a.5.5 0 0 0 .219-.219c.054-.107.054-.247.054-.527V26.8c0-.28 0-.42-.054-.527a.5.5 0 0 0-.219-.218c-.107-.055-.247-.055-.527-.055h-5.872v-.992q0-2.261 1.323-3.31 1.379-1.102 3.75-1.102.454 0 .939.044c.345.031.518.047.634-.004a.48.48 0 0 0 .241-.22c.061-.111.061-.274.061-.6V15.39c0-.304 0-.457-.061-.589a.7.7 0 0 0-.248-.284c-.122-.078-.261-.097-.537-.136a14.5 14.5 0 0 0-1.966-.126q-5.184 0-8.273 2.812t-3.088 7.942V26H186.53c-.3 0-.451 0-.58.05a.75.75 0 0 0-.296.205c-.091.104-.143.244-.248.526l-7.43 19.9-7.483-19.903c-.105-.28-.158-.42-.249-.524a.75.75 0 0 0-.296-.205c-.129-.049-.279-.049-.578-.049h-5.769c-.394 0-.591 0-.717.083a.5.5 0 0 0-.213.314c-.031.147.041.33.186.697L174.281 56l-.661 1.6q-.883 1.874-2.041 3.033-1.103 1.158-3.584 1.158-.883 0-1.875-.166a13 13 0 0 1-.73-.1c-.389-.066-.584-.099-.709-.053a.47.47 0 0 0-.26.22c-.066.116-.066.298-.066.663v4.329c0 .243 0 .365.045.481a.7.7 0 0 0 .189.266c.095.081.194.116.392.185q.684.24 1.47.351 1.158.22 2.371.22 4.246 0 7.059-2.426 2.867-2.37 4.577-6.728l10.517-26.58h5.72V55.2c0 .28 0 .42.055.527a.5.5 0 0 0 .218.219M154.363 56c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.219-.219c-.054-.107-.054-.247-.054-.527V15.054c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527V55.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.219c-.107.054-.247.054-.527.054zm-11.621 0c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.219-.219c-.054-.107-.054-.247-.054-.527V15.054c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .219.219c.054.107.054.247.054.527V55.2c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.219c-.107.054-.247.054-.527.054zm-18.132.662q-4.632-.001-8.107-2.096a14.6 14.6 0 0 1-5.404-5.68q-1.93-3.585-1.93-7.942 0-4.522 1.93-7.996 1.985-3.53 5.349-5.57 3.42-2.04 7.61-2.04 4.688 0 7.942 2.04 3.253 1.986 4.963 5.294 1.71 3.309 1.709 7.335 0 .828-.11 1.654-.031.45-.12.841c-.037.165-.055.247-.115.33a.55.55 0 0 1-.208.168c-.095.04-.194.04-.393.04h-21.057q.33 3.309 2.537 5.294 2.205 1.986 5.459 1.985 2.482 0 4.191-1.047a8.2 8.2 0 0 0 2.206-1.986c.241-.316.362-.474.484-.542a.6.6 0 0 1 .352-.083c.139.006.296.083.608.236l4.269 2.094c.239.118.359.176.431.275a.52.52 0 0 1 .098.298c0 .122-.058.231-.172.45q-1.432 2.742-4.526 4.607-3.419 2.04-7.996 2.04m-.552-25.368q-2.702 0-4.687 1.654-1.93 1.6-2.537 4.577h14.118q-.22-2.757-2.151-4.466-1.875-1.765-4.743-1.765M90.801 56c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.218-.218C90 55.62 90 55.48 90 55.2v-5.294c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h1.572q2.646 0 4.19-1.489 1.6-1.545 1.6-4.08V15.715c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.219c.107-.054.247-.054.527-.054h5.956c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527v27.546q0 3.804-1.655 6.672-1.599 2.868-4.632 4.467-2.979 1.6-7.06 1.6z" />
                </g>
                <defs>
                    <linearGradient id="b" x1="12" x2="71.999" y1="30.001" y2="63.002" gradientUnits="userSpaceOnUse">
                        <stop stop-color="#aa5cc3" />
                        <stop offset="1" stop-color="#00a4dc" />
                    </linearGradient>
                    <linearGradient id="c" x1="12" x2="71.999" y1="29.999" y2="63.001" gradientUnits="userSpaceOnUse">
                        <stop stop-color="#aa5cc3" />
                        <stop offset="1" stop-color="#00a4dc" />
                    </linearGradient>
                    <clipPath id="a">
                        <path fill="#fff" d="M0 0h251v72H0z" />
                    </clipPath>
                </defs>
            </svg>

        {{^IF isInReportingMode}}
        <div class="status">
            <div class="mdl-spinner" dir="ltr" aria-hidden="true">
                <div class="mdl-spinner__layer mdl-spinner__layer-1"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle mdl-spinner__circleLeft"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle mdl-spinner__circleRight"></div></div></div>
                <div class="mdl-spinner__layer mdl-spinner__layer-2"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle mdl-spinner__circleLeft"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle mdl-spinner__circleRight"></div></div></div>
                <div class="mdl-spinner__layer mdl-spinner__layer-3"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle mdl-spinner__circleLeft"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle mdl-spinner__circleRight"></div></div></div>
                <div class="mdl-spinner__layer mdl-spinner__layer-4"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle mdl-spinner__circleLeft"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle mdl-spinner__circleRight"></div></div></div>
            </div>
            <p class="status-text">Jellyfin is still starting. Please wait&hellip; {{currentActivity}}</p>
        </div>
        {{#ELSE}}
        <div class="status is-error">
            <p class="status-text">Jellyfin has encountered an error and was not able to start.</p>
        </div>
        {{/ELSE}}
        {{/IF}}

        {{#DECLARE LogEntry |--}}
        {{#LET children = Children}}
        <li class="{{FormatLogLevel(children).ToString()}}-item">
            {{--| #IF children.Count > 0}}
            <details open>
                <summary>{{DateOfCreation}} - {{Content}}</summary>
                <ul class="action-list">
                    {{--| #EACH children |-}}
                    {{#IMPORT 'LogEntry'}}
                    {{--| /EACH |-}}
                </ul>
            </details>
            {{--| #ELSE |-}}
            <span class="{{FormatLogLevel(children).ToString()}}-item">{{DateOfCreation}} - {{Content}}</span>
            {{--| /ELSE |--}}
            {{--| /IF |-}}
        </li>
        {{--| /DECLARE}}

        {{#IF localNetworkRequest}}
        <div class="logs-panel">
            <div class="logs-header">
                <h2>Startup log</h2>
                <a class="download-logs" href='/startup/logger' target="_blank">Download logs</a>
            </div>
            <div class="logs-scroll" id="logs-scroll">
                <ol class="action-list">
                    {{#FOREACH log IN logs}}
                    {{#IMPORT 'LogEntry' #WITH log}}
                    {{/FOREACH}}
                </ol>
            </div>
        </div>
        {{/IF}}
    </div>
    <script>
        (function () {
            var reporting = false;
            {{#IF isInReportingMode}}
            reporting = true;
            {{/IF}}
            var intervalMs = {{ retryValue.TotalMilliseconds }};

            var box = document.getElementById('logs-scroll');
            var panel = box ? box.closest('.logs-panel') : null;

            function px(el, prop) {
                return el ? (parseFloat(getComputedStyle(el)[prop]) || 0) : 0;
            }

            // Size the log viewport so the panel never runs past the bottom of the window
            // (keeping the body's bottom padding as the margin). Recomputed on resize.
            function fit() {
                if (!box) {
                    return;
                }
                var topFromDoc = box.getBoundingClientRect().top + window.scrollY;
                var below = px(panel, 'paddingBottom') + px(document.body, 'paddingBottom');
                box.style.maxHeight = Math.max(window.innerHeight - topFromDoc - below, 120) + 'px';
            }

            function nearBottom(el) {
                return el.scrollHeight - el.scrollTop - el.clientHeight < 24;
            }

            fit();
            window.addEventListener('resize', fit);
            window.addEventListener('load', fit);
            if (box) {
                box.scrollTop = box.scrollHeight; // start pinned to the newest entry
            }

            // In the terminal error state the page is static, so stop here.
            if (reporting) {
                return;
            }

            // Soft refresh: pull the page in the background and swap the log list + activity line in
            // place, so polling never disturbs where the user has scrolled. Only follow to the bottom
            // when the user is already there. A real reload happens only on the final transition.
            function poll() {
                fetch(window.location.href, { cache: 'no-store' }).then(function (resp) {
                    if (resp.ok) {
                        // The real server is now answering (HTTP 200) -> offer to continue to the app.
                        onServerReady();
                        return null;
                    }
                    return resp.text();
                }).then(function (html) {
                    if (!html) {
                        return;
                    }
                    var doc = new DOMParser().parseFromString(html, 'text/html');

                    // Startup failed and the page switched to the error view -> reload to render it.
                    if (doc.querySelector('.status.is-error')) {
                        window.location.reload();
                        return;
                    }

                    var newStatus = doc.querySelector('.status-text');
                    var curStatus = document.querySelector('.status-text');
                    if (newStatus && curStatus) {
                        curStatus.innerHTML = newStatus.innerHTML;
                    }

                    if (box) {
                        var newList = doc.querySelector('#logs-scroll .action-list');
                        var curList = box.querySelector('.action-list');
                        if (newList && curList) {
                            var stick = nearBottom(box);
                            var prevTop = box.scrollTop;
                            curList.replaceWith(document.importNode(newList, true));
                            fit();
                            box.scrollTop = stick ? box.scrollHeight : prevTop;
                        }
                    }
                }).catch(function () {
                    // Server is mid-transition (port rebinding); just try again on the next tick.
                });
            }

            // The server finished starting. Stop polling and present a cancelable countdown so the
            // user can either ride the redirect into the app or stay to review the startup output.
            function onServerReady() {
                clearInterval(pollTimer);

                var status = document.querySelector('.status');
                var statusText = document.querySelector('.status-text');
                var spinner = document.querySelector('.mdl-spinner');
                if (spinner) {
                    spinner.style.display = 'none';
                }
                if (status) {
                    status.classList.add('is-success');
                }
                if (statusText) {
                    statusText.textContent = 'Jellyfin started successfully.';
                }
                if (!status) {
                    window.location.reload();
                    return;
                }

                var bar = document.createElement('div');
                bar.className = 'redirect-bar';
                var countdownText = document.createElement('span');
                countdownText.className = 'redirect-countdown';
                var cancelButton = document.createElement('button');
                cancelButton.type = 'button';
                cancelButton.className = 'jf-button jf-button-secondary';
                cancelButton.textContent = 'Cancel';
                bar.appendChild(countdownText);
                bar.appendChild(cancelButton);
                status.insertAdjacentElement('afterend', bar);

                var remaining = 5;
                function renderCountdown() {
                    countdownText.textContent = 'Redirecting in ' + remaining + '…';
                }
                renderCountdown();
                var countdown = setInterval(function () {
                    remaining -= 1;
                    if (remaining <= 0) {
                        clearInterval(countdown);
                        window.location.reload();
                        return;
                    }
                    renderCountdown();
                }, 1000);

                // Cancel stops both the redirect and the refreshing, and offers a manual continue.
                cancelButton.addEventListener('click', function () {
                    clearInterval(countdown);
                    bar.innerHTML = '';
                    var continueButton = document.createElement('button');
                    continueButton.type = 'button';
                    continueButton.className = 'jf-button jf-button-primary';
                    continueButton.textContent = 'Continue to Jellyfin';
                    continueButton.addEventListener('click', function () {
                        window.location.reload();
                    });
                    bar.appendChild(continueButton);
                });
            }

            var pollTimer = setInterval(poll, intervalMs);
        })();
    </script>
</body>

</html>
