420 lines
10 KiB
HTML
420 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, viewport-fit=cover">
|
|
<title>$GODOT_PROJECT_NAME</title>
|
|
<script>
|
|
(function () {
|
|
let savedTheme = null;
|
|
try {
|
|
savedTheme = window.localStorage.getItem('medmind-color-scheme');
|
|
} catch (error) {
|
|
console.warn('Theme preference storage is unavailable.', error);
|
|
}
|
|
const systemTheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
? 'dark'
|
|
: 'light';
|
|
document.documentElement.dataset.theme = savedTheme === 'dark' || savedTheme === 'light'
|
|
? savedTheme
|
|
: systemTheme;
|
|
}());
|
|
</script>
|
|
<style>
|
|
@font-face {
|
|
font-family: "Inter";
|
|
src: url("medmind-inter.ttf") format("truetype");
|
|
font-style: normal;
|
|
font-weight: 100 900;
|
|
font-display: swap;
|
|
}
|
|
|
|
:root {
|
|
color-scheme: light;
|
|
--background: #f8faff;
|
|
--background-accent: rgba(124, 58, 237, 0.12);
|
|
--background-accent-secondary: rgba(37, 99, 235, 0.08);
|
|
--dot: rgba(99, 102, 241, 0.26);
|
|
--brand-dark: #0b1324;
|
|
--muted: #64748b;
|
|
--track: #e8eaf7;
|
|
--error-background: rgba(255, 255, 255, 0.94);
|
|
--error-border: #ef4444;
|
|
--error-text: #991b1b;
|
|
}
|
|
|
|
:root[data-theme="dark"] {
|
|
color-scheme: dark;
|
|
--background: #020b1d;
|
|
--background-accent: rgba(124, 58, 237, 0.3);
|
|
--background-accent-secondary: rgba(37, 99, 235, 0.18);
|
|
--dot: rgba(61, 91, 210, 0.68);
|
|
--brand-dark: #f8fafc;
|
|
--muted: #94a3b8;
|
|
--track: #111c38;
|
|
--error-background: rgba(17, 27, 48, 0.96);
|
|
--error-border: #fb7185;
|
|
--error-text: #fecdd3;
|
|
}
|
|
|
|
html, body, #canvas {
|
|
border: 0;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
html, body {
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
|
|
body {
|
|
background: var(--background);
|
|
color: var(--brand-dark);
|
|
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
overflow: hidden;
|
|
touch-action: none;
|
|
}
|
|
|
|
#canvas {
|
|
display: block;
|
|
}
|
|
|
|
#canvas:focus {
|
|
outline: none;
|
|
}
|
|
|
|
#status {
|
|
align-items: center;
|
|
background:
|
|
radial-gradient(circle at 0% 100%, var(--background-accent-secondary) 0, transparent 31%),
|
|
radial-gradient(circle at 100% 0%, var(--background-accent) 0, transparent 32%),
|
|
var(--background);
|
|
display: flex;
|
|
inset: 0;
|
|
justify-content: center;
|
|
min-height: 100%;
|
|
position: fixed;
|
|
transition: opacity 180ms ease;
|
|
visibility: hidden;
|
|
z-index: 10;
|
|
}
|
|
|
|
#status::before,
|
|
#status::after {
|
|
background-image: radial-gradient(circle, var(--dot) 0 3px, transparent 3.5px);
|
|
background-size: 27px 27px;
|
|
content: "";
|
|
height: 150px;
|
|
position: absolute;
|
|
width: 180px;
|
|
z-index: 1;
|
|
}
|
|
|
|
#status::before {
|
|
left: clamp(24px, 3vw, 64px);
|
|
top: clamp(22px, 5vh, 64px);
|
|
}
|
|
|
|
#status::after {
|
|
bottom: clamp(22px, 5vh, 64px);
|
|
right: clamp(24px, 3vw, 64px);
|
|
}
|
|
|
|
.loader {
|
|
align-items: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 32px;
|
|
position: relative;
|
|
width: min(640px, calc(100vw - 48px));
|
|
z-index: 2;
|
|
}
|
|
|
|
.corner-accent {
|
|
border-radius: 50%;
|
|
height: min(42vw, 430px);
|
|
pointer-events: none;
|
|
position: absolute;
|
|
width: min(42vw, 430px);
|
|
z-index: 0;
|
|
}
|
|
|
|
.corner-accent--top {
|
|
background: radial-gradient(circle at 35% 65%, rgba(124, 58, 237, 0.04), rgba(99, 102, 241, 0.13));
|
|
right: max(-150px, -10vw);
|
|
top: max(-180px, -13vw);
|
|
}
|
|
|
|
.corner-accent--bottom {
|
|
background: radial-gradient(circle at 65% 35%, rgba(124, 58, 237, 0.02), rgba(99, 102, 241, 0.1));
|
|
bottom: max(-210px, -16vw);
|
|
left: max(-180px, -13vw);
|
|
}
|
|
|
|
:root[data-theme="dark"] .corner-accent--top {
|
|
background: radial-gradient(circle at 30% 70%, rgba(124, 58, 237, 0.18), rgba(49, 46, 129, 0.72));
|
|
}
|
|
|
|
:root[data-theme="dark"] .corner-accent--bottom {
|
|
background: radial-gradient(circle at 70% 30%, rgba(37, 99, 235, 0.1), rgba(30, 58, 138, 0.55));
|
|
}
|
|
|
|
.loader-logo-wrap {
|
|
height: clamp(112px, 16vw, 154px);
|
|
position: relative;
|
|
width: clamp(112px, 16vw, 154px);
|
|
}
|
|
|
|
.loader-logo {
|
|
display: block;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
width: 100%;
|
|
}
|
|
|
|
.loader-logo-wrap::before,
|
|
.loader-logo-wrap::after {
|
|
background: #3154c4;
|
|
border-radius: 2px;
|
|
content: "";
|
|
left: 50%;
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.loader-logo-wrap::before {
|
|
height: 8%;
|
|
width: 19%;
|
|
}
|
|
|
|
.loader-logo-wrap::after {
|
|
height: 19%;
|
|
width: 8%;
|
|
}
|
|
|
|
:root[data-theme="dark"] .loader-logo-wrap::before,
|
|
:root[data-theme="dark"] .loader-logo-wrap::after {
|
|
background: #ffffff;
|
|
}
|
|
|
|
.loader-wordmark {
|
|
font-size: clamp(52px, 7vw, 78px);
|
|
font-weight: 650;
|
|
letter-spacing: 0;
|
|
line-height: 1;
|
|
margin: 12px 0 0;
|
|
}
|
|
|
|
.wordmark-med {
|
|
color: var(--brand-dark);
|
|
}
|
|
|
|
.wordmark-mind {
|
|
background: linear-gradient(90deg, #2563eb 0%, #7c3aed 58%, #d946ef 100%);
|
|
background-clip: text;
|
|
color: transparent;
|
|
-webkit-background-clip: text;
|
|
}
|
|
|
|
.loader-tagline {
|
|
color: var(--muted);
|
|
font-size: clamp(20px, 2.3vw, 28px);
|
|
font-weight: 430;
|
|
letter-spacing: 0;
|
|
margin: 17px 0 clamp(36px, 6vh, 54px);
|
|
}
|
|
|
|
.progress-row {
|
|
align-items: center;
|
|
display: flex;
|
|
gap: 18px;
|
|
width: 100%;
|
|
}
|
|
|
|
.progress-track {
|
|
background: var(--track);
|
|
border-radius: 999px;
|
|
flex: 1;
|
|
height: 16px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-value {
|
|
background: linear-gradient(90deg, #2563eb 0%, #7c3aed 60%, #a855f7 100%);
|
|
border-radius: inherit;
|
|
height: 100%;
|
|
transform-origin: left center;
|
|
transition: width 120ms ease-out;
|
|
width: 0%;
|
|
}
|
|
|
|
.progress-percent {
|
|
color: #3563f4;
|
|
font-size: 22px;
|
|
font-variant-numeric: tabular-nums;
|
|
font-weight: 650;
|
|
min-width: 60px;
|
|
}
|
|
|
|
.loader-message {
|
|
color: var(--muted);
|
|
font-size: 19px;
|
|
letter-spacing: 0;
|
|
margin: 24px 0 0;
|
|
text-align: center;
|
|
}
|
|
|
|
#status-notice {
|
|
background: var(--error-background);
|
|
border: 1px solid var(--error-border);
|
|
border-radius: 8px;
|
|
color: var(--error-text);
|
|
display: none;
|
|
font-size: 16px;
|
|
line-height: 1.5;
|
|
margin-top: 28px;
|
|
max-height: min(240px, 35vh);
|
|
overflow: auto;
|
|
padding: 16px 20px;
|
|
text-align: center;
|
|
width: min(540px, calc(100vw - 80px));
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
#status::before,
|
|
#status::after {
|
|
background-size: 20px 20px;
|
|
height: 100px;
|
|
opacity: 0.72;
|
|
width: 120px;
|
|
}
|
|
|
|
.loader {
|
|
padding: 24px;
|
|
width: min(430px, calc(100vw - 32px));
|
|
}
|
|
|
|
.loader-wordmark {
|
|
font-size: clamp(46px, 15vw, 66px);
|
|
}
|
|
|
|
.progress-row {
|
|
gap: 12px;
|
|
}
|
|
|
|
.progress-track {
|
|
height: 13px;
|
|
}
|
|
|
|
.progress-percent {
|
|
font-size: 18px;
|
|
min-width: 48px;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#status,
|
|
.progress-value {
|
|
transition: none;
|
|
}
|
|
}
|
|
</style>
|
|
$GODOT_HEAD_INCLUDE
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas">
|
|
Your browser does not support the canvas tag.
|
|
</canvas>
|
|
|
|
<noscript>Your browser does not support JavaScript.</noscript>
|
|
|
|
<div id="status" role="status" aria-live="polite">
|
|
<div class="corner-accent corner-accent--top"></div>
|
|
<div class="corner-accent corner-accent--bottom"></div>
|
|
<div class="loader">
|
|
<div class="loader-logo-wrap">
|
|
<img class="loader-logo" src="medmind-loader-icon.png" alt="MedMind">
|
|
</div>
|
|
<div class="loader-wordmark" aria-label="MedMind">
|
|
<span class="wordmark-med">Med</span><span class="wordmark-mind">Mind</span>
|
|
</div>
|
|
<p class="loader-tagline">Think like a physician.</p>
|
|
<div id="status-progress" class="progress-row" role="progressbar" aria-label="Loading MedMind" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
|
|
<div class="progress-track"><div id="status-progress-value" class="progress-value"></div></div>
|
|
<span id="status-progress-percent" class="progress-percent">0%</span>
|
|
</div>
|
|
<p id="status-message" class="loader-message">Loading your study experience...</p>
|
|
<div id="status-notice" role="alert"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="$GODOT_URL"></script>
|
|
<script>
|
|
const GODOT_CONFIG = $GODOT_CONFIG;
|
|
const engine = new Engine(GODOT_CONFIG);
|
|
|
|
(function () {
|
|
const statusOverlay = document.getElementById('status');
|
|
const statusProgress = document.getElementById('status-progress');
|
|
const progressValue = document.getElementById('status-progress-value');
|
|
const progressPercent = document.getElementById('status-progress-percent');
|
|
const statusMessage = document.getElementById('status-message');
|
|
const statusNotice = document.getElementById('status-notice');
|
|
let initializing = true;
|
|
|
|
function showLoader() {
|
|
statusOverlay.style.visibility = 'visible';
|
|
}
|
|
|
|
function updateProgress(current, total) {
|
|
const percentage = current > 0 && total > 0
|
|
? Math.max(0, Math.min(100, Math.round((current / total) * 100)))
|
|
: 0;
|
|
progressValue.style.width = percentage + '%';
|
|
progressPercent.textContent = percentage + '%';
|
|
statusProgress.setAttribute('aria-valuenow', String(percentage));
|
|
}
|
|
|
|
function displayFailureNotice(error) {
|
|
console.error(error);
|
|
const message = error instanceof Error
|
|
? error.message
|
|
: (typeof error === 'string' ? error : 'An unknown error occurred.');
|
|
statusProgress.style.display = 'none';
|
|
statusMessage.textContent = 'MedMind could not finish loading.';
|
|
statusNotice.textContent = message;
|
|
statusNotice.style.display = 'block';
|
|
initializing = false;
|
|
}
|
|
|
|
showLoader();
|
|
const missing = Engine.getMissingFeatures({ threads: false });
|
|
if (missing.length !== 0) {
|
|
displayFailureNotice('Required browser features are unavailable: ' + missing.join(', '));
|
|
return;
|
|
}
|
|
|
|
engine.startGame({
|
|
onProgress: updateProgress,
|
|
}).then(function () {
|
|
if (!initializing) {
|
|
return;
|
|
}
|
|
statusOverlay.style.opacity = '0';
|
|
statusOverlay.addEventListener('transitionend', function () {
|
|
statusOverlay.remove();
|
|
}, { once: true });
|
|
window.setTimeout(function () {
|
|
if (statusOverlay.isConnected) {
|
|
statusOverlay.remove();
|
|
}
|
|
}, 250);
|
|
initializing = false;
|
|
}, displayFailureNotice);
|
|
}());
|
|
</script>
|
|
</body>
|
|
</html>
|