diff --git a/docs/umbra-audio-database-prompt-1-schema-contract.txt b/docs/umbra-audio-database-prompt-1-schema-contract.txt new file mode 100644 index 0000000..4e397eb --- /dev/null +++ b/docs/umbra-audio-database-prompt-1-schema-contract.txt @@ -0,0 +1,226 @@ +UMBRA AUDIO ENTERPRISE DATABASE INTEGRATION — PROMPT 1 +CONTINUITY, ACCESS, AND SCHEMA CONTRACT + +You are the Umbra Audio integration agent. + +Objective: +Design a safe transition for Umbra Audio to use the shared enterprise PostgreSQL database while the audio system continues operating and creating audiobooks. + +This is not a shutdown-and-rebuild project. Audiobook production must remain available during the transition. The shared enterprise database will become the authoritative source for accounts, organizations, projects, memberships, permissions, project state, source-book analysis, speakers, voices, translation references, audio runs, artifacts, lineage, and audit history. + +The same enterprise database will later be used by the translation/review UI. The audio system and that UI must use the same identities, organizations, projects, permissions, and stable record IDs. They must not create independent account or project silos. + +This is a DISCOVERY AND DESIGN PHASE ONLY. + +Do not: +- connect to the production database; +- run DDL, DML, migrations, grants, or role changes; +- create tables, alter tables, copy data, or change configuration; +- stop or disable the audiobook production pipeline; +- invent existing columns, tables, users, projects, or production data; +- use or request credentials, tokens, private keys, connection strings, or secret values; +- put secrets in prompts, command lines, environment dumps, logs, reports, or files. + +Inputs: +- the current Umbra Audio source code and configuration; +- the current ebook ingestion, analysis, voice, and audiobook-generation pipeline; +- the current API and review UI; +- the current local user, project, job, and artifact persistence; +- the approved shared schema reference, currently the control-plane schema at version 001_control_plane; +- any existing local database, JSON, SQLite, or file-based persistence. + +Known shared control-plane concepts: +- organizations +- projects +- users +- roles and permissions +- user_roles and project memberships +- sessions, invitations, and MFA-related records +- audit_events +- project-scoped roles including translator, proofreader, audio_director, voice_manager, project_manager, and release_manager + +The shared database currently has tenancy and control-plane tables, but you must verify this rather than assume it. Do not claim that ebook, voice, translation, or audiobook domain tables already exist. + +Tasks: + +1. Continuity and current-state inventory. + +Document how the audio system operates today: +- how an ebook enters the system; +- how analysis creates speakers and voices; +- how audiobook jobs are queued, retried, and completed; +- how artifacts are named, stored, and retrieved; +- how the API and UI identify a project; +- how the current system stores users, projects, and job state; +- which operations must continue during migration; +- which local data is authoritative, cached, or temporary. + +For every current persistent object, report: +- logical entity; +- current storage location; +- owner/service; +- primary identifier; +- important fields and types; +- relationships; +- lifecycle/status values; +- whether it is authoritative, derived, cached, or temporary; +- whether it contains personal, licensed, copyrighted, or otherwise sensitive data. + +2. Enterprise identity and project-access contract. + +Design how the existing audio system and the future translation/review UI will use enterprise identities. + +The design must: +- use the enterprise users, organizations, projects, roles, permissions, and user_roles records as the canonical account/project model; +- map any existing local accounts to canonical enterprise user IDs; +- avoid creating a second password store or a second project namespace; +- use the approved enterprise authentication provider or session mechanism; +- support login, logout, session expiry, revocation, and account suspension; +- require project membership before a user can see or work on an audiobook; +- restrict users to projects where they have an active membership; +- use the existing project roles where appropriate, including project_manager, audio_director, translator, proofreader, and viewer; +- record authorization decisions and material changes in audit_events; +- keep human user sessions separate from background audio-worker identities. + +The UI and API must derive organization and project context from the authenticated server-side session. They must never trust an organization_id, project_id, role, or user_id supplied by an untrusted browser, request body, query parameter, or local file. + +Explicitly define: +- login flow; +- session lifetime and revocation; +- project selection; +- authorization checks for viewing, reviewing, editing, generating, downloading, and releasing; +- behavior for suspended or deactivated users; +- service-to-service authentication for the audio worker; +- account and project segmentation rules. + +3. Canonical shared domain model. + +Evaluate, and explicitly accept or reject, models equivalent to: + +- source books / editions / source documents; +- immutable analysis runs and analysis snapshots; +- speakers or characters; +- canonical voice profiles; +- voice casting and voice reuse; +- voice licensing, consent, and usage restrictions; +- translation runs and language targets; +- translation segments or units; +- audio generation runs and voice-cast snapshots; +- generated ebook and audiobook artifacts; +- pipeline jobs, retries, and idempotency; +- provenance, lineage, checksums, and audit events. + +For every proposed table, specify: +- table name and purpose; +- row grain: exactly what one row represents; +- authoritative writer; +- primary key; +- columns, types, nullability, and defaults; +- foreign keys and delete behavior; +- unique constraints; +- indexes; +- organization_id and project_id tenancy fields; +- lifecycle/status state machine; +- RLS requirements; +- data classification and retention; +- idempotency and concurrency strategy; +- whether it is new, an extension of an existing table, or should not be stored in PostgreSQL. + +4. Shared-record and cross-language invariants. + +The design must ensure that: +- one source ebook has one stable source identity; +- one canonical project contains the audiobook and translation work for that project; +- analysis has immutable versions; +- speaker/character identity is stable across languages; +- a voice profile can be reused across translations only when language, accent, consent, and licensing rules allow it; +- every translation references a source book, project, and specific analysis version; +- every audiobook references a project, translation when applicable, a voice-cast snapshot, and a generation run; +- translated ebooks and audiobooks never become independent duplicate source records; +- the audio system continues creating audiobooks before, during, and after the cutover; +- large media files are stored in object storage, with only immutable object keys, checksums, media metadata, and lifecycle state in PostgreSQL; +- every write is idempotent and safe to retry; +- concurrent workers cannot silently overwrite one another; +- service identities are not represented by arbitrary client-supplied user IDs; +- the future translation UI can discover authorized projects and audiobooks without receiving data from a separate account system. + +5. Continuity and cutover plan. + +Design a gradual migration that does not interrupt audiobook production. + +Include: +- a repository/adapter boundary that hides the storage backend; +- a read-only mirror or shadow-read phase; +- a backfill plan for existing local users, projects, books, jobs, and artifact references; +- stable-ID mapping and reconciliation rules; +- a feature flag or per-project cutover; +- a dual-write or write-through period, if safe; +- validation of counts, IDs, checksums, and status transitions; +- retry and recovery behavior for jobs created during migration; +- a rollback plan that does not delete source data or stop the audio worker; +- a point at which the enterprise database becomes authoritative; +- a plan for historical local records that cannot be mapped. + +The audio system must be able to continue audiobook creation during the transition. The translation/review UI must not be a prerequisite for the audio worker to produce audiobooks. + +6. Schema delta. + +Classify every change as: +- ADD TABLE +- ALTER TABLE +- ADD CONSTRAINT +- ADD INDEX +- ENABLE/PROVE RLS +- BACKFILL +- GRANT/REVOKE +- NEW SERVICE ROLE +- NO CHANGE + +For each change provide: +- reason; +- dependency order; +- backward-compatible rollout strategy; +- rollback strategy; +- data-volume estimate; +- lock/availability risk; +- verification query or test; +- whether human approval is required. + +Do not emit production migration SQL in this phase. A separate reviewed migration process will produce it. + +7. Security and tenancy review. + +Explicitly evaluate: +- how the audio system authenticates; +- how the future UI authenticates; +- which database role each component uses; +- how organization and project context is established; +- how RLS is enforced; +- how a human session is distinguished from a worker identity; +- how a worker maps to a project without impersonating a user; +- how service-to-service writes are authorized; +- how audit events are recorded; +- how secrets remain outside the database and application logs; +- whether any existing RLS design can be bypassed by client-controlled settings or roles; +- whether login state can be forged or reused across organizations. + +If the existing control-plane RLS or identity mechanism is not safe for a service worker or UI session, mark it as a BLOCKER. Do not work around it. + +Required output format: + +A. Human-readable executive summary +B. Audio-production continuity assessment +C. Current-state persistence and identity inventory +D. Enterprise identity, session, and project-access contract +E. Proposed canonical entity model +F. Table-by-table contract +G. Shared-record and cross-language invariants +H. Schema delta and migration order +I. Gradual cutover, reconciliation, and rollback plan +J. Security/RLS review +K. Backup and restore requirements +L. Acceptance tests +M. Open questions and blockers +N. Machine-readable YAML or JSON contract + +Stop after producing the contract. Do not connect, migrate, or write application data until a human approves it. diff --git a/docs/umbra-audio-database-prompt-2-connect-and-verify.txt b/docs/umbra-audio-database-prompt-2-connect-and-verify.txt new file mode 100644 index 0000000..33cd6e1 --- /dev/null +++ b/docs/umbra-audio-database-prompt-2-connect-and-verify.txt @@ -0,0 +1,169 @@ +UMBRA AUDIO ENTERPRISE DATABASE INTEGRATION — PROMPT 2 +CONNECT, LOGIN, PROJECT ISOLATION, AND CONTINUITY VERIFICATION + +You are the Umbra Audio database connection and verification agent. + +Objective: +Connect the existing Umbra Audio system to the shared enterprise PostgreSQL database without interrupting audiobook production, and verify that the database is authoritative for enterprise accounts, organizations, projects, permissions, and shared audiobook state. + +The same enterprise database will be used by the future translation/review UI. Both systems must use the same identities, organizations, projects, memberships, permissions, and stable record IDs. + +Audiobook creation must continue during and after the transition. Translation generation must not begin until this verification succeeds and a human approves the result. + +Preconditions: +- the schema contract is approved; +- the continuity and cutover plan is approved; +- the migration plan is approved and applied by the designated migrator process; +- a verified logical backup and restore test exist; +- the enterprise authentication provider and session mechanism are approved; +- the runtime database roles are provisioned; +- object-storage locations and voice-licensing rules are defined. + +Never: +- use the PostgreSQL superuser for the application; +- use umbracontrol_backup for application writes; +- use umbracontrol_migrator at runtime; +- use bootstrap tokens, MFA secrets, session tokens, password hashes, /etc/shadow, pg_authid, pg_shadow, or credential files; +- print or log a connection string or secret; +- store user passwords in the audio system or the future UI; +- accept a database URL, token, organization_id, project_id, role, or user_id from an untrusted client; +- run DDL; +- copy the live PostgreSQL data directory; +- change firewall, SSH, Docker, or service configuration automatically; +- use local SQLite, JSON, or files as an independent source of truth; +- disable or weaken RLS to make a test pass; +- stop the audiobook worker as part of connection setup. + +Connection procedure: + +1. Obtain connection material from the approved secret manager or protected runtime environment. + Do not place secrets in source code, prompts, command arguments, filenames, environment dumps, or logs. + +2. Establish a read-only connection using the approved runtime role. + + The expected role model is: + - umbracontrol_api for synchronous application operations; + - umbracontrol_worker for approved asynchronous audiobook and analysis operations; + - umbracontrol_readonly for diagnostics; + - umbracontrol_migrator only for a separately reviewed migration job; + - umbracontrol_backup only for backup operations. + +3. Verify and report, without exposing secrets: + - database name; + - runtime role; + - server version; + - TLS/certificate verification state; + - host/port classification; + - UTC timezone; + - search_path; + - application name; + - connection-pool settings; + - statement and lock timeouts; + - retry/backoff behavior; + - schema_migrations version; + - expected tables and views; + - RLS-enabled status; + - sequence and identity settings; + - extension availability; + - current connection latency; + - audiobook worker health before and after connection changes. + +4. Verify enterprise login and session behavior. + + Test the real authentication path through the approved identity provider or session service. + + Confirm: + - a valid user can log in; + - logout invalidates the session; + - expired or revoked sessions cannot access the API or UI; + - suspended or deactivated accounts are denied; + - the session identifies the canonical enterprise user; + - organization and project context is derived server-side; + - a user cannot select an unauthorized project by changing a URL, query parameter, request body, or local file; + - project membership is checked for every audiobook view, review, edit, generation, download, and release action; + - the UI only lists projects and audiobooks the user is authorized to see; + - error messages do not reveal the existence of another organization’s records. + + Do not use real user credentials in tests. Use approved test identities or a staging environment. + +5. Run only read-only catalog checks. + + Do not query authentication catalogs or secret-bearing tables. + + Verify: + - the approved schema version; + - expected tables; + - foreign keys; + - unique constraints; + - indexes; + - RLS flags and policies; + - grants for the runtime role; + - absence of unexpected schema drift; + - existence of the approved project-membership and audit paths. + +6. Test tenancy, RLS, and service identity without using real content. + + In a transaction that is guaranteed to roll back: + - create synthetic organization/project identifiers; + - use one authorized test identity; + - use one identity from a different organization; + - verify the first identity can access only its project; + - verify the second identity is denied; + - verify a worker can process only jobs assigned to its service/project scope; + - verify a worker cannot impersonate an arbitrary human user; + - verify the API cannot be made to trust a client-supplied user_id, role, organization_id, or project_id; + - verify audit events are written with the correct server-derived actor context; + - verify an audiobook job can be created and inspected without exposing another project’s data; + - verify all test writes roll back cleanly. + +7. Verify audiobook-production continuity. + + With the existing audio worker and job queue still operating: + - confirm the worker can read its required canonical project and source references; + - confirm a new idempotent analysis or audiobook job can be created under an authorized project context; + - confirm job status and artifact references can be updated transactionally; + - confirm retries do not create duplicate books, analyses, voices, or audio runs; + - confirm a worker restart does not lose or duplicate an in-flight job; + - confirm local caches and temporary files are treated as derived data only; + - confirm the future translation UI can discover the resulting project and audiobook records without receiving a separate account or project list. + +8. Configure the audio system repository layer. + + The application must: + - read canonical enterprise records from PostgreSQL; + - store only derived/cache data locally; + - use database transactions for related writes; + - use idempotency keys for analysis, voice, and audiobook jobs; + - use optimistic concurrency or row versions for shared records; + - reference stable IDs rather than copying source analysis into every translation; + - record lineage from source book to analysis to translation to audio run to artifact; + - write audit events for material changes; + - retry safely; + - preserve an approved rollback or feature-flag path; + - continue audiobook creation while a project is being migrated; + - fail closed on schema-version, identity, membership, or RLS mismatch. + +9. Do not begin translation generation yet. + + Translation work may begin only after this report is approved and the audio system has demonstrated that base book, analysis, speaker, voice, project, and audiobook records are persisted canonically and are visible only to authorized users. + +Produce a sanitized JSON report containing only: + +- service name and version; +- database name; +- runtime role; +- TLS enabled and certificate verification result; +- schema version; +- enterprise login/session test result; +- project-membership test result; +- UI project/audiobook visibility test result; +- RLS verification result; +- synthetic tenancy test result; +- worker identity and authorization result; +- audiobook-production continuity result; +- transaction/idempotency test result; +- connection pool and timeout settings; +- blockers; +- approval_required: true or false. + +If any check fails, stop and report the exact blocker. Do not work around RLS, change grants, alter the schema, or disable authentication. diff --git a/docs/umbratrans-ui-design.md b/docs/umbratrans-ui-design.md new file mode 100644 index 0000000..3b9dab6 --- /dev/null +++ b/docs/umbratrans-ui-design.md @@ -0,0 +1,112 @@ +# UmbraTrans Web UI design + +**Status:** static prototype for review +**Database transition:** paused +**Primary URL:** `https://umbratrans.bdmurphy.me/` +**Admin URL:** `https://umbratrans.bdmurphy.me/uadmin/` +**Prototype listener:** local machine, TCP `8442` + +## Product shape + +UmbraTrans has two related surfaces: + +1. **Workspace** — the daily working environment for a signed-in user. It is project-focused and optimized for reviewing books, speakers, voices, translations, audio runs, and delivery status. +2. **Control** — the `/uadmin/` management surface for account provisioning, organizations, projects, maintenance, audit, and system statistics. + +The workspace and control surfaces share the Umbra brand and accessibility conventions, but intentionally use different layouts and visual emphasis. + +## Visual reference + +The control surface references the existing Umbra Audio Review UI at `umbraudio.bdmurphy.me`: + +- background: `#1a1a2e` +- panel: `#16213e` +- elevated/stat panel: `#0f3460` +- primary accent: `#7c3aed` +- secondary accent: `#a78bfa` +- text: `#e0e0e0` +- muted text: `#94a3b8` +- success: `#10b981` +- warning: `#f59e0b` +- danger: `#ef4444` +- compact cards, top navigation, status badges, and data tables +- supplied site icon: `/home/umbra/Documents/UmberTrans_Icon.jpg`, copied to the prototype as `web/assets/umbratrans-icon.jpg` + +The workspace uses a deliberately different **Studio** language: a slate/indigo canvas, left navigation rail, teal action accent, amber review accent, larger reading areas, and a pipeline-oriented layout. It should feel like a production desk rather than an admin console. + +## Routes + +| Route | Purpose | Access | +|---|---|---| +| `/` | Sign-in and account entry | Public | +| `/workspace/` or `/workspace.html` | Authenticated project workspace | Authenticated user | +| `/uadmin/` | Accounts, projects, maintenance, statistics | Organization/platform administrator | +| `/uadmin/accounts` | Account provisioning and lifecycle | Administrator | +| `/uadmin/projects` | Project management | Administrator | +| `/uadmin/maintenance` | Worker, queue, and storage operations | Administrator | +| `/uadmin/statistics` | Usage and system health | Administrator | + +The static prototype uses `index.html`, `workspace.html`, and `uadmin/index.html`. The eventual application should serve the same routes without exposing filesystem paths. + +## Authentication direction + +The UI supports two account-enrollment paths: + +1. **Temporary local password** — an administrator provisions an account with a one-time temporary password. Only an Argon2id hash is stored. The account is marked `must_change_password`; the first login must change it before any workspace access. +2. **OIDC enrollment** — an administrator provisions an account or invitation and the identity provider completes credential setup. No local password is created. + +The application should hide this choice behind an authentication adapter. Both paths must resolve to the same canonical `users` identity, organization membership, project membership, and server-side session. The browser must never supply a trusted `user_id`, `organization_id`, `project_id`, or role. + +The prototype does not authenticate, store credentials, or call a database. + +## Admin workflows to design + +- Create/suspend/reactivate an account. +- Issue a temporary password or OIDC invitation. +- Require first-login password change for temporary-password accounts. +- Assign organization and project memberships. +- View account status, last login, and MFA state. +- Create, archive, and transfer projects. +- View worker health, queue depth, failed jobs, and storage status. +- View audit history and export a sanitized report. + +Every destructive action requires a confirmation step, an explicit permission check, and an audit event. + +## Workspace workflows to design + +- Select an authorized project. +- View source book, analysis version, speakers, canonical voices, and voice cast. +- Review translation status by locale. +- Monitor analysis, audio, QA, and release stages. +- Inspect job progress without exposing filesystem paths. +- Download only artifacts authorized for the current project and release state. + +## Prototype limitations + +The current prototype uses mock data and client-side navigation only. It does not: + +- connect to PostgreSQL; +- authenticate users; +- issue or validate passwords; +- expose real accounts or projects; +- accept real credentials; +- perform administrative actions. + +Buttons demonstrate intended states and interactions only. The UI must remain a prototype until the database transition and security gates are approved. + +## Local review + +From the workspace root: + +```bash +cd /home/umbra/src/UmbraTrans +python3 -m http.server 8442 --bind 0.0.0.0 --directory web +``` + +Then open: + +- `http://:8442/` +- `http://:8442/workspace.html` +- `http://:8442/uadmin/` + +For production, terminate TLS at the approved reverse proxy, restrict the application listener to the required interface, and define the network allow-list before exposing port 8442. diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..374ebb6 --- /dev/null +++ b/web/README.md @@ -0,0 +1,20 @@ +# UmbraTrans static UI prototype + +This directory contains a local, mock-data prototype for review. + +It does not connect to PostgreSQL, authenticate accounts, issue passwords, or change production data. The admin page demonstrates the intended flows only. + +## Run locally + +```bash +cd /home/umbra/src/UmbraTrans +python3 -m http.server 8442 --bind 0.0.0.0 --directory web +``` + +Review URLs: + +- `http://:8442/` +- `http://:8442/workspace.html` +- `http://:8442/uadmin/` + +For production, put the application behind the approved TLS reverse proxy, restrict the listener/interface, and implement the authentication and authorization adapter described in the UI design document. diff --git a/web/assets/admin.js b/web/assets/admin.js new file mode 100644 index 0000000..0ee80d8 --- /dev/null +++ b/web/assets/admin.js @@ -0,0 +1,46 @@ +const $ = (selector, root = document) => root.querySelector(selector); +const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; + +function showToast(message) { + const toast = $('#toast'); + if (!toast) return; + toast.textContent = message; + toast.classList.add('toast--visible'); + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => toast.classList.remove('toast--visible'), 3600); +} + +const modal = $('#account-modal'); +const openModal = $('#open-account-modal'); +const closeModal = $('#close-account-modal'); +const cancelModal = $('#cancel-account-modal'); +const form = $('#account-form'); + +function setModal(open) { + if (!modal) return; + modal.hidden = !open; + document.body.classList.toggle('modal-open', open); + if (open) $('#account-name')?.focus(); +} + +openModal?.addEventListener('click', () => setModal(true)); +closeModal?.addEventListener('click', () => setModal(false)); +cancelModal?.addEventListener('click', () => setModal(false)); +modal?.addEventListener('click', (event) => { + if (event.target === modal) setModal(false); +}); +document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') setModal(false); +}); + +form?.addEventListener('submit', (event) => { + event.preventDefault(); + const oidc = $('#oidc-enrollment')?.checked; + setModal(false); + form.reset(); + showToast(oidc ? 'Prototype OIDC enrollment flow submitted locally.' : 'Prototype temporary-password flow submitted locally.'); +}); + +$$('[data-demo-action]').forEach((button) => { + button.addEventListener('click', () => showToast(button.dataset.demoAction)); +}); diff --git a/web/assets/app.js b/web/assets/app.js new file mode 100644 index 0000000..c99f298 --- /dev/null +++ b/web/assets/app.js @@ -0,0 +1,56 @@ +const $ = (selector, root = document) => root.querySelector(selector); +const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; + +function showToast(message) { + const toast = $('#toast'); + if (!toast) return; + toast.textContent = message; + toast.classList.add('toast--visible'); + window.clearTimeout(showToast.timer); + showToast.timer = window.setTimeout(() => toast.classList.remove('toast--visible'), 3600); +} + +function setupLogin() { + const form = $('#login-form'); + if (!form) return; + const message = $('#login-message'); + const email = $('#email'); + const password = $('#password'); + const demo = $('#demo-login'); + + const completeDemoLogin = (value) => { + email.value = value || 'alex.morgan@example.com'; + password.value = 'prototype-only'; + sessionStorage.setItem('ut-demo-user', email.value); + window.location.href = 'workspace.html'; + }; + + demo?.addEventListener('click', () => completeDemoLogin()); + form.addEventListener('submit', (event) => { + event.preventDefault(); + if (!email.value.trim() || !password.value) { + message.textContent = 'Enter an account and password to continue.'; + message.className = 'form-message form-message--error'; + return; + } + message.textContent = 'Prototype login accepted locally. No credentials were transmitted.'; + message.className = 'form-message form-message--success'; + window.setTimeout(() => completeDemoLogin(email.value.trim()), 350); + }); +} + +function setupWorkspace() { + $$('[data-demo-action]').forEach((button) => { + button.addEventListener('click', () => { + if (button.textContent.trim() === 'Sign out') { + sessionStorage.removeItem('ut-demo-user'); + window.location.href = 'index.html'; + return; + } + showToast(button.dataset.demoAction); + }); + }); +} + +setupLogin(); +setupWorkspace(); diff --git a/web/assets/styles.css b/web/assets/styles.css new file mode 100644 index 0000000..25b267f --- /dev/null +++ b/web/assets/styles.css @@ -0,0 +1,201 @@ +:root { + --admin-bg: #1a1a2e; + --admin-panel: #16213e; + --admin-stat: #0f3460; + --admin-border: #2c3550; + --admin-border-bright: #414a68; + --admin-text: #e0e0e0; + --admin-muted: #94a3b8; + --admin-purple: #7c3aed; + --admin-lilac: #a78bfa; + --admin-blue: #3b82f6; + --admin-green: #10b981; + --admin-amber: #f59e0b; + --admin-red: #ef4444; + --workspace-bg: #0b1220; + --workspace-panel: #111c2f; + --workspace-panel-raised: #16263d; + --workspace-border: #243650; + --workspace-text: #eef2ff; + --workspace-muted: #8fa3bf; + --workspace-teal: #2dd4bf; + --workspace-amber: #fbbf24; + --workspace-blue: #60a5fa; + --shadow: 0 18px 45px rgba(0, 0, 0, .22); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { margin: 0; } +a { color: inherit; } +button, input, select { font: inherit; } +button { cursor: pointer; } +button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible { outline: 3px solid rgba(45, 212, 191, .45); outline-offset: 2px; } + +/* Shared brand and status language */ +.brand-lockup { display: inline-flex; align-items: center; gap: .7rem; color: var(--admin-lilac); font-weight: 800; letter-spacing: -.02em; text-decoration: none; font-size: 1.1rem; } +.brand-image { display: block; width: 2.55rem; height: 2.55rem; border-radius: .72rem; object-fit: cover; box-shadow: 0 8px 22px rgba(124, 58, 237, .32); } +.brand-lockup--rail .brand-image { width: 2.35rem; height: 2.35rem; } +.eyebrow { margin: 0 0 .45rem; color: var(--admin-lilac); font-size: .72rem; font-weight: 800; letter-spacing: .13em; text-transform: uppercase; } +.eyebrow--teal { color: var(--workspace-teal); } +.status-pill { display: inline-flex; align-items: center; gap: .35rem; width: fit-content; border-radius: 999px; padding: .28rem .6rem; font-size: .7rem; font-weight: 750; white-space: nowrap; } +.status-pill--info { color: #bfdbfe; background: #1e3a5f; } +.status-pill--success { color: #a7f3d0; background: #065f46; } +.status-pill--warning { color: #fde68a; background: #78350f; } +.status-pill--muted { color: #cbd5e1; background: #334155; } +.status-dot { display: inline-block; width: .55rem; height: .55rem; border-radius: 50%; } +.status-dot--good { background: #34d399; box-shadow: 0 0 0 .25rem rgba(52, 211, 153, .12); } +.button { display: inline-flex; align-items: center; justify-content: center; gap: .45rem; min-height: 2.45rem; border: 1px solid transparent; border-radius: .55rem; padding: .55rem .9rem; font-size: .82rem; font-weight: 750; text-decoration: none; transition: transform .15s ease, background .15s ease, border-color .15s ease; } +.button:hover { transform: translateY(-1px); } +.button--primary { color: #fff; background: var(--admin-blue); } +.button--primary:hover { background: #2563eb; } +.button--secondary { color: var(--workspace-text); background: var(--workspace-panel-raised); border-color: var(--workspace-border); } +.button--secondary:hover { border-color: var(--workspace-teal); } +.button--teal { color: #062a28; background: var(--workspace-teal); } +.button--teal:hover { background: #5eead4; } +.button--ghost { color: var(--admin-muted); background: transparent; border-color: var(--admin-border); } +.button--ghost:hover { color: var(--admin-text); border-color: var(--admin-lilac); } +.button--wide { width: 100%; } +.text-button, .admin-text-button { border: 0; background: none; color: var(--admin-lilac); font-size: .75rem; font-weight: 750; } +.text-button:hover, .admin-text-button:hover { color: #fff; text-decoration: underline; } +.muted-label { color: var(--admin-muted); font-size: .75rem; } + +/* Login */ +.auth-page { min-height: 100vh; color: var(--admin-text); background: radial-gradient(circle at 10% 0%, #26204b 0, transparent 34rem), var(--admin-bg); } +.auth-shell { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(22rem, 30rem); gap: clamp(2rem, 7vw, 8rem); align-items: center; width: min(1120px, calc(100% - 3rem)); min-height: 100vh; margin: 0 auto; padding: 3rem 0; } +.auth-brand { align-self: stretch; display: flex; flex-direction: column; justify-content: space-between; min-height: 40rem; padding: 1rem 0; } +.brand-copy { max-width: 34rem; margin: auto 0; } +.brand-copy h1 { max-width: 12ch; margin: 0 0 1.3rem; color: #fff; font-size: clamp(2.7rem, 6vw, 5.4rem); line-height: .98; letter-spacing: -.065em; } +.lede { max-width: 30rem; margin: 0; color: var(--admin-muted); font-size: 1.05rem; line-height: 1.7; } +.brand-points { display: grid; gap: .7rem; margin: 2.5rem 0; color: #c4b5fd; font-size: .85rem; } +.brand-points > div { display: flex; align-items: center; gap: .65rem; } +.point-dot { width: .45rem; height: .45rem; border-radius: 50%; background: var(--workspace-teal); box-shadow: 0 0 0 .3rem rgba(45, 212, 191, .12); } +.prototype-note { margin: 0; color: #64748b; font-size: .72rem; letter-spacing: .04em; } +.auth-card { padding: 2rem; background: rgba(22, 33, 62, .88); border: 1px solid var(--admin-border); border-radius: 1rem; box-shadow: var(--shadow); backdrop-filter: blur(14px); } +.auth-card-heading h2 { margin: .75rem 0 .55rem; color: #fff; font-size: 1.6rem; letter-spacing: -.03em; } +.auth-card-heading p { margin: 0 0 1.7rem; color: var(--admin-muted); font-size: .86rem; line-height: 1.6; } +.stack-form { display: grid; gap: .55rem; } +.stack-form label { margin-top: .45rem; color: #c4b5fd; font-size: .74rem; font-weight: 750; } +.stack-form input, .stack-form select { width: 100%; min-height: 2.8rem; padding: .65rem .8rem; color: var(--admin-text); background: #0f172a; border: 1px solid var(--admin-border-bright); border-radius: .5rem; } +.stack-form input::placeholder { color: #64748b; } +.stack-form input:focus, .stack-form select:focus { border-color: var(--admin-purple); outline: none; box-shadow: 0 0 0 3px rgba(124, 58, 237, .18); } +.form-message { min-height: 1.2rem; margin: .35rem 0 .2rem; color: var(--admin-muted); font-size: .75rem; } +.form-message--error { color: #fca5a5; } +.form-message--success { color: #6ee7b7; } +.auth-divider { display: flex; align-items: center; gap: .8rem; margin: 1.6rem 0 1rem; color: #64748b; font-size: .7rem; } +.auth-divider::before, .auth-divider::after { content: ""; height: 1px; flex: 1; background: var(--admin-border); } +.admin-link { display: flex; justify-content: space-between; padding: .8rem .9rem; color: var(--admin-lilac); background: rgba(124, 58, 237, .08); border: 1px solid rgba(167, 139, 250, .3); border-radius: .55rem; font-size: .8rem; font-weight: 750; text-decoration: none; } +.admin-link:hover { background: rgba(124, 58, 237, .18); } +.auth-footnote { margin: 1.3rem 0 0; color: #64748b; font-size: .7rem; line-height: 1.5; } + +/* Admin / control surface */ +.admin-page { min-height: 100vh; color: var(--admin-text); background: var(--admin-bg); } +.admin-shell { min-height: 100vh; } +.admin-topbar { display: flex; align-items: center; gap: 1.5rem; min-height: 4.5rem; padding: .8rem clamp(1rem, 3vw, 3rem); background: rgba(15, 23, 42, .5); border-bottom: 1px solid var(--admin-border); } +.admin-context { display: flex; align-items: center; gap: .7rem; padding-left: 1.5rem; border-left: 1px solid var(--admin-border); } +.admin-context-name { color: var(--admin-muted); font-size: .78rem; } +.admin-top-actions { display: flex; align-items: center; gap: 1rem; margin-left: auto; } +.admin-user { display: inline-flex; align-items: center; gap: .5rem; color: #cbd5e1; font-size: .78rem; } +.admin-exit { color: var(--admin-lilac); font-size: .75rem; font-weight: 750; text-decoration: none; } +.admin-layout { display: grid; grid-template-columns: 15rem minmax(0, 1fr); min-height: calc(100vh - 4.5rem); } +.admin-sidebar { display: flex; flex-direction: column; padding: 1.5rem 1rem; background: #141c33; border-right: 1px solid var(--admin-border); } +.admin-nav-label { padding: 0 .7rem .55rem; color: #64748b; font-size: .64rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; } +.admin-nav-label--spaced { margin-top: 1.7rem; } +.admin-nav-link { display: flex; align-items: center; gap: .7rem; margin: .15rem 0; padding: .65rem .7rem; color: var(--admin-muted); border-radius: .45rem; font-size: .8rem; font-weight: 700; text-decoration: none; } +.admin-nav-link span { width: 1rem; color: #64748b; text-align: center; } +.admin-nav-link:hover { color: #fff; background: rgba(124, 58, 237, .1); } +.admin-nav-link--active { color: #fff; background: rgba(124, 58, 237, .2); box-shadow: inset 2px 0 var(--admin-purple); } +.admin-nav-link--active span { color: var(--admin-lilac); } +.admin-sidebar-footer { margin-top: auto; padding: .8rem .7rem 0; color: #94a3b8; font-size: .68rem; line-height: 1.7; border-top: 1px solid var(--admin-border); } +.admin-sidebar-footer small { color: #64748b; } +.admin-main { width: min(1440px, 100%); margin: 0 auto; padding: clamp(1.2rem, 3vw, 2.5rem); } +.admin-banner { display: flex; align-items: center; gap: .7rem; margin-bottom: 1.6rem; padding: .7rem .9rem; color: #c4b5fd; background: rgba(124, 58, 237, .1); border: 1px solid rgba(167, 139, 250, .25); border-radius: .5rem; font-size: .73rem; } +.admin-banner strong { color: #fff; white-space: nowrap; } +.admin-heading { display: flex; align-items: end; justify-content: space-between; gap: 1.5rem; margin-bottom: 1.6rem; } +.admin-heading h1 { margin: 0 0 .35rem; color: #fff; font-size: clamp(1.9rem, 3vw, 2.7rem); letter-spacing: -.05em; } +.admin-heading p:not(.eyebrow) { margin: 0; color: var(--admin-muted); font-size: .86rem; } +.admin-stat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; margin-bottom: 1rem; } +.admin-stat, .admin-card { background: var(--admin-panel); border: 1px solid var(--admin-border); border-radius: .7rem; } +.admin-stat { padding: 1.1rem; } +.admin-stat-label { display: block; margin-bottom: .5rem; color: var(--admin-muted); font-size: .72rem; } +.admin-stat strong { display: block; color: var(--admin-purple); font-size: 2rem; letter-spacing: -.05em; } +.admin-stat small { display: block; margin-top: .35rem; color: #64748b; font-size: .68rem; } +.trend-up { color: #6ee7b7; } +.health-value { display: flex !important; align-items: center; gap: .5rem; color: #a7f3d0 !important; font-size: 1.35rem !important; } +.admin-columns { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(20rem, .9fr); gap: 1rem; margin-bottom: 1rem; } +.admin-columns--lower { grid-template-columns: minmax(0, 1.1fr) minmax(20rem, .9fr); } +.admin-card { min-width: 0; padding: 1.2rem; } +.admin-card-heading { display: flex; align-items: start; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; } +.admin-card-heading h2 { margin: 0; color: #fff; font-size: 1.05rem; letter-spacing: -.02em; } +.admin-table-wrap { overflow-x: auto; } +.admin-table { width: 100%; border-collapse: collapse; font-size: .74rem; } +.admin-table th { padding: .55rem .5rem; color: var(--admin-lilac); font-size: .66rem; text-align: left; text-transform: uppercase; letter-spacing: .08em; } +.admin-table td { padding: .7rem .5rem; color: #cbd5e1; border-top: 1px solid #26334f; white-space: nowrap; } +.admin-table tbody tr:hover { background: #1e293b; } +.table-user { display: flex; align-items: center; gap: .55rem; } +.table-user > span:last-child { display: grid; gap: .12rem; } +.table-user strong { color: #f8fafc; } +.table-user small { color: #64748b; font-size: .66rem; } +.avatar { display: inline-grid; place-items: center; flex: 0 0 auto; width: 2rem; height: 2rem; color: #fff; background: #334155; border-radius: .6rem; font-size: .65rem; font-weight: 800; } +.avatar--admin { background: var(--admin-purple); } +.avatar--small { width: 1.85rem; height: 1.85rem; border-radius: .5rem; } +.avatar--violet { background: #7c3aed; }.avatar--blue { background: #2563eb; }.avatar--amber { background: #b45309; }.avatar--rose { background: #be185d; } +.admin-project-list { display: grid; gap: .55rem; } +.admin-project-row { display: flex; align-items: center; gap: .65rem; padding: .7rem; background: #101a30; border: 1px solid #26334f; border-radius: .5rem; } +.admin-project-row > div { display: grid; flex: 1; gap: .18rem; min-width: 0; } +.admin-project-row strong { color: #f8fafc; font-size: .78rem; } +.admin-project-row small { color: #64748b; font-size: .67rem; } +.project-mark { display: grid; place-items: center; width: 2rem; height: 2rem; border-radius: .5rem; font-size: .65rem; font-weight: 800; } +.project-mark--purple { color: #ddd6fe; background: #4c1d95; }.project-mark--blue { color: #bfdbfe; background: #1e3a8a; }.project-mark--teal { color: #99f6e4; background: #115e59; } +.admin-card--maintenance { margin-bottom: 1rem; } +.maintenance-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .7rem; } +.maintenance-item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: .6rem; padding: .75rem; background: #101a30; border: 1px solid #26334f; border-radius: .5rem; } +.maintenance-item > div { display: grid; gap: .2rem; min-width: 0; } +.maintenance-item strong { color: #e2e8f0; font-size: .74rem; }.maintenance-item small { color: #64748b; font-size: .65rem; white-space: nowrap; } +.maintenance-icon { display: grid; place-items: center; width: 1.8rem; height: 1.8rem; border-radius: .5rem; font-weight: 800; } +.maintenance-icon--green { color: #a7f3d0; background: #065f46; }.maintenance-icon--purple { color: #ddd6fe; background: #4c1d95; }.maintenance-icon--blue { color: #bfdbfe; background: #1e3a5f; }.maintenance-icon--amber { color: #fde68a; background: #78350f; } +.admin-chart-placeholder { padding: .7rem 0 0; } +.chart-bars { display: flex; align-items: end; gap: .45rem; height: 9rem; padding: 0 .3rem; border-bottom: 1px solid #334155; } +.chart-bars i { display: block; flex: 1; min-width: .4rem; background: linear-gradient(180deg, var(--admin-lilac), #4c1d95); border-radius: .25rem .25rem 0 0; opacity: .85; } +.chart-axis { display: flex; justify-content: space-between; padding-top: .5rem; color: #64748b; font-size: .62rem; } +.audit-list { display: grid; gap: .85rem; } +.audit-list > div { display: flex; align-items: start; gap: .6rem; } +.audit-list p { display: grid; gap: .18rem; margin: 0; }.audit-list strong { color: #e2e8f0; font-size: .75rem; }.audit-list small { color: #64748b; font-size: .66rem; } +.audit-dot { width: .55rem; height: .55rem; margin-top: .35rem; border-radius: 50%; }.audit-dot--purple { background: var(--admin-lilac); }.audit-dot--blue { background: #60a5fa; }.audit-dot--amber { background: var(--admin-amber); } + +/* Workspace surface */ +.workspace-page { min-height: 100vh; color: var(--workspace-text); background: var(--workspace-bg); } +.workspace-shell { display: grid; grid-template-columns: 15rem minmax(0, 1fr); min-height: 100vh; } +.workspace-rail { display: flex; flex-direction: column; padding: 1.3rem 1rem; background: #0d1728; border-right: 1px solid var(--workspace-border); } +.brand-lockup--rail { color: #c4b5fd; } +.rail-section-label { margin: 2.3rem .7rem .55rem; color: #64748b; font-size: .62rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; } +.rail-section-label--spaced { margin-top: 1.7rem; } +.rail-nav { display: grid; gap: .2rem; } +.rail-link { display: flex; align-items: center; gap: .7rem; padding: .68rem .7rem; color: var(--workspace-muted); border-radius: .5rem; font-size: .78rem; font-weight: 700; text-decoration: none; } +.rail-link:hover { color: #fff; background: #16263d; }.rail-link--active { color: #fff; background: #1b2f4a; box-shadow: inset 2px 0 var(--workspace-teal); } +.rail-icon { width: 1.1rem; color: #5eead4; text-align: center; font-size: .95rem; } +.rail-footer { margin-top: auto; padding: .9rem .7rem 0; border-top: 1px solid var(--workspace-border); } +.rail-user { display: flex; align-items: center; gap: .55rem; margin-bottom: .8rem; }.rail-user > span:last-child { display: grid; gap: .12rem; }.rail-user strong { color: #e2e8f0; font-size: .73rem; }.rail-user small { color: #64748b; font-size: .63rem; } +.workspace-main { min-width: 0; width: min(1440px, 100%); margin: 0 auto; padding: 0 clamp(1.1rem, 3vw, 2.8rem) 3rem; } +.workspace-topbar { display: flex; align-items: center; justify-content: space-between; min-height: 4.5rem; gap: 1rem; border-bottom: 1px solid var(--workspace-border); } +.breadcrumb { display: flex; align-items: center; gap: .55rem; color: #64748b; font-size: .74rem; }.breadcrumb strong { color: #cbd5e1; }.topbar-actions { display: flex; align-items: center; gap: .8rem; }.icon-button { display: grid; place-items: center; width: 2rem; height: 2rem; color: #93c5fd; background: #16263d; border: 1px solid #2b4363; border-radius: .5rem; }.topbar-admin-link { color: #5eead4; font-size: .72rem; font-weight: 750; text-decoration: none; } +.workspace-hero { display: flex; align-items: end; justify-content: space-between; gap: 1.5rem; padding: 2.6rem 0 2rem; }.workspace-hero h1 { margin: 0 0 .45rem; font-size: clamp(2.2rem, 5vw, 4rem); letter-spacing: -.06em; }.hero-subtitle { max-width: 38rem; margin: 0; color: var(--workspace-muted); font-size: .9rem; line-height: 1.6; }.hero-actions { display: flex; gap: .6rem; flex-wrap: wrap; } +.pipeline-card, .workspace-panel { padding: 1.2rem; background: var(--workspace-panel); border: 1px solid var(--workspace-border); border-radius: .8rem; }.pipeline-card { margin-bottom: 1rem; }.section-heading { display: flex; align-items: start; justify-content: space-between; gap: 1rem; }.section-heading h2 { margin: 0; font-size: 1.05rem; letter-spacing: -.02em; }.section-heading .text-button { color: var(--workspace-teal); } +.pipeline-track { display: flex; align-items: center; margin-top: 1.5rem; overflow-x: auto; padding: .25rem 0 .4rem; }.pipeline-step { display: grid; justify-items: center; gap: .2rem; min-width: 5.5rem; text-align: center; }.pipeline-number { display: grid; place-items: center; width: 2rem; height: 2rem; color: #64748b; background: #1b2a40; border: 2px solid #3b4d69; border-radius: 50%; font-size: .72rem; font-weight: 800; }.pipeline-step strong { color: #cbd5e1; font-size: .72rem; }.pipeline-step small { color: #64748b; font-size: .62rem; }.pipeline-step--done .pipeline-number { color: #99f6e4; background: #115e59; border-color: #2dd4bf; }.pipeline-step--active .pipeline-number { color: #062a28; background: var(--workspace-teal); border-color: var(--workspace-teal); }.pipeline-step--active strong { color: #fff; }.pipeline-line { flex: 1; min-width: 1.2rem; height: 2px; margin: 0 .4rem 1.6rem; background: #2b3c58; }.pipeline-line--done { background: #2dd4bf; } +.metric-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .8rem; margin-bottom: 1rem; }.metric-card { display: grid; gap: .3rem; padding: 1rem; background: var(--workspace-panel-raised); border: 1px solid var(--workspace-border); border-radius: .7rem; }.metric-label { color: var(--workspace-muted); font-size: .68rem; }.metric-card strong { font-size: 1.8rem; letter-spacing: -.05em; }.metric-note { color: #64748b; font-size: .65rem; }.metric-note--good { color: #6ee7b7; }.metric-note--warn { color: #fcd34d; } +.workspace-columns { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(18rem, .75fr); gap: 1rem; margin-bottom: 1rem; }.workspace-panel { min-width: 0; }.language-list { display: grid; gap: .2rem; margin-top: 1rem; }.language-row { display: grid; grid-template-columns: 2.2rem minmax(8rem, 1fr) minmax(5rem, .8fr) 2.5rem auto; align-items: center; gap: .7rem; padding: .65rem .2rem; border-top: 1px solid #1e2f48; }.language-code { display: grid; place-items: center; width: 2rem; height: 2rem; color: #bfdbfe; background: #1e3a5f; border-radius: .5rem; font-size: .62rem; font-weight: 800; }.language-code--source { color: #99f6e4; background: #115e59; }.language-name { display: grid; gap: .15rem; }.language-name strong { font-size: .76rem; }.language-name small { color: #64748b; font-size: .63rem; }.progress-track { height: .35rem; overflow: hidden; background: #243650; border-radius: 999px; }.progress-track span { display: block; height: 100%; background: linear-gradient(90deg, #14b8a6, #5eead4); border-radius: inherit; }.language-percent { color: #cbd5e1; font-size: .7rem; text-align: right; } +.cast-summary { display: flex; align-items: center; gap: 1rem; margin: 1.1rem 0 1.2rem; }.cast-summary p { margin: .35rem 0 0; color: #64748b; font-size: .68rem; line-height: 1.5; }.cast-ring { display: grid; place-items: center; width: 4.6rem; height: 4.6rem; border: 5px solid #1f4152; border-top-color: var(--workspace-teal); border-right-color: var(--workspace-teal); border-radius: 50%; }.cast-ring strong { font-size: .8rem; }.cast-ring span { color: #64748b; font-size: .55rem; }.cast-avatars { display: flex; padding-left: .3rem; }.cast-avatar { display: grid; place-items: center; width: 2rem; height: 2rem; margin-left: -.3rem; color: #e0e7ff; border: 2px solid var(--workspace-panel); border-radius: 50%; font-size: .58rem; font-weight: 800; }.cast-avatar--one { background: #7c3aed; }.cast-avatar--two { background: #2563eb; }.cast-avatar--three { background: #b45309; }.cast-avatar--four { background: #be185d; }.cast-avatar--more { color: #94a3b8; background: #243650; } +.activity-list { display: grid; gap: .2rem; margin-top: 1rem; }.activity-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: .7rem; padding: .7rem .2rem; border-top: 1px solid #1e2f48; }.activity-row > div { display: grid; gap: .15rem; }.activity-row strong { font-size: .76rem; }.activity-row small { color: #64748b; font-size: .63rem; }.activity-icon { display: grid; place-items: center; width: 1.8rem; height: 1.8rem; border-radius: .5rem; font-weight: 800; }.activity-icon--teal { color: #99f6e4; background: #115e59; }.activity-icon--amber { color: #fde68a; background: #78350f; }.activity-icon--blue { color: #bfdbfe; background: #1e3a5f; } + +/* Modal and toast */ +.modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 1rem; background: rgba(2, 6, 23, .78); backdrop-filter: blur(7px); }.modal-backdrop[hidden] { display: none; }.modal { width: min(100%, 32rem); max-height: calc(100vh - 2rem); overflow: auto; padding: 1.3rem; background: var(--admin-panel); border: 1px solid var(--admin-border-bright); border-radius: .8rem; box-shadow: var(--shadow); }.modal-heading { display: flex; justify-content: space-between; gap: 1rem; }.modal-heading h2 { margin: 0; color: #fff; }.modal-close { width: 2rem; height: 2rem; color: #94a3b8; background: transparent; border: 1px solid var(--admin-border); border-radius: .5rem; font-size: 1.2rem; }.modal-notice { margin: 1rem 0; padding: .75rem; color: #c4b5fd; background: rgba(124, 58, 237, .1); border: 1px solid rgba(167, 139, 250, .25); border-radius: .5rem; font-size: .72rem; line-height: 1.55; }.modal-notice strong { color: #fff; }.checkbox-row { display: flex !important; align-items: center; gap: .5rem; margin-top: .8rem; color: #cbd5e1 !important; font-weight: 500 !important; }.checkbox-row input { width: auto; min-height: auto; }.modal-actions { display: flex; justify-content: end; gap: .6rem; margin-top: 1.2rem; }.toast { position: fixed; right: 1.2rem; bottom: 1.2rem; z-index: 30; max-width: min(26rem, calc(100% - 2rem)); padding: .8rem 1rem; color: #e0e7ff; background: #1e293b; border: 1px solid #475569; border-radius: .6rem; box-shadow: var(--shadow); font-size: .75rem; opacity: 0; transform: translateY(.6rem); pointer-events: none; transition: opacity .2s ease, transform .2s ease; }.toast--visible { opacity: 1; transform: translateY(0); } + +@media (max-width: 1000px) { + .auth-shell { grid-template-columns: 1fr; gap: 1.5rem; width: min(38rem, calc(100% - 2rem)); padding: 2rem 0; }.auth-brand { min-height: 0; gap: 3rem; }.brand-copy { margin: 2rem 0; }.brand-copy h1 { max-width: 15ch; font-size: 3.4rem; }.brand-points { margin: 1.5rem 0; } + .admin-stat-grid, .maintenance-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }.admin-columns, .admin-columns--lower, .workspace-columns { grid-template-columns: 1fr; } + .workspace-rail { width: 13rem; }.workspace-shell { grid-template-columns: 13rem minmax(0, 1fr); } +} +@media (max-width: 720px) { + .admin-layout, .workspace-shell { display: block; }.admin-sidebar, .workspace-rail { min-height: auto; border-right: 0; border-bottom: 1px solid var(--admin-border); }.admin-sidebar { display: none; }.workspace-rail { padding: .8rem 1rem; }.rail-section-label, .rail-section-label--spaced, .rail-footer { display: none; }.rail-nav { display: flex; gap: .25rem; overflow-x: auto; }.rail-link { white-space: nowrap; }.rail-link:not(.rail-link--active) { display: none; }.workspace-rail .brand-lockup { margin-bottom: .6rem; }.workspace-topbar { min-height: 3.7rem; }.topbar-admin-link, .status-pill { display: none; }.workspace-hero { display: block; padding: 1.8rem 0 1.2rem; }.hero-actions { margin-top: 1rem; }.metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }.language-row { grid-template-columns: 2.2rem minmax(0, 1fr) auto; }.language-row .progress-track, .language-row .language-percent { display: none; }.admin-topbar { flex-wrap: wrap; gap: .7rem; }.admin-context { padding-left: 0; border-left: 0; }.admin-top-actions { width: 100%; margin-left: 0; justify-content: space-between; }.admin-heading { display: block; }.admin-heading .button { margin-top: 1rem; }.admin-stat strong { font-size: 1.6rem; } +} diff --git a/web/assets/umbratrans-icon.jpg b/web/assets/umbratrans-icon.jpg new file mode 100644 index 0000000..13a4940 Binary files /dev/null and b/web/assets/umbratrans-icon.jpg differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..02b01be --- /dev/null +++ b/web/index.html @@ -0,0 +1,53 @@ + + + + + + + Sign in · UmbraTrans + + + + +
+
+ + + UmbraTrans + +
+

Translation production workspace

+

One source of truth for every language.

+

Review books, translations, voices, and audio delivery from one project-aware workspace.

+
+
+
Project-scoped access
+
Shared speaker and voice identity
+
Traceable analysis and delivery lineage
+
+

Prototype environment · no credentials are transmitted

+
+ +
+
+ Secure workspace +

Sign in to your workspace

+

Use your UmbraTrans account. Project access is determined by your organization membership.

+
+
+ + + + +
+ + +
+
Administrator?
+ Open control panel +

First login with an administrator-issued temporary password will require a password change before workspace access.

+
+
+ + + diff --git a/web/uadmin/index.html b/web/uadmin/index.html new file mode 100644 index 0000000..6612786 --- /dev/null +++ b/web/uadmin/index.html @@ -0,0 +1,99 @@ + + + + + + + Control panel · UmbraTrans + + + + +
+
+ UmbraTrans +
Control panelSystem administration
+
OP OperatorExit admin ↗
+
+
+ +
+
Prototype control panelButtons demonstrate the intended management flow. No accounts, passwords, or database records are changed.
+
+

Administration

System overview

Manage identities, project access, maintenance, and service health.

+ +
+ +
+
Active users24↑ 3 this month
+
Organizations32 active · 1 pending
+
Projects11↑ 2 in production
+
Worker healthGoodLast check 2 min ago
+
+ +
+
+

Identity

Accounts

+
+ + + + +
UserOrganizationRoleStatusLast activity
AMAlex Morganalex@example.com
Northstar MediaProject managerActive4 min ago
SKSam Klinesam@example.com
Northstar MediaTranslatorMFA enabled18 min ago
JRJamie Reedjamie@example.com
Harbor HouseAudio directorTemp passwordNever
TPTaylor Pricetaylor@example.com
Harbor HouseViewerInvitedNever
+
+
+

Tenancy

Projects

+
+
DH
Deal HunterNorthstar Media · 4 locales
Audio in progress
+
TI
Treasure IslandNorthstar Media · 2 locales
Ready for review
+
LD
Last DwarfHarbor House · 1 locale
Draft
+
+
+
+ +
+

Operations

Maintenance

All systems nominal
+
+
✓
Audio workers3 online · 0 stalled
+
↻
Background jobs42 queued · 3 retrying
+
◫
StorageObject storage connected
+
!
Password policy2 temporary accounts pending first login
+
+
+ +
+

Usage

Production activity

Last 30 days
Aug 26Sep 09Sep 26
+

Traceability

Recent audit events

Project access updatedAlex Morgan · 4 min ago

Translation batch approvedSam Kline · 18 min ago

Temporary account provisionedOperator · 1 hr ago

+
+
+
+
+ + +
+ + + diff --git a/web/workspace.html b/web/workspace.html new file mode 100644 index 0000000..706fed4 --- /dev/null +++ b/web/workspace.html @@ -0,0 +1,114 @@ + + + + + + Workspace · UmbraTrans + + + + +
+ + +
+
+ +
+ Prototype data + + Control panel ↗ +
+
+ +
+
+

Active project · English master

+

Deal Hunter

+

Production workspace for the source analysis, translations, voice cast, and audio deliverables.

+
+
+ + +
+
+ +
+
+

Production pipeline

Release readiness

+ Last reconciled 4 minutes ago +
+
+
✓SourceImported
+
+
✓Analysis12 speakers
+
+
2Voices8 of 12 cast
+
+
3AudioNot started
+
+
4ReleaseNot started
+
+
+ +
+
Target languages42 in progress
+
Approved speakers12All identities linked
+
Voice cast84 need review
+
Open work items17Across 4 locales
+
+ +
+
+

Language lanes

Translation progress

+
+
EN
English masterSource analysis
100%Ready
+
DE
GermanTranslation and proof
72%In review
+
FR
FrenchTranslation draft
41%In progress
+
ES
SpanishQueued for review
18%Queued
+
+
+ +
+

Identity

Voice cast

+
67%cast
8 of 12 speakers assigned

Voice profiles are shared across language lanes after rights review.

+
MCJRALSK+4
+
+
+ +
+

Operations

Recent activity

+
+
✓
German proofing batch approvedProject manager · 18 minutes ago
Complete
+
◉
Voice cast needs review4 speakers awaiting rights confirmation
Action
+
↻
French translation synchronizedTranslation worker · 42 minutes ago
Synced
+
+
+
+
+
+ + +