Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 46s
Forgejo Docker Build / Root app tests (push) Successful in 56s
Forgejo Android APK / Build signed APK (push) Successful in 2m6s
Forgejo Docker Build / Build Docker image (push) Successful in 15s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
The sign-in screen asks for an email first, then offers both ways in together: a six-digit code sent to that address, or the password. Beside rather than instead — a code depends on mail being delivered and a password does not, so neither may be the only route. "Use a different email" goes back a step, and creating an account stays where it was. What keeps it from being a second, weaker front door: - Only a bcrypt hash is stored, so a code read out of the database is not a working credential. - Ten minutes, single use, marked used before the session is issued so a replay cannot race it, and requesting a new one deletes the old. - Five wrong guesses burn it. Six digits is a million possibilities, which is plenty against a person and nothing against a script with unlimited tries. - Requesting a code answers identically whether or not the address exists, and every verify failure returns one message. A sign-in screen that says "no such account" is a way of finding out who has one. - Two-factor still applies: a code proves you can read the mailbox, which is one factor, and an account that asked for a second still wants it. - Its own rate limits, tighter for requesting than for attempting, because requesting sends mail to someone else's address. These had to be separate limiters: Express matches app.use paths on segment boundaries, so /api/auth/login does not cover /api/auth/login-code — checked against a real router rather than assumed. Two bugs found while building it, both mine: authFetch keeps an allowlist of endpoints callable with no verified owner and rejects everything else before it is sent. The new endpoints were not on it, so the request never left the browser and surfaced as "Connection error". reveal() hid elements by appending 'hidden' to className and showed them with a non-global replace, so hiding twice left two copies and showing stripped one. The "use a different email" link never reappeared. It uses classList now, which is idempotent. Verified against the running server: correct code signs in, the same code again is refused, a superseded code is refused, five wrong guesses burn it, an expired one is refused, and the stored value is a hash. In the browser: requesting a code advances the screen, a wrong code is refused without losing the screen, and the password route still signs in. Not yet demonstrated: a correct code typed into the browser. The harness keeps racing the one-live-code rule — the page's own request supersedes whatever code the test holds, and with SMTP off the delivered one cannot be read. The same request reaches the server on the wrong-code path, and the endpoint itself is verified, but that last step is untested end to end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
546 lines
31 KiB
HTML
546 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content">
|
|
<title>Pediatric Clinical Tools</title>
|
|
<link rel="stylesheet" href="/css/styles.css">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link rel="preconnect" href="https://cdnjs.cloudflare.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
|
integrity="sha384-/o6I2CkkWC//PSjvWC/eYN7l3xM3tJm8ZzVkCOfp//W05QcE3mlGskpoHB6XqI+B" crossorigin="anonymous" referrerpolicy="no-referrer">
|
|
<!-- Explicit render mode: the register/forgot widgets live inside forms that
|
|
start hidden, and Turnstile's implicit auto-render does not reliably
|
|
complete a challenge inside a display:none container. auth.js renders
|
|
each widget the first time its form is shown. -->
|
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
|
|
<script src="/vendor/dompurify/purify.min.js"></script>
|
|
<link rel="stylesheet" href="/vendor/katex/katex.min.css">
|
|
<script src="/vendor/marked/marked.umd.js" defer></script>
|
|
<script src="/vendor/markdown-it/markdown-it.min.js" defer></script>
|
|
<script src="/vendor/katex/katex.min.js" defer></script>
|
|
<script src="/vendor/katex/contrib/mhchem.min.js" defer></script>
|
|
<script src="/vendor/mathjax/tex-mml-chtml.js" defer></script>
|
|
<link rel="manifest" href="/manifest.json">
|
|
<meta name="theme-color" content="#ffffff">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="PedScribe">
|
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16.png">
|
|
<link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192.png">
|
|
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512.png">
|
|
<style>
|
|
html.account-transition body > :not(#account-recovery) { display: none !important; }
|
|
#account-recovery { padding: 2rem; font-family: sans-serif; }
|
|
</style>
|
|
<script src="/js/accountBoundary.js"></script>
|
|
<script type="module" src="/js/authFetch.js"></script>
|
|
<!-- Auth screen hidden by default — auth.js shows it only when no valid session exists -->
|
|
<style>#auth-screen { display: none; }</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- AUTH SCREEN -->
|
|
<div id="auth-screen" class="auth-screen">
|
|
<div class="auth-box">
|
|
<div class="auth-logo">
|
|
<i class="fas fa-stethoscope"></i>
|
|
<p>AI-Powered Pediatric Practice</p>
|
|
</div>
|
|
|
|
<!-- Login Form -->
|
|
<form id="login-form" class="auth-form" autocomplete="off">
|
|
<h2>Sign In</h2>
|
|
<!-- Biometric quick-login (Capacitor only). Hidden by default;
|
|
revealed by auth.js when isNativeApp() + biometric available
|
|
+ credentials previously enrolled. -->
|
|
<button type="button" id="btn-bio-login" class="btn-auth btn-bio-login hidden" style="display:none;">
|
|
<i class="fas fa-fingerprint"></i> <span id="bio-login-label">Sign in with biometric</span>
|
|
</button>
|
|
<div id="bio-divider" class="hidden" style="display:none;text-align:center;margin:6px 0 12px;position:relative;">
|
|
<span style="background:white;padding:0 12px;color:#9ca3af;font-size:12px;position:relative;z-index:1;">or use password</span>
|
|
<hr style="border:none;border-top:1px solid #e5e7eb;position:absolute;top:50%;left:0;right:0;margin:0;">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Email</label>
|
|
<input type="email" id="login-email" required placeholder="your@email.com">
|
|
</div>
|
|
<!-- Email first. Both ways in are then offered together, because a
|
|
code depends on mail arriving and a password does not, so neither
|
|
can be the only route. -->
|
|
<button type="button" class="btn-auth" id="btn-login-continue">Continue</button>
|
|
|
|
<div id="login-choice" class="hidden" style="display:none;gap:8px;flex-direction:column;margin-top:2px;">
|
|
<button type="button" class="btn-auth" id="btn-login-send-code">
|
|
<i class="fas fa-envelope"></i> Email me a sign-in code
|
|
</button>
|
|
<button type="button" class="btn-auth" id="btn-login-use-password"
|
|
style="background:none;color:#2563eb;border:1px solid #e5e7eb;">
|
|
Use my password instead
|
|
</button>
|
|
</div>
|
|
|
|
<div id="login-code-group" class="form-group hidden">
|
|
<label>Sign-in code</label>
|
|
<input type="text" id="login-code" placeholder="6-digit code" maxlength="6"
|
|
inputmode="numeric" autocomplete="one-time-code">
|
|
<p id="login-code-hint" style="margin:6px 0 0;font-size:12px;color:#6b7280;"></p>
|
|
</div>
|
|
|
|
<div id="login-password-group" class="form-group hidden">
|
|
<label>Password</label>
|
|
<input type="password" id="login-password" placeholder="••••••••">
|
|
</div>
|
|
<div id="totp-group" class="form-group hidden">
|
|
<label>2FA Code</label>
|
|
<input type="text" id="login-totp" placeholder="6-digit code" maxlength="6">
|
|
</div>
|
|
<button type="submit" class="btn-auth hidden" id="btn-local-login" style="display:none;">Sign In</button>
|
|
<div id="sso-divider" class="hidden" style="display:none;text-align:center;margin:16px 0 12px;position:relative;">
|
|
<span style="background:white;padding:0 12px;color:#9ca3af;font-size:12px;position:relative;z-index:1;">or</span>
|
|
<hr style="border:none;border-top:1px solid #e5e7eb;position:absolute;top:50%;left:0;right:0;margin:0;">
|
|
</div>
|
|
<a href="/api/auth/oidc" id="btn-sso" class="btn-auth" style="display:none;text-align:center;text-decoration:none;background:linear-gradient(135deg,#0f172a,#334155);margin-top:0;">
|
|
<i class="fas fa-shield-halved"></i> <span id="sso-label">Sign in with SSO</span>
|
|
</a>
|
|
<div id="resend-verify-box" class="hidden" style="margin:12px 0;padding:12px;background:#fef3c7;border-radius:8px;text-align:center;">
|
|
<p style="margin:0 0 8px;font-size:13px;color:#92400e;">Your email is not verified yet.</p>
|
|
<a href="#" id="resend-verify-link" style="font-size:13px;font-weight:600;color:#2563eb;">Resend verification link</a>
|
|
</div>
|
|
<div class="auth-links">
|
|
<a href="#" id="show-register" style="display:none">Create account</a>
|
|
<a href="#" id="show-forgot">Forgot password?</a>
|
|
<a href="#" id="login-change-email" class="hidden" style="display:none;">Use a different email</a>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Register Form -->
|
|
<form id="register-form" class="auth-form hidden">
|
|
<h2>Create Account</h2>
|
|
<div class="form-group">
|
|
<label>Full Name</label>
|
|
<input type="text" id="reg-name" required placeholder="Dr. Jane Smith">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Email</label>
|
|
<input type="email" id="reg-email" required placeholder="your@email.com">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Password (8+ characters)</label>
|
|
<input type="password" id="reg-password" required minlength="8" placeholder="••••••••">
|
|
</div>
|
|
<!-- Shown only when the server says registration is invite-only. -->
|
|
<div class="form-group hidden" id="reg-invite-group">
|
|
<label>Invitation code</label>
|
|
<input type="text" id="reg-invite" placeholder="XXXX-XXXX-XXXX-XXXX" autocomplete="off" spellcheck="false">
|
|
</div>
|
|
<div id="turnstile-register" data-sitekey="0x4AAAAAAC0VtKAhC8rzpMx6"></div>
|
|
<button type="submit" class="btn-auth">Create Account</button>
|
|
<div class="auth-links">
|
|
<a href="#" id="show-login">Back to sign in</a>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Forgot Password Form -->
|
|
<form id="forgot-form" class="auth-form hidden">
|
|
<h2>Reset Password</h2>
|
|
<div class="form-group">
|
|
<label>Email</label>
|
|
<input type="email" id="forgot-email" required placeholder="your@email.com">
|
|
</div>
|
|
<div id="turnstile-forgot" data-sitekey="0x4AAAAAAC0VtKAhC8rzpMx6"></div>
|
|
<button type="submit" class="btn-auth">Send Reset Link</button>
|
|
<div class="auth-links">
|
|
<a href="#" id="show-login-2">Back to sign in</a>
|
|
</div>
|
|
</form>
|
|
|
|
</div>
|
|
<footer class="app-footer">
|
|
Committed to healthcare equity — always consult a qualified healthcare professional for medical advice.
|
|
•
|
|
© 2024-2026 Pediatric Clinical Tools by PedsHub. All rights reserved.
|
|
</footer>
|
|
</div>
|
|
|
|
<!-- MAIN APP (hidden until authenticated) -->
|
|
<div id="main-app" class="hidden">
|
|
|
|
<!-- No header bar. Settings and Log out live in the account card at the foot
|
|
of the menu, the brand sits at its head, and the mobile menu button
|
|
floats — so a whole band of chrome earned its place back as content.
|
|
btn-settings and btn-logout stay as the canonical handlers other code
|
|
already calls; they are hidden, not duplicated. -->
|
|
<div class="mobile-topbar" aria-hidden="true"></div>
|
|
<button id="btn-menu-toggle" class="btn-menu-toggle" title="Menu" aria-label="Toggle menu"><i class="fas fa-bars"></i></button>
|
|
<button id="btn-settings" hidden></button>
|
|
<button id="btn-logout" hidden></button>
|
|
|
|
<!-- ANNOUNCEMENT BANNER -->
|
|
<div id="announcement-banner" class="announcement-banner hidden" role="alert">
|
|
<i id="announcement-icon" class="fas fa-info-circle"></i>
|
|
<span id="announcement-text"></span>
|
|
<button id="announcement-close" class="announcement-close" aria-label="Dismiss"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
|
|
<div class="app-body">
|
|
|
|
<!-- SIDEBAR NAVIGATION -->
|
|
<nav class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<span class="sidebar-title"><i class="fas fa-stethoscope"></i> Menu</span>
|
|
<button id="btn-sidebar-close" class="btn-sm btn-ghost" title="Close menu" style="padding:4px 8px;">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="sidebar-nav">
|
|
<!-- Brand, collapse and search share one row, as in the reference UIs.
|
|
Three stacked full-width rows wasted the top of the menu. -->
|
|
<div class="menu-head">
|
|
<button type="button" class="menu-icon-btn assistant-menu-toggle" data-menu-toggle aria-expanded="true" title="Hide menu" aria-label="Hide menu"><i class="fas fa-angles-left"></i></button>
|
|
<div class="menu-brand">
|
|
<i class="fas fa-stethoscope"></i>
|
|
<h1>PedAI</h1>
|
|
</div>
|
|
<button type="button" class="menu-icon-btn" data-menu-search title="Search" aria-label="Search"><i class="fas fa-magnifying-glass"></i></button>
|
|
</div>
|
|
<!-- The app IS workspace mode. One switch, shown in both places and
|
|
styled identically, so moving between them is a highlight change
|
|
rather than a different-looking screen. -->
|
|
<div class="assistant-mode-switch" role="tablist" aria-label="Assistant or workspace">
|
|
<button type="button" role="tab" aria-selected="false" data-assistant-mode="assistant"><i class="fas fa-brain"></i> Assistant</button>
|
|
<button type="button" role="tab" class="active" aria-selected="true" data-assistant-mode="workspace"><i class="fas fa-grip"></i> Workspace</button>
|
|
</div>
|
|
<div class="sidebar-tabs">
|
|
<span class="sidebar-section-label">Encounters</span>
|
|
<button class="tab-btn active" data-tab="encounter">
|
|
<i class="fas fa-comments"></i>
|
|
<span>Encounter HPI</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="dictation">
|
|
<i class="fas fa-microphone"></i>
|
|
<span>Dictation HPI</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="ed">
|
|
<i class="fas fa-truck-medical"></i>
|
|
<span>ED Encounter</span>
|
|
</button>
|
|
<span class="sidebar-section-label">Notes</span>
|
|
<button class="tab-btn" data-tab="hospital">
|
|
<i class="fas fa-hospital"></i>
|
|
<span>Hospital Course</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="chart">
|
|
<i class="fas fa-clipboard-list"></i>
|
|
<span>Chart Review</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="soap">
|
|
<i class="fas fa-file-waveform"></i>
|
|
<span>SOAP Note</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="wellvisit">
|
|
<i class="fas fa-calendar-check"></i>
|
|
<span>Well Visit</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="sickvisit">
|
|
<i class="fas fa-thermometer-half"></i>
|
|
<span>Sick Visit</span>
|
|
</button>
|
|
<span class="sidebar-section-label">Clinical Tools</span>
|
|
<button class="tab-btn" data-tab="vaxschedule">
|
|
<i class="fas fa-table"></i>
|
|
<span>Vaccine Schedule</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="catchup">
|
|
<i class="fas fa-rotate"></i>
|
|
<span>Catch-Up Schedule</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="peguide">
|
|
<i class="fas fa-stethoscope"></i>
|
|
<span>Physical Exam Guide</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="bedside">
|
|
<i class="fas fa-briefcase-medical" style="color:var(--red);"></i>
|
|
<span>Bedside</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="calculators">
|
|
<i class="fas fa-calculator"></i>
|
|
<span>Calculators</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="extensions">
|
|
<i class="fas fa-phone-volume"></i>
|
|
<span>Pagers & Extensions</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="notes">
|
|
<i class="fas fa-note-sticky"></i>
|
|
<span>Notes</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="diagrams">
|
|
<i class="fas fa-diagram-project"></i>
|
|
<span>Diagrams</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="myresources">
|
|
<i class="fas fa-folder-open"></i>
|
|
<span>My Resources</span>
|
|
</button>
|
|
<button class="tab-btn" data-tab="learning">
|
|
<i class="fas fa-graduation-cap"></i>
|
|
<span>Learning Hub</span>
|
|
</button>
|
|
<button class="tab-btn hidden" data-tab="cms" id="cms-tab-btn">
|
|
<i class="fas fa-pen-to-square"></i>
|
|
<span>Content Manager</span>
|
|
</button>
|
|
<button class="tab-btn hidden" data-tab="docs" id="docs-tab-btn">
|
|
<i class="fas fa-book"></i>
|
|
<span>Docs</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- Account card. The same markup renders in the assistant rail, so the
|
|
two menus end the same way. Settings and Log out were only reachable
|
|
from icon buttons in the header, which the slim bar no longer has
|
|
room to explain. -->
|
|
<div class="account-card">
|
|
<button type="button" class="account-card-btn" id="btn-account-menu" aria-haspopup="true" aria-expanded="false" aria-controls="account-menu">
|
|
<span class="account-avatar" id="account-avatar" aria-hidden="true"></span>
|
|
<span class="account-id">
|
|
<span class="account-name" id="account-name"></span>
|
|
<span class="account-email" id="account-email"></span>
|
|
</span>
|
|
<i class="fas fa-chevron-right account-chevron"></i>
|
|
</button>
|
|
<div class="account-menu" id="account-menu" role="menu" hidden>
|
|
<button type="button" class="account-menu-item" role="menuitem" data-account-tab="settings"><i class="fas fa-gear"></i> Settings</button>
|
|
<button type="button" class="account-menu-item" role="menuitem" data-account-tab="faq"><i class="fas fa-circle-question"></i> FAQ</button>
|
|
<button type="button" class="account-menu-item" role="menuitem" data-account-logout><i class="fas fa-right-from-bracket"></i> Log out</button>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- One palette, both views. The view decides what it searches: the app menu
|
|
here, saved chats in the assistant. -->
|
|
<div class="menu-search" id="menu-search" hidden role="dialog" aria-modal="true" aria-label="Search">
|
|
<div class="menu-search-panel">
|
|
<div class="menu-search-head">
|
|
<i class="fas fa-magnifying-glass"></i>
|
|
<input type="search" id="menu-search-input" autocomplete="off" placeholder="Search...">
|
|
<button type="button" id="menu-search-close" aria-label="Close"><i class="fas fa-xmark"></i></button>
|
|
</div>
|
|
<div class="menu-search-results" id="menu-search-results" role="listbox"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-overlay" id="sidebar-overlay"></div>
|
|
|
|
<main class="main-content">
|
|
<div id="busy-bar" class="busy-bar">
|
|
<div class="busy-spinner"></div>
|
|
<span class="busy-text" id="busy-text">Processing...</span>
|
|
</div>
|
|
|
|
|
|
<!-- Tab components (lazy-loaded from /components/*.html) -->
|
|
<section id="encounter-tab" class="tab-content active" data-component="encounter"></section>
|
|
<section id="dictation-tab" class="tab-content" data-component="dictation"></section>
|
|
<section id="hospital-tab" class="tab-content" data-component="hospital"></section>
|
|
<section id="chart-tab" class="tab-content" data-component="chart"></section>
|
|
<section id="soap-tab" class="tab-content" data-component="soap"></section>
|
|
<section id="vaxschedule-tab" class="tab-content" data-component="vaxschedule"></section>
|
|
<section id="catchup-tab" class="tab-content" data-component="catchup"></section>
|
|
<section id="peguide-tab" class="tab-content" data-component="pe-guide"></section>
|
|
<section id="bedside-tab" class="tab-content" data-component="bedside"></section>
|
|
<section id="calculators-tab" class="tab-content" data-component="calculators"></section>
|
|
<section id="extensions-tab" class="tab-content" data-component="extensions"></section>
|
|
<section id="notes-tab" class="tab-content" data-component="notes"></section>
|
|
<section id="myresources-tab" class="tab-content" data-component="my-resources"></section>
|
|
<section id="diagrams-tab" class="tab-content" data-component="diagrams"></section>
|
|
<section id="assistant-tab" class="tab-content" data-component="assistant"></section>
|
|
<section id="learning-tab" class="tab-content" data-component="learning"></section>
|
|
<section id="cms-tab" class="tab-content" data-component="cms"></section>
|
|
<section id="docs-tab" class="tab-content" data-component="admin-docs"></section>
|
|
<section id="admin-tab" class="tab-content" data-component="admin"></section>
|
|
<section id="wellvisit-tab" class="tab-content" data-component="wellvisit"></section>
|
|
<section id="sickvisit-tab" class="tab-content" data-component="sickvisit"></section>
|
|
<section id="ed-tab" class="tab-content" data-component="ed-encounter"></section>
|
|
<section id="settings-tab" class="tab-content" data-component="settings"></section>
|
|
<section id="faq-tab" class="tab-content" data-component="faq"></section>
|
|
|
|
</main>
|
|
|
|
</div><!-- /.app-body -->
|
|
|
|
|
|
<!-- About Modal -->
|
|
<div id="about-modal" class="confirm-modal hidden">
|
|
<div class="confirm-modal-box" style="max-width:600px;max-height:80vh;overflow-y:auto;">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
<h2 style="margin:0;font-size:18px;color:var(--g800);"><i class="fas fa-stethoscope" style="color:var(--blue);margin-right:8px;"></i>Pediatric Clinical Tools</h2>
|
|
<button id="btn-close-about" class="btn-sm btn-ghost" style="padding:4px 8px;"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<p style="color:var(--g600);line-height:1.7;font-size:13px;margin:0 0 14px;">An AI-powered clinical documentation platform designed specifically for pediatric medicine. Built to help physicians spend less time on documentation and more time with patients.</p>
|
|
|
|
<h4 style="font-size:13px;color:var(--blue-dark);margin:16px 0 8px;">Clinical Documentation</h4>
|
|
<ul style="font-size:12.5px;color:var(--g700);line-height:1.8;padding-left:18px;margin:0 0 12px;">
|
|
<li><strong>Live Encounter</strong> — Record doctor-patient conversations, AI generates structured HPI using OLDCARTS framework</li>
|
|
<li><strong>Voice Dictation</strong> — Dictate your narrative, AI restructures into clean clinical documentation</li>
|
|
<li><strong>SOAP Notes</strong> — Full SOAP or subjective-only from transcript or dictation</li>
|
|
<li><strong>Hospital Course</strong> — Prose, day-by-day, organ-system (ICU), or psych format from progress notes</li>
|
|
<li><strong>Chart Review</strong> — Summarize outpatient, subspecialty, and ED visits for precharting</li>
|
|
<li><strong>Well Visit</strong> — AAP Bright Futures with SSHADESS, milestones, vaccines, and billing codes</li>
|
|
<li><strong>Sick Visit</strong> — Quick documentation with auto-suggested ROS and PE</li>
|
|
</ul>
|
|
|
|
<h4 style="font-size:13px;color:var(--blue-dark);margin:16px 0 8px;">Pediatric Calculators</h4>
|
|
<ul style="font-size:12.5px;color:var(--g700);line-height:1.8;padding-left:18px;margin:0 0 12px;">
|
|
<li><strong>BP Percentile</strong> — AAP 2017 with Rosner spline regression, height-adjusted</li>
|
|
<li><strong>BMI Percentile</strong> — CDC 2000 with extended obesity classification and visual chart</li>
|
|
<li><strong>Growth Charts</strong> — Visual WHO/CDC/Fenton curves with patient plotting and mid-parental height</li>
|
|
<li><strong>Bilirubin</strong> — AAP 2022 phototherapy thresholds and Bhutani nomogram</li>
|
|
<li><strong>Vital Signs</strong> — Harriet Lane reference with quick formulas (ETT, fluids, weight estimation)</li>
|
|
<li><strong>BSA & Dosing</strong> — Mosteller BSA and weight-based dose calculator with volume</li>
|
|
</ul>
|
|
|
|
<h4 style="font-size:13px;color:var(--blue-dark);margin:16px 0 8px;">AI & Speech</h4>
|
|
<ul style="font-size:12.5px;color:var(--g700);line-height:1.8;padding-left:18px;margin:0 0 12px;">
|
|
<li>Multiple AI providers with HIPAA-compliant options (BAA-covered)</li>
|
|
<li>Multiple transcription engines including medical-specific models</li>
|
|
<li>Configurable speech-to-text with server-side providers</li>
|
|
<li>Text-to-speech for reading notes aloud</li>
|
|
<li>AI can follow your saved note templates and formatting preferences</li>
|
|
<li>Customizable AI prompts for each note type</li>
|
|
</ul>
|
|
|
|
<h4 style="font-size:13px;color:var(--blue-dark);margin:16px 0 8px;">Learning Hub</h4>
|
|
<ul style="font-size:12.5px;color:var(--g700);line-height:1.8;padding-left:18px;margin:0 0 12px;">
|
|
<li>Educational articles, clinical pearls, quizzes, and slide presentations</li>
|
|
<li>AI-powered content generation from topics, PDFs, or Nextcloud files</li>
|
|
<li>Marp presentations with PPTX export</li>
|
|
<li>Semantic search powered by vector embeddings</li>
|
|
</ul>
|
|
|
|
<h4 style="font-size:13px;color:var(--blue-dark);margin:16px 0 8px;">Security & Privacy</h4>
|
|
<ul style="font-size:12.5px;color:var(--g700);line-height:1.8;padding-left:18px;margin:0 0 12px;">
|
|
<li>Two-factor authentication (TOTP) and SSO/OIDC support</li>
|
|
<li>Session management with device tracking and revocation</li>
|
|
<li>Comprehensive audit logging of all clinical actions</li>
|
|
<li>Auto-expiring data (encounters 7 days, audio 24 hours)</li>
|
|
<li>Cloudflare Turnstile bot protection</li>
|
|
<li>Role-based access control (admin, moderator, user)</li>
|
|
</ul>
|
|
|
|
<p style="font-size:11px;color:var(--g400);margin:16px 0 0;text-align:center;">AI-generated notes should always be reviewed by a qualified healthcare professional before clinical use.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- dummy settings-modal kept for any stale JS references -->
|
|
<div id="settings-modal" class="hidden" style="display:none !important;"></div>
|
|
|
|
</div>
|
|
|
|
<!-- Confirmation Modal -->
|
|
<div id="confirm-modal" class="confirm-modal hidden">
|
|
<div class="confirm-modal-box">
|
|
<p id="confirm-modal-text"></p>
|
|
<div id="confirm-modal-input-wrap" style="display:none;margin:10px 0;">
|
|
<input type="text" id="confirm-modal-input" style="width:100%;padding:8px;border:1px solid var(--g300);border-radius:6px;font-size:13px;">
|
|
</div>
|
|
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:14px;">
|
|
<button id="confirm-modal-cancel" class="btn-sm btn-ghost">Cancel</button>
|
|
<button id="confirm-modal-ok" class="btn-sm btn-primary">Confirm</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- LOADING -->
|
|
<div id="loading-overlay" class="loading-overlay hidden">
|
|
<div class="loading-box"><div class="spinner"></div><p id="loading-text">Processing...</p></div>
|
|
</div>
|
|
|
|
<!-- TOAST -->
|
|
<div id="toast-container" class="toast-container"></div>
|
|
|
|
<!-- SLIDE PREVIEW MODAL -->
|
|
<div id="slide-preview-modal" class="slide-preview-modal hidden" role="dialog" aria-modal="true">
|
|
<div class="slide-preview-overlay" id="slide-preview-overlay"></div>
|
|
<div class="slide-preview-shell">
|
|
<div class="slide-preview-topbar">
|
|
<span class="slide-preview-counter" id="slide-preview-counter">1 / 1</span>
|
|
<span style="font-size:13px;color:rgba(255,255,255,0.6);">Use arrow keys or swipe to navigate</span>
|
|
<button class="slide-preview-close" id="slide-preview-close" aria-label="Close preview">✕ Close</button>
|
|
</div>
|
|
<div class="slide-preview-stage" id="slide-preview-stage">
|
|
<button class="slide-nav-btn slide-nav-prev" id="slide-nav-prev" aria-label="Previous slide">‹</button>
|
|
<div class="slide-preview-frame" id="slide-preview-frame">
|
|
<style id="slide-preview-css"></style>
|
|
<div id="slide-preview-content"></div>
|
|
</div>
|
|
<button class="slide-nav-btn slide-nav-next" id="slide-nav-next" aria-label="Next slide">›</button>
|
|
</div>
|
|
<div class="slide-preview-dots" id="slide-preview-dots"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SCRIPTS (defer for faster initial paint) -->
|
|
<script src="/vendor/tiptap.bundle.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"
|
|
integrity="sha384-JUh163oCRItcbPme8pYnROHQMC6fNKTBWtRG3I3I0erJkzNgL7uxKlNwcrcFKeqF"
|
|
crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
|
|
<script defer src="/js/milestonesData.js"></script>
|
|
<script src="/js/recordingModules.js"></script>
|
|
<script src="/js/myResources.js" defer></script>
|
|
<script type="module" src="/js/audioBackup.js"></script>
|
|
<script type="module" src="/js/speechRecognition.js"></script>
|
|
<script type="module" src="/js/transcriptionSettings.js"></script>
|
|
<script type="module" src="/js/voicePreferences.js"></script>
|
|
<script defer src="/js/app.js?v=7.1.3"></script>
|
|
<script type="module" src="/js/ui-state.js"></script>
|
|
<script type="module" src="/js/secureStorage.js"></script>
|
|
<script defer src="/js/auth.js"></script>
|
|
<script defer src="/js/liveEncounter.js"></script>
|
|
<script defer src="/js/voiceDictation.js"></script>
|
|
<script type="module" src="/js/hospitalCourse.js"></script>
|
|
<script type="module" src="/js/chartReview.js"></script>
|
|
<script type="module" src="/js/soap.js"></script>
|
|
<script type="module" src="/js/milestones.js"></script>
|
|
<script type="module" src="/js/peGuide.js"></script>
|
|
<script type="module" src="/js/extensions.js"></script>
|
|
<script type="module" src="/js/notes.js"></script>
|
|
<script type="module" src="/js/diagrams.js"></script>
|
|
<script type="module" src="/js/clinicalAssistant.js"></script>
|
|
<script type="module" src="/js/nextcloud.js"></script>
|
|
<script type="module" src="/js/wellVisit.js"></script>
|
|
<script defer src="/js/shadess.js"></script>
|
|
<script type="module" src="/js/sickVisit.js"></script>
|
|
<script type="module" src="/js/ed-encounters.js"></script>
|
|
<script defer src="/js/encounters.js"></script>
|
|
<script type="module" src="/js/memories.js"></script>
|
|
<script type="module" src="/js/admin-docs.js"></script>
|
|
<script type="module" src="/js/documents.js"></script>
|
|
<script defer src="/js/calc-math.js"></script>
|
|
<script type="module" src="/js/drugs-loader.js"></script>
|
|
<script type="module" src="/js/calculators.js"></script>
|
|
<script type="module" src="/js/bedside/index.js"></script>
|
|
<script type="module" src="/js/learningHub.js"></script>
|
|
<script type="module" src="/js/admin.js?v=7.1.3"></script>
|
|
|
|
<!-- ═══════════ IMAGE LIGHTBOX (global overlay — triggered by any
|
|
[data-img-src] button in any tab; lives here so it exists in the
|
|
DOM from page load, independent of which tab has been lazy-loaded
|
|
yet). Styled inline to keep the CSP-compliant "no inline scripts"
|
|
rule (inline styles are allowed). ═══════════ -->
|
|
<div id="img-lightbox" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:9999;align-items:center;justify-content:center;padding:20px;flex-direction:column;">
|
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;color:white;max-width:95%;">
|
|
<div id="img-lightbox-title" style="flex:1;font-weight:600;font-size:14px;"></div>
|
|
<a id="img-lightbox-open" href="#" target="_blank" rel="noopener" style="color:#93c5fd;font-size:13px;text-decoration:none;"><i class="fas fa-up-right-from-square"></i> Open in new tab</a>
|
|
<button id="img-lightbox-close" type="button" style="background:rgba(255,255,255,0.15);color:white;border:none;border-radius:6px;padding:6px 10px;cursor:pointer;font-size:14px;"><i class="fas fa-xmark"></i> Close</button>
|
|
</div>
|
|
<img id="img-lightbox-img" src="" alt="" style="max-width:95%;max-height:85vh;object-fit:contain;border-radius:6px;box-shadow:0 4px 30px rgba(0,0,0,0.5);background:white;">
|
|
</div>
|
|
</body>
|
|
</html>
|