Files
step3/web/medmind_shell.html
T

394 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-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>