Files
UmbraTrans/web/index.html
umbra audio 3e0568b9d1 Generate backend endpoints for language-project workflow
Add REST endpoints and frontend wiring for per-language project actions:
- POST /api/workspace/{project}/translation/{lang}/queue — queue translation
- POST /api/workspace/{project}/audio/{lang}/queue — queue audio generation
- GET/POST /api/workspace/{project}/format/{lang} — format metadata (EPUB/PDF)
- GET /api/workspace/{project}/review/{lang}/chapters/{chapter} — utterance review
- GET/POST /api/workspace/{project}/front-matter/{lang} — front matter editor
- GET /api/workspace/{project}/chapters/{lang} — chapter list
- GET /api/workspace/{project}/chapter-content/{lang}/{chapter} — chapter text

Frontend fixes:
- Review page shows per-language text (English source, Chinese translation, etc.)
- Non-translated languages show 'Queue translation' button only
- Speaker data from English source matched by paragraph ID
- UmbraAudio column layout: Speaker, Variation, Text, Actions
- Chapter tree in left sidebar: Front matter, Back matter, Body
- Audio Format label at top of sidebar
- Pandoc formatting toolbar for front matter text editing
2026-10-25 11:04:53 -07:00

119 lines
7.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="UmbraTrans workspace sign in">
<title>Sign in · UmbraTrans</title>
<link rel="icon" type="image/png" href="assets/umbratrans-emblem.png">
<link rel="stylesheet" href="assets/styles.css">
</head>
<body class="auth-page">
<main class="auth-shell">
<section class="auth-brand" aria-labelledby="brand-title">
<a class="brand-lockup" href="index.html" aria-label="UmbraTrans home">
<img class="brand-image" src="assets/umbratrans-emblem.png" alt="" aria-hidden="true">
<span>UmbraTrans</span>
</a>
<div class="brand-copy">
<p class="eyebrow">Translation production workspace</p>
<h1 id="brand-title">One source of truth for every language.</h1>
<p class="lede">Review books, translations, voices, and audio delivery from one project-aware workspace.</p>
</div>
<div class="brand-points" aria-label="Workspace capabilities">
<div><span class="point-dot"></span>Project-scoped access</div>
<div><span class="point-dot"></span>Shared speaker and voice identity</div>
<div><span class="point-dot"></span>Traceable analysis and delivery lineage</div>
</div>
<p class="prototype-note" id="service-note">Checking service status…</p>
</section>
<section class="auth-card" aria-label="Account access">
<div id="login-view">
<div class="auth-card-heading">
<span class="status-pill status-pill--info">Secure workspace</span>
<h2 id="signin-title">Sign in to your workspace</h2>
<p>Use the email address your account was issued with, and your password. Access is determined by your organization membership and role.</p>
</div>
<!-- method="post" is load-bearing, not decoration.
A form with no method attribute defaults to GET, and a GET submission puts every
named field into the query string. If the script fails to load for any reason -
a syntax error, a truncated deploy, a blocked asset - the submit handler that
calls preventDefault() never runs, and the browser performs a native GET. That
happened: a syntax error I introduced left both password fields going into
`/?email=...&password=...`, which the server logged in plain text.
With method="post" the fallback is a POST body, which does not appear in the
URL, in the request line, or in referrer headers. The JavaScript still intercepts
and submits via fetch; this is what happens when the JavaScript does not. -->
<form id="login-form" class="stack-form" method="post" action="/api/auth/login" novalidate>
<label for="login-email">Email address</label>
<input id="login-email" name="email" type="email" inputmode="email" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" placeholder="you@example.com" required>
<label for="login-password">Password</label>
<div class="password-field">
<input id="login-password" name="password" type="password" autocomplete="current-password" placeholder="Enter your password" required>
<button id="toggle-login-password" class="password-toggle" type="button" aria-pressed="false" aria-controls="login-password">Show</button>
</div>
<div id="login-message" class="form-message" role="status" aria-live="polite"></div>
<button id="login-submit" class="button button--primary button--wide" type="submit">Sign in <span aria-hidden="true">→</span></button>
</form>
<div id="verify-panel" class="verify-panel" hidden>
<h3>Confirm your email address</h3>
<p id="verify-panel-text">Your password is correct, but this address has not been confirmed yet. Check your inbox for the confirmation link, or send a new one.</p>
<button id="resend-verification" class="button button--ghost" type="button">Send a new link</button>
<div id="verify-panel-message" class="form-message" role="status" aria-live="polite"></div>
</div>
<div id="setup-entry" class="auth-divider" hidden><span>First run</span></div>
<a id="setup-link" class="admin-link" href="setup.html" hidden><span>Set up this deployment</span><span aria-hidden="true">↗</span></a>
<div id="admin-entry" class="auth-divider"><span>Administrator?</span></div>
<a id="admin-link" class="admin-link" href="umbra-app.html"><span>Open control panel</span><span aria-hidden="true">↗</span></a>
<p class="auth-footnote">An account issued a temporary password must replace it at first sign-in before any workspace access is granted.</p>
</div>
<div id="password-view" hidden>
<div class="auth-card-heading">
<span class="status-pill status-pill--warning">Action required</span>
<h2 id="password-title">Choose a new password</h2>
<p id="password-lede">Your temporary password must be replaced before you can continue. This step is mandatory.</p>
</div>
<div class="policy-list" aria-labelledby="policy-title">
<p class="policy-title" id="policy-title">Password policy</p>
<ul>
<li id="policy-length">At least 14 characters.</li>
<li>Must not begin or end with whitespace.</li>
<li id="policy-username">Must not contain your account name.</li>
<li>Must differ from the temporary password you are replacing.</li>
</ul>
</div>
<!-- method="post" for the same reason as the login form: a native fallback
submission of a password change must never reach the query string. The action is
the real endpoint so that even the fallback is the right request. -->
<form id="password-form" class="stack-form" method="post" action="/api/auth/change-password" novalidate>
<label for="current-password">Current temporary password</label>
<div class="password-field">
<input id="current-password" name="current_password" type="password" autocomplete="current-password" required>
<button id="toggle-current-password" class="password-toggle" type="button" aria-pressed="false" aria-controls="current-password">Show</button>
</div>
<label for="new-password">New password</label>
<div class="password-field">
<input id="new-password" name="new_password" type="password" autocomplete="new-password" required>
<button id="toggle-new-password" class="password-toggle" type="button" aria-pressed="false" aria-controls="new-password">Show</button>
</div>
<label for="confirm-password">Confirm new password</label>
<input id="confirm-password" name="confirm_password" type="password" autocomplete="new-password" required>
<div id="password-message" class="form-message" role="status" aria-live="polite"></div>
<button id="password-submit" class="button button--primary button--wide" type="submit">Set new password <span aria-hidden="true">→</span></button>
<a id="password-settings" class="admin-link" href="settings.html" hidden><span>Your account settings</span><span aria-hidden="true">↗</span></a>
<button id="password-signout" class="button button--ghost button--wide" type="button">Sign out instead</button>
</form>
<p class="auth-footnote">This page cannot be left until the password has been changed. Your session stays open while you do.</p>
</div>
</section>
</main>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="assets/api.js" defer></script>
<script src="assets/app.js" defer></script>
</body>
</html>